Accessing MSBuild properties and metadata in DiagnosticAnalyzer
s is actually quite similar to how they're read and tested in ISourceGenerator
s/IIncrementalGenerator
s, since Source Generators are technically .NET analyzers as well.
I assume that the documentation you've mentioned is the Source Generators Cookbook.
First, we need to make the MSBuild property available to the global analyzer config options of the analyzer:
<Project>
<ItemGroup>
<CompilerVisibleProperty Include="MyAnalyzer_MyProperty" />
</ItemGroup>
</Project>
Then, we may read the value of that property from the AnalyzerConfigOptionsProvider's GlobalOptions
. You'll find it within the parameter of the AnalysisContext.Register*
method of your choice that you use within your overridden DiagnosticAnalyzer.Initialize(AnalysisContext)
method.
For example RegisterCompilationAction
:
bool isEnabled = false;
if (compilationAnalysisContext.Options.AnalyzerConfigOptionsProvider.GlobalOptions.TryGetValue("build_property.MyAnalyzer_MyProperty", out string? value))
{
isEnabled = value.Equals("true", StringComparison.OrdinalIgnoreCase) || value.Equals("enable", StringComparison.OrdinalIgnoreCase);
}
ImmutableDictionary<string, string?>.Builder properties = ImmutableDictionary.CreateBuilder<string, string?>();
if (isEnabled)
{
properties.Add("IsEnabled", value);
}
var diagnostic = Diagnostic.Create(Rule, location, properties.ToImmutable());
compilationAnalysisContext.ReportDiagnostic(diagnostic);
The CodeFixProvider
's CodeFixContext
does not have a dedicated AnalyzerOptions Options
property, but you may pass the value via the Diagnostic.Properties
:
foreach (Diagnostic diagnostic in context.Diagnostics)
{
if (diagnostic.Properties.TryGetValue("IsEnabled", out string? value))
{
var action = CodeAction.Create(Title, cancellationToken => OnCreateChangedDocument(context.Document, cancellationToken), diagnostic.Id);
context.RegisterCodeFix(action, diagnostic);
}
}
... or, what I just discovered while composing this answer, access the AnalyzerConfigOptionsProvider
through CodeFixContext.Document.Project.AnalyzerOptions
. This works wherever you have a Document
(or Project
) available:
public override Task RegisterCodeFixesAsync(CodeFixContext context)
{
bool hasValue = context.Document.Project.AnalyzerOptions.AnalyzerConfigOptionsProvider.GlobalOptions.TryGetValue("build_property.MyAnalyzer_MyProperty", out string? value);
}
Additionally, it works with CodeRefactoringProvider
:
public override Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
bool hasValue = context.Document.Project.AnalyzerOptions.AnalyzerConfigOptionsProvider.GlobalOptions.TryGetValue("build_property.MyAnalyzer_MyProperty", out string? value);
return Task.CompletedTask;
}
... and with CompletionProvider
:
public override Task ProvideCompletionsAsync(CompletionContext context)
{
bool hasValue = context.Document.Project.AnalyzerOptions.AnalyzerConfigOptionsProvider.GlobalOptions.TryGetValue("build_property.MyAnalyzer_MyProperty", out string? value);
return Task.CompletedTask;
}
... and also with DiagnosticSuppressor
:
public override void ReportSuppressions(SuppressionAnalysisContext context)
{
bool hasValue = context.Options.AnalyzerConfigOptionsProvider.GlobalOptions.TryGetValue("build_property.MyAnalyzer_MyProperty", out string? value);
}
And for testing via Microsoft.CodeAnalysis.Testing, you can add the global analyzer config option via ProjectState.AnalyzerConfigFiles
of the AnalyzerTest<TVerifier>
's SolutionState TestState
property:
string config = $"is_global = true{Environment.NewLine}build_property.MyAnalyzer_MyProperty = {true}";
analyzerTest.TestState.AnalyzerConfigFiles.Add(("/.globalconfig", config));
Above I described the usage with the custom MSBuild property MyAnalyzer_MyProperty
, but of course it works with the well-known ImplicitUsings
property too.