Skip to content
Merged
Show file tree
Hide file tree
Changes from 82 commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
ab2b2de
remove CompilationPair to see waht breas
CyrusNajmabadi Aug 29, 2025
1114e46
in progress
CyrusNajmabadi Aug 29, 2025
2527492
Compiling
CyrusNajmabadi Aug 29, 2025
c37ee9e
Fix tests
CyrusNajmabadi Aug 29, 2025
a46e753
cleanup
CyrusNajmabadi Aug 29, 2025
b051d87
cleanup
CyrusNajmabadi Aug 29, 2025
2c15ab0
cleanup
CyrusNajmabadi Aug 29, 2025
8ab740e
cleanup
CyrusNajmabadi Aug 29, 2025
522ba7f
cleanup
CyrusNajmabadi Aug 29, 2025
750bb54
cleanup
CyrusNajmabadi Aug 29, 2025
06838ce
cleanup
CyrusNajmabadi Aug 29, 2025
cb533f9
cleanup
CyrusNajmabadi Aug 29, 2025
28b21a8
cleanup
CyrusNajmabadi Aug 29, 2025
06364bc
cleanup
CyrusNajmabadi Aug 29, 2025
dabdd08
cleanup
CyrusNajmabadi Aug 29, 2025
5273899
cleanup
CyrusNajmabadi Aug 29, 2025
325cfbb
cleanup
CyrusNajmabadi Aug 29, 2025
1583c82
Merge remote-tracking branch 'upstream/main' into noCompilationPair
CyrusNajmabadi Aug 30, 2025
ccae16c
Remove comments
CyrusNajmabadi Aug 30, 2025
8ab1449
Merge branch 'main' into noCompilationPair
CyrusNajmabadi Sep 1, 2025
10d5103
Merge branch 'main' into noCompilationPair
CyrusNajmabadi Sep 1, 2025
ceb530c
Fixtest
CyrusNajmabadi Sep 1, 2025
7cf5a48
Merge
CyrusNajmabadi Sep 1, 2025
6398a7b
Clean
CyrusNajmabadi Sep 1, 2025
a114a53
Rename
CyrusNajmabadi Sep 1, 2025
48f563e
hide
CyrusNajmabadi Sep 1, 2025
006d929
Docs
CyrusNajmabadi Sep 1, 2025
5ff9b15
cleanup
CyrusNajmabadi Sep 1, 2025
bf367f6
cleanup
CyrusNajmabadi Sep 1, 2025
22c861a
Cleanup
CyrusNajmabadi Sep 1, 2025
f4eab9a
Fix
CyrusNajmabadi Sep 1, 2025
2180608
Fix keys
CyrusNajmabadi Sep 1, 2025
b1d93a6
Fix naming styles
CyrusNajmabadi Sep 2, 2025
45c14ba
Fix test
CyrusNajmabadi Sep 2, 2025
d244d54
Simplify
CyrusNajmabadi Sep 2, 2025
e7e6930
Feed into compiler layer
CyrusNajmabadi Sep 4, 2025
c199b42
Compute up front
CyrusNajmabadi Sep 4, 2025
6f8701b
Update APIs
CyrusNajmabadi Sep 4, 2025
67b6539
in progress
CyrusNajmabadi Sep 4, 2025
cf08a44
IDE side
CyrusNajmabadi Sep 4, 2025
d9f15e9
Fix tests
CyrusNajmabadi Sep 4, 2025
53511e0
Merge branch 'removeCodeOnlyCalledByTests' into noCompilationPairWith…
CyrusNajmabadi Sep 4, 2025
e508df7
Delete
CyrusNajmabadi Sep 4, 2025
30ebad7
Simplify
CyrusNajmabadi Sep 4, 2025
57f1490
Update
CyrusNajmabadi Sep 4, 2025
649ea9d
Simplify
CyrusNajmabadi Sep 4, 2025
0f8fdca
Use reference equality
CyrusNajmabadi Sep 4, 2025
8be5be6
Merge branch 'removeCodeOnlyCalledByTests' into noCompilationPairWith…
CyrusNajmabadi Sep 4, 2025
e472d23
Add explicit test
CyrusNajmabadi Sep 4, 2025
d581b39
Add test
CyrusNajmabadi Sep 4, 2025
3777db4
Fix
CyrusNajmabadi Sep 4, 2025
9d4e4fa
Docs
CyrusNajmabadi Sep 4, 2025
05b2b8e
Simplify
CyrusNajmabadi Sep 4, 2025
733478e
Merge branch 'main' into noCompilationPairWithApiChange
CyrusNajmabadi Sep 4, 2025
c8134f4
remove
CyrusNajmabadi Sep 4, 2025
a711435
Use options provider
CyrusNajmabadi Sep 4, 2025
d140723
IDE side
CyrusNajmabadi Sep 4, 2025
4ad5326
Use options provider
CyrusNajmabadi Sep 4, 2025
bde3304
Flesh out tests
CyrusNajmabadi Sep 4, 2025
42f56a4
Merge tetts
CyrusNajmabadi Sep 4, 2025
a545545
Docs
CyrusNajmabadi Sep 4, 2025
f783d93
Merge remote-tracking branch 'upstream/main' into noCompilationPairWi…
CyrusNajmabadi Sep 9, 2025
823a02d
renames
CyrusNajmabadi Sep 9, 2025
1205eef
Update src/Tools/SemanticSearch/ReferenceAssemblies/Apis/Microsoft.Co…
CyrusNajmabadi Sep 9, 2025
e93dd41
REvert
CyrusNajmabadi Sep 9, 2025
e23e41a
Update src/Compilers/CSharp/Test/Emit3/Diagnostics/DiagnosticAnalyzer…
CyrusNajmabadi Sep 10, 2025
e60b0b3
Merge remote-tracking branch 'upstream/main' into compilationOptionsApi
CyrusNajmabadi Sep 10, 2025
10efd55
One arg per line
CyrusNajmabadi Sep 10, 2025
38b020e
Docs
CyrusNajmabadi Sep 10, 2025
0a60001
Pass options around less
CyrusNajmabadi Sep 10, 2025
b20dc51
Use extension
CyrusNajmabadi Sep 10, 2025
4c1808d
Use extension
CyrusNajmabadi Sep 10, 2025
72f6a29
IN progress
CyrusNajmabadi Sep 10, 2025
cd30de1
Use analyzer specific options in more locations
CyrusNajmabadi Sep 10, 2025
7ee1fd6
Remove
CyrusNajmabadi Sep 10, 2025
811f0d9
Remove
CyrusNajmabadi Sep 10, 2025
c72cfc5
Remove
CyrusNajmabadi Sep 10, 2025
f0cb9d7
Fix tests
CyrusNajmabadi Sep 10, 2025
d70f51d
Fix tests
CyrusNajmabadi Sep 10, 2025
eb2f324
Fix tests
CyrusNajmabadi Sep 10, 2025
5a6dd89
Fix tests
CyrusNajmabadi Sep 10, 2025
b7e535a
Fix tests
CyrusNajmabadi Sep 10, 2025
d9e9d9a
Simplify
CyrusNajmabadi Sep 10, 2025
cd6325d
simplify
CyrusNajmabadi Sep 10, 2025
2d57345
Apply suggestions from code review
CyrusNajmabadi Sep 10, 2025
5d65b24
Fixes
CyrusNajmabadi Sep 10, 2025
5d4831b
Merge branch 'main' into compilationOptionsApi
CyrusNajmabadi Sep 12, 2025
9dc329b
Move method
CyrusNajmabadi Sep 12, 2025
4149c67
Doc
CyrusNajmabadi Sep 12, 2025
1c82871
Update src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs
CyrusNajmabadi Sep 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Roslyn.Test.Utilities.TestGenerators;
using Roslyn.Utilities;
using Xunit;

Expand Down Expand Up @@ -4441,5 +4442,203 @@ record B(int I) : A(I);
var diagnostics = await compWithAnalyzers.GetAnalyzerSemanticDiagnosticsAsync(model, filterSpan: null, CancellationToken.None);
diagnostics.Verify(Diagnostic("ID0001", "B").WithLocation(1, 8));
}

private sealed class OptionsOverrideDiagnosticAnalyzer(AnalyzerConfigOptionsProvider customOptions) : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor s_descriptor = new DiagnosticDescriptor(
id: "ID0001",
title: "Title",
messageFormat: "Message",
category: "Category",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);

private readonly AnalyzerConfigOptionsProvider _customOptions = customOptions;

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [s_descriptor];

public bool RegisterAdditionalFileActionInvoked { get; private set; }
public bool RegisterCodeBlockActionInvoked { get; private set; }
public bool RegisterCodeBlockStartActionInvoked { get; private set; }
public bool RegisterCompilationActionInvoked { get; private set; }
public bool RegisterOperationActionInvoked { get; private set; }
public bool RegisterOperationBlockActionInvoked { get; private set; }
public bool RegisterSemanticModelActionInvoked { get; private set; }
public bool RegisterSymbolActionInvoked { get; private set; }
public bool RegisterSyntaxNodeActionInvoked { get; private set; }
public bool RegisterSyntaxTreeActionInvoked { get; private set; }

public bool RegisterOperationBlockStartActionInvoked { get; private set; }
public bool RegisterOperationBlockEndActionInvoked { get; private set; }
public bool RegisterCompilationStartActionInvoked { get; private set; }
public bool RegisterCompilationEndActionInvoked { get; private set; }
public bool RegisterSymbolStartActionInvoked { get; private set; }
public bool RegisterSymbolEndActionInvoked { get; private set; }

public AnalyzerOptions SeenOptions;

private void AssertSame(AnalyzerOptions options)
{
// First, assert that the options provider we see is the custom one the test sets.
Assert.Same(options.AnalyzerConfigOptionsProvider, _customOptions);

if (SeenOptions is null)
SeenOptions = options;

// Also ensure that the compiler actually passes the same AnalyzerOptions wrapper around
// the options provider. That ensures we're not accidentally creating new instances unnecessarily.
Assert.Same(SeenOptions, options);
}

public override void Initialize(AnalysisContext context)
{
context.RegisterAdditionalFileAction(context => { AssertSame(context.Options); RegisterAdditionalFileActionInvoked = true; });
context.RegisterCodeBlockAction(context => { AssertSame(context.Options); RegisterCodeBlockActionInvoked = true; });
context.RegisterCodeBlockStartAction<SyntaxKind>(context => { AssertSame(context.Options); RegisterCodeBlockStartActionInvoked = true; });
context.RegisterCompilationAction(context => { AssertSame(context.Options); RegisterCompilationActionInvoked = true; });
context.RegisterOperationAction(context => { AssertSame(context.Options); RegisterOperationActionInvoked = true; }, OperationKind.Block);
context.RegisterOperationBlockAction(context => { AssertSame(context.Options); RegisterOperationBlockActionInvoked = true; });
context.RegisterSemanticModelAction(context => { AssertSame(context.Options); RegisterSemanticModelActionInvoked = true; });
context.RegisterSymbolAction(context => { AssertSame(context.Options); RegisterSymbolActionInvoked = true; }, SymbolKind.NamedType);
context.RegisterSyntaxNodeAction(context => { AssertSame(context.Options); RegisterSyntaxNodeActionInvoked = true; }, SyntaxKind.ClassDeclaration);
context.RegisterSyntaxTreeAction(context => { AssertSame(context.Options); RegisterSyntaxTreeActionInvoked = true; });

context.RegisterOperationBlockStartAction(context =>
{
AssertSame(context.Options);
RegisterOperationBlockStartActionInvoked = true;
context.RegisterOperationBlockEndAction(context =>
{
AssertSame(context.Options);
RegisterOperationBlockEndActionInvoked = true;
});
});

context.RegisterCompilationStartAction(context =>
{
AssertSame(context.Options);
RegisterCompilationStartActionInvoked = true;
context.RegisterCompilationEndAction(context =>
{
AssertSame(context.Options);
RegisterCompilationEndActionInvoked = true;
});
});
context.RegisterSymbolStartAction(context =>
{
AssertSame(context.Options);
RegisterSymbolStartActionInvoked = true;
context.RegisterSymbolEndAction(context =>
{
AssertSame(context.Options);
RegisterSymbolEndActionInvoked = true;
});
}, SymbolKind.NamedType);
}

public void AssertAllCallbacksInvoked()
{
Assert.NotNull(SeenOptions);

Assert.True(RegisterAdditionalFileActionInvoked);

Assert.True(RegisterAdditionalFileActionInvoked);
Assert.True(RegisterCodeBlockActionInvoked);
Assert.True(RegisterCodeBlockStartActionInvoked);
Assert.True(RegisterCompilationActionInvoked);
Assert.True(RegisterOperationActionInvoked);
Assert.True(RegisterOperationBlockActionInvoked);
Assert.True(RegisterSemanticModelActionInvoked);
Assert.True(RegisterSymbolActionInvoked);
Assert.True(RegisterSyntaxNodeActionInvoked);
Assert.True(RegisterSyntaxTreeActionInvoked);

Assert.True(RegisterOperationBlockStartActionInvoked);
Assert.True(RegisterOperationBlockEndActionInvoked);
Assert.True(RegisterCompilationStartActionInvoked);
Assert.True(RegisterCompilationEndActionInvoked);
Assert.True(RegisterSymbolStartActionInvoked);
Assert.True(RegisterSymbolEndActionInvoked);
}
}

[Fact]
public async Task TestAnalyzerSpecificOptionsFactory()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test ensures that all callbacks get the right options instance if customized by teh host.

{
// lang=C#-Test
string source = """
class C
{
void M()
{
int x = 0;
}
}
""";

var tree = CSharpSyntaxTree.ParseText(source);
var compilation = CreateCompilationWithCSharp(new[] { tree, CSharpSyntaxTree.ParseText(IsExternalInitTypeDefinition) });
compilation.VerifyDiagnostics(
// (5,13): warning CS0219: The variable 'x' is assigned but its value is never used
// int x = 0;
Diagnostic(ErrorCode.WRN_UnreferencedVarAssg, "x").WithArguments("x").WithLocation(5, 13));

var additionalText = new InMemoryAdditionalText("path", "content");

// Ensure that the analyzer only sees the custom options passed to the callbacks, and never the shared options.
var sharedOptions = new AnalyzerOptions([additionalText]);

// Test1. Just a single analyzer. Ensure all callbacks get the custom options.
{
var customOptions = new CompilerAnalyzerConfigOptionsProvider(
ImmutableDictionary<object, AnalyzerConfigOptions>.Empty,
new DictionaryAnalyzerConfigOptions(
ImmutableDictionary<string, string>.Empty));
Assert.NotSame(sharedOptions, customOptions);

var analyzer = new OptionsOverrideDiagnosticAnalyzer(customOptions);

var compWithAnalyzers = new CompilationWithAnalyzers(
compilation,
[analyzer],
new CompilationWithAnalyzersOptions(
sharedOptions, onAnalyzerException: null, concurrentAnalysis: false, logAnalyzerExecutionTime: false, reportSuppressedDiagnostics: false, analyzerExceptionFilter: null,
_ => customOptions));

var diagnostics = await compWithAnalyzers.GetAllDiagnosticsAsync();
Assert.Single(diagnostics);

analyzer.AssertAllCallbacksInvoked();
}

// Test2. Two analyzers. Ensure both gets the custom options across all callbacks.
// Also, ensure that across the analyzers we're getting the exact same AnalyzerOptions instance.
{
var customOptions = new CompilerAnalyzerConfigOptionsProvider(
ImmutableDictionary<object, AnalyzerConfigOptions>.Empty,
new DictionaryAnalyzerConfigOptions(
ImmutableDictionary<string, string>.Empty));
Assert.NotSame(sharedOptions, customOptions);

var analyzer1 = new OptionsOverrideDiagnosticAnalyzer(customOptions);
var analyzer2 = new OptionsOverrideDiagnosticAnalyzer(customOptions);

var compWithAnalyzers = new CompilationWithAnalyzers(
compilation,
[analyzer1, analyzer2],
new CompilationWithAnalyzersOptions(
sharedOptions, onAnalyzerException: null, concurrentAnalysis: false, logAnalyzerExecutionTime: false, reportSuppressedDiagnostics: false, analyzerExceptionFilter: null,
_ => customOptions));

var diagnostics = await compWithAnalyzers.GetAllDiagnosticsAsync();
Assert.Single(diagnostics);

analyzer1.AssertAllCallbacksInvoked();
analyzer2.AssertAllCallbacksInvoked();

// Both analyzers should get the exact same AnalyzerOptions instance since they used the same customOptions.
Assert.Same(analyzer1.SeenOptions, analyzer2.SeenOptions);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -304,11 +304,21 @@ private static void TestDescriptorIsExceptionSafeCore(DiagnosticDescriptor descr
Action<Exception, DiagnosticAnalyzer, Diagnostic, CancellationToken> onAnalyzerException = (ex, a, diag, ct) => exceptionDiagnostics.Add(diag);
var analyzerManager = new AnalyzerManager(analyzer);
var compilation = CSharp.CSharpCompilation.Create("test");
var analyzerExecutor = AnalyzerExecutor.Create(compilation, AnalyzerOptions.Empty,
addNonCategorizedDiagnostic: (_, _) => { }, onAnalyzerException, analyzerExceptionFilter: null,
isCompilerAnalyzer: _ => false, analyzerManager, shouldSkipAnalysisOnGeneratedCode: _ => false,
shouldSuppressGeneratedCodeDiagnostic: (_, _, _, _) => false, isGeneratedCodeLocation: (_, _, _) => false,
isAnalyzerSuppressedForTree: (_, _, _, _) => false, getAnalyzerGate: _ => null,
var analyzerExecutor = AnalyzerExecutor.Create(
compilation,
AnalyzerOptions.Empty,
addNonCategorizedDiagnostic: (_, _, _) => { },
onAnalyzerException,
analyzerExceptionFilter: null,
isCompilerAnalyzer: _ => false,
diagnosticAnalyzers: [analyzer],
getAnalyzerConfigOptionsProvider: null,
analyzerManager,
shouldSkipAnalysisOnGeneratedCode: _ => false,
shouldSuppressGeneratedCodeDiagnostic: (_, _, _, _) => false,
isGeneratedCodeLocation: (_, _, _) => false,
isAnalyzerSuppressedForTree: (_, _, _, _) => false,
getAnalyzerGate: _ => null,
getSemanticModel: tree => compilation.GetSemanticModel(tree, ignoreAccessibility: true),
SeverityFilter.None);
var descriptors = analyzerManager.GetSupportedDiagnosticDescriptors(analyzer, analyzerExecutor, CancellationToken.None);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,20 +95,19 @@ internal class AnalysisScope

public static AnalysisScope Create(Compilation compilation, ImmutableArray<DiagnosticAnalyzer> analyzers, CompilationWithAnalyzers compilationWithAnalyzers)
{
var analyzerOptions = compilationWithAnalyzers.AnalysisOptions.Options;
var additionalFiles = compilationWithAnalyzers.AnalysisOptions.Options.GetAdditionalFiles();
var hasAllAnalyzers = ComputeHasAllAnalyzers(analyzers, compilationWithAnalyzers);
var concurrentAnalysis = compilationWithAnalyzers.AnalysisOptions.ConcurrentAnalysis;
return Create(compilation, analyzerOptions, analyzers, hasAllAnalyzers, concurrentAnalysis);
return Create(compilation, additionalFiles, analyzers, hasAllAnalyzers, concurrentAnalysis);
}

public static AnalysisScope CreateForBatchCompile(Compilation compilation, AnalyzerOptions analyzerOptions, ImmutableArray<DiagnosticAnalyzer> analyzers)
public static AnalysisScope CreateForBatchCompile(Compilation compilation, ImmutableArray<AdditionalText> additionalFiles, ImmutableArray<DiagnosticAnalyzer> analyzers)
{
return Create(compilation, analyzerOptions, analyzers, hasAllAnalyzers: true, concurrentAnalysis: compilation.Options.ConcurrentBuild);
return Create(compilation, additionalFiles, analyzers, hasAllAnalyzers: true, concurrentAnalysis: compilation.Options.ConcurrentBuild);
}

private static AnalysisScope Create(Compilation compilation, AnalyzerOptions? analyzerOptions, ImmutableArray<DiagnosticAnalyzer> analyzers, bool hasAllAnalyzers, bool concurrentAnalysis)
private static AnalysisScope Create(Compilation compilation, ImmutableArray<AdditionalText> additionalFiles, ImmutableArray<DiagnosticAnalyzer> analyzers, bool hasAllAnalyzers, bool concurrentAnalysis)
{
var additionalFiles = analyzerOptions?.AdditionalFiles ?? ImmutableArray<AdditionalText>.Empty;
return new AnalysisScope(compilation.CommonSyntaxTrees, additionalFiles,
analyzers, hasAllAnalyzers, filterFile: null, filterSpanOpt: null,
originalFilterFile: null, originalFilterSpan: null, isSyntacticSingleFileAnalysis: false,
Expand Down
Loading
Loading