Skip to content

Commit

Permalink
Expose AnalysisContext.MinimumReportedSeverity
Browse files Browse the repository at this point in the history
Implements the compiler/analyzer driver change from dotnet#52991 (comment)
This flag indicates the minimum reported diagnostic severity for analyzer execution context. For command line builds, this will be warning, while for the IDE case this will be hidden.
Analyzer diagnostics with severity lesser than this severity are not reported by the driver.

This flag will allow the IDE code style analyzers to turn on/off in command line builds based on `option_name = option_value:severity` entries in editorconfig.
  • Loading branch information
mavasani committed May 13, 2021
1 parent 8af8e62 commit d4ecad2
Show file tree
Hide file tree
Showing 9 changed files with 86 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3762,5 +3762,29 @@ public void VerifyCachedModel(SyntaxTree tree, SemanticModel model)
Assert.Same(model, _cache[tree]);
}
}

[Theory]
// IDE scenario where no reported severities are filtered.
[InlineData(SeverityFilter.None, DiagnosticSeverity.Hidden)]
// Command line scenario where hidden and info severities are filtered.
[InlineData(SeverityFilter.Hidden | SeverityFilter.Info, DiagnosticSeverity.Warning)]
internal async Task TestMinimumReportedSeverity(SeverityFilter severityFilter, DiagnosticSeverity expectedMinimumReportedSeverity)
{
var tree = CSharpSyntaxTree.ParseText(@"class C { }");
var compilation = CreateCompilation(new[] { tree });

var analyzer = new MinimumReportedSeverityAnalyzer();
var analyzersArray = ImmutableArray.Create<DiagnosticAnalyzer>(analyzer);
var analyzerManager = new AnalyzerManager(analyzersArray);
var driver = AnalyzerDriver.CreateAndAttachToCompilation(compilation, analyzersArray, AnalyzerOptions.Empty, analyzerManager, onAnalyzerException: null,
analyzerExceptionFilter: null, reportAnalyzer: false, severityFilter, out var newCompilation, CancellationToken.None);

// Force complete compilation event queue and analyzer execution.
_ = newCompilation.GetDiagnostics(CancellationToken.None);
_ = await driver.GetDiagnosticsAsync(newCompilation);

Assert.True(analyzer.AnalyzerInvoked);
Assert.Equal(expectedMinimumReportedSeverity, analyzer.MinimumReportedSeverity);
}
}
}
15 changes: 15 additions & 0 deletions src/Compilers/Core/Portable/Diagnostic/SeverityFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,20 @@ internal static bool Contains(this SeverityFilter severityFilter, ReportDiagnost
_ => false
};
}

internal static DiagnosticSeverity GetMinimumUnfilteredSeverity(this SeverityFilter severityFilter)
{
if ((severityFilter & SeverityFilter.Hidden) == 0)
{
return DiagnosticSeverity.Hidden;
}

if ((severityFilter & SeverityFilter.Info) == 0)
{
return DiagnosticSeverity.Info;
}

return DiagnosticSeverity.Warning;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ internal void Initialize(
var analyzerExecutor = AnalyzerExecutor.Create(
compilation, analysisOptions.Options ?? AnalyzerOptions.Empty, addNotCategorizedDiagnostic, newOnAnalyzerException, analysisOptions.AnalyzerExceptionFilter,
IsCompilerAnalyzer, AnalyzerManager, ShouldSkipAnalysisOnGeneratedCode, ShouldSuppressGeneratedCodeDiagnostic, IsGeneratedOrHiddenCodeLocation, IsAnalyzerSuppressedForTree, GetAnalyzerGate,
getSemanticModel: GetOrCreateSemanticModel,
getSemanticModel: GetOrCreateSemanticModel, _severityFilter,
analysisOptions.LogAnalyzerExecutionTime, addCategorizedLocalDiagnostic, addCategorizedNonLocalDiagnostic, s => _programmaticSuppressions!.Add(s), cancellationToken);

Initialize(analyzerExecutor, diagnosticQueue, compilationData, cancellationToken);
Expand Down
16 changes: 12 additions & 4 deletions src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ internal partial class AnalyzerExecutor
private readonly Func<DiagnosticAnalyzer, bool>? _isCompilerAnalyzer;
private readonly Func<DiagnosticAnalyzer, object?>? _getAnalyzerGate;
private readonly Func<SyntaxTree, SemanticModel>? _getSemanticModel;
private readonly SeverityFilter _severityFilter;
private readonly Func<DiagnosticAnalyzer, bool> _shouldSkipAnalysisOnGeneratedCode;
private readonly Func<Diagnostic, DiagnosticAnalyzer, Compilation, CancellationToken, bool> _shouldSuppressGeneratedCodeDiagnostic;
private readonly Func<SyntaxTree, TextSpan, bool> _isGeneratedCodeLocation;
Expand Down Expand Up @@ -96,6 +97,7 @@ private bool IsAnalyzerSuppressedForTree(DiagnosticAnalyzer analyzer, SyntaxTree
/// All analyzer callbacks for non-concurrent analyzers will be guarded with a lock on the gate.
/// </param>
/// <param name="getSemanticModel">Delegate to get a semantic model for the given syntax tree which can be shared across analyzers.</param>
/// <param name="severityFilter"><see cref="SeverityFilter"/> for analysis.</param>
/// <param name="shouldSkipAnalysisOnGeneratedCode">Delegate to identify if analysis should be skipped on generated code.</param>
/// <param name="shouldSuppressGeneratedCodeDiagnostic">Delegate to identify if diagnostic reported while analyzing generated code should be suppressed.</param>
/// <param name="isGeneratedCodeLocation">Delegate to identify if the given location is in generated code.</param>
Expand All @@ -119,6 +121,7 @@ public static AnalyzerExecutor Create(
Func<DiagnosticAnalyzer, SyntaxTree, SyntaxTreeOptionsProvider?, bool> isAnalyzerSuppressedForTree,
Func<DiagnosticAnalyzer, object?> getAnalyzerGate,
Func<SyntaxTree, SemanticModel> getSemanticModel,
SeverityFilter severityFilter,
bool logExecutionTime = false,
Action<Diagnostic, DiagnosticAnalyzer, bool>? addCategorizedLocalDiagnostic = null,
Action<Diagnostic, DiagnosticAnalyzer>? addCategorizedNonLocalDiagnostic = null,
Expand All @@ -133,7 +136,7 @@ public static AnalyzerExecutor Create(

return new AnalyzerExecutor(compilation, analyzerOptions, addNonCategorizedDiagnostic, onAnalyzerException, analyzerExceptionFilter,
isCompilerAnalyzer, analyzerManager, shouldSkipAnalysisOnGeneratedCode, shouldSuppressGeneratedCodeDiagnostic, isGeneratedCodeLocation,
isAnalyzerSuppressedForTree, getAnalyzerGate, getSemanticModel, analyzerExecutionTimeMap, addCategorizedLocalDiagnostic, addCategorizedNonLocalDiagnostic,
isAnalyzerSuppressedForTree, getAnalyzerGate, getSemanticModel, severityFilter, analyzerExecutionTimeMap, addCategorizedLocalDiagnostic, addCategorizedNonLocalDiagnostic,
addSuppression, cancellationToken);
}

Expand Down Expand Up @@ -163,6 +166,7 @@ public static AnalyzerExecutor CreateForSupportedDiagnostics(
isAnalyzerSuppressedForTree: null,
getAnalyzerGate: null,
getSemanticModel: null,
severityFilter: SeverityFilter.None,
onAnalyzerException: onAnalyzerException,
analyzerExceptionFilter: null,
analyzerManager: analyzerManager,
Expand All @@ -187,6 +191,7 @@ private AnalyzerExecutor(
Func<DiagnosticAnalyzer, SyntaxTree, SyntaxTreeOptionsProvider?, bool>? isAnalyzerSuppressedForTree,
Func<DiagnosticAnalyzer, object?>? getAnalyzerGate,
Func<SyntaxTree, SemanticModel>? getSemanticModel,
SeverityFilter severityFilter,
ConcurrentDictionary<DiagnosticAnalyzer, StrongBox<long>>? analyzerExecutionTimeMap,
Action<Diagnostic, DiagnosticAnalyzer, bool>? addCategorizedLocalDiagnostic,
Action<Diagnostic, DiagnosticAnalyzer>? addCategorizedNonLocalDiagnostic,
Expand All @@ -206,6 +211,7 @@ private AnalyzerExecutor(
_isAnalyzerSuppressedForTree = isAnalyzerSuppressedForTree;
_getAnalyzerGate = getAnalyzerGate;
_getSemanticModel = getSemanticModel;
_severityFilter = severityFilter;
_analyzerExecutionTimeMap = analyzerExecutionTimeMap;
_addCategorizedLocalDiagnostic = addCategorizedLocalDiagnostic;
_addCategorizedNonLocalDiagnostic = addCategorizedNonLocalDiagnostic;
Expand All @@ -224,7 +230,7 @@ public AnalyzerExecutor WithCancellationToken(CancellationToken cancellationToke

return new AnalyzerExecutor(_compilation, _analyzerOptions, _addNonCategorizedDiagnostic, _onAnalyzerException, _analyzerExceptionFilter,
_isCompilerAnalyzer, _analyzerManager, _shouldSkipAnalysisOnGeneratedCode, _shouldSuppressGeneratedCodeDiagnostic, _isGeneratedCodeLocation,
_isAnalyzerSuppressedForTree, _getAnalyzerGate, _getSemanticModel, _analyzerExecutionTimeMap, _addCategorizedLocalDiagnostic, _addCategorizedNonLocalDiagnostic,
_isAnalyzerSuppressedForTree, _getAnalyzerGate, _getSemanticModel, _severityFilter, _analyzerExecutionTimeMap, _addCategorizedLocalDiagnostic, _addCategorizedNonLocalDiagnostic,
_addSuppression, cancellationToken);
}

Expand Down Expand Up @@ -256,6 +262,7 @@ internal AnalyzerOptions AnalyzerOptions

internal CancellationToken CancellationToken => _cancellationToken;
internal Action<Exception, DiagnosticAnalyzer, Diagnostic> OnAnalyzerException => _onAnalyzerException;
internal SeverityFilter SeverityFilter => _severityFilter;
internal ImmutableDictionary<DiagnosticAnalyzer, TimeSpan> AnalyzerExecutionTimes
{
get
Expand All @@ -270,14 +277,15 @@ internal ImmutableDictionary<DiagnosticAnalyzer, TimeSpan> AnalyzerExecutionTime
/// </summary>
/// <param name="analyzer">Analyzer to get session wide analyzer actions.</param>
/// <param name="sessionScope">Session scope to store register session wide analyzer actions.</param>
/// <param name="severityFilter">Severity filter for analysis.</param>
/// <remarks>
/// Note that this API doesn't execute any <see cref="CompilationStartAnalyzerAction"/> registered by the Initialize invocation.
/// Use <see cref="ExecuteCompilationStartActions(ImmutableArray{CompilationStartAnalyzerAction}, HostCompilationStartAnalysisScope)"/> API
/// to get execute these actions to get the per-compilation analyzer actions.
/// </remarks>
public void ExecuteInitializeMethod(DiagnosticAnalyzer analyzer, HostSessionStartAnalysisScope sessionScope)
public void ExecuteInitializeMethod(DiagnosticAnalyzer analyzer, HostSessionStartAnalysisScope sessionScope, SeverityFilter severityFilter)
{
var context = new AnalyzerAnalysisContext(analyzer, sessionScope);
var context = new AnalyzerAnalysisContext(analyzer, sessionScope, severityFilter);

// The Initialize method should be run asynchronously in case it is not well behaved, e.g. does not terminate.
ExecuteAndCatchIfThrows(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ static Task<HostSessionStartAnalysisScope> getSessionAnalysisScopeTaskSlow(Analy
return Task.Run(() =>
{
var sessionScope = new HostSessionStartAnalysisScope();
executor.ExecuteInitializeMethod(context._analyzer, sessionScope);
executor.ExecuteInitializeMethod(context._analyzer, sessionScope, executor.SeverityFilter);
return sessionScope;
}, executor.CancellationToken);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,12 @@ public virtual void ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags an
throw new NotImplementedException();
}

/// <summary>
/// Indicates the minimum reported diagnostic severity for this analysis context.
/// Analyzer diagnostics with severity lesser than this severity are not reported.
/// </summary>
public virtual DiagnosticSeverity MinimumReportedSeverity => throw new NotImplementedException();

/// <summary>
/// Attempts to compute or get the cached value provided by the given <paramref name="valueProvider"/> for the given <paramref name="text"/>.
/// Note that the pair {<paramref name="valueProvider"/>, <paramref name="text"/>} acts as the key.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ internal sealed class AnalyzerAnalysisContext : AnalysisContext
private readonly DiagnosticAnalyzer _analyzer;
private readonly HostSessionStartAnalysisScope _scope;

public AnalyzerAnalysisContext(DiagnosticAnalyzer analyzer, HostSessionStartAnalysisScope scope)
public AnalyzerAnalysisContext(DiagnosticAnalyzer analyzer, HostSessionStartAnalysisScope scope, SeverityFilter severityFilter)
{
_analyzer = analyzer;
_scope = scope;
MinimumReportedSeverity = severityFilter.GetMinimumUnfilteredSeverity();
}

public override void RegisterCompilationStartAction(Action<CompilationStartAnalysisContext> action)
Expand Down Expand Up @@ -114,6 +115,8 @@ public override void ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags m
{
_scope.ConfigureGeneratedCodeAnalysis(_analyzer, mode);
}

public override DiagnosticSeverity MinimumReportedSeverity { get; }
}

/// <summary>
Expand Down
1 change: 1 addition & 0 deletions src/Compilers/Core/Portable/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ override Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.GetGenerators(
override Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.GetGeneratorsForAllLanguages() -> System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.ISourceGenerator!>
override Microsoft.CodeAnalysis.Operations.OperationWalker<TArgument>.DefaultVisit(Microsoft.CodeAnalysis.IOperation! operation, TArgument argument) -> object?
override Microsoft.CodeAnalysis.Operations.OperationWalker<TArgument>.Visit(Microsoft.CodeAnalysis.IOperation? operation, TArgument argument) -> object?
virtual Microsoft.CodeAnalysis.Diagnostics.AnalysisContext.MinimumReportedSeverity.get -> Microsoft.CodeAnalysis.DiagnosticSeverity
virtual Microsoft.CodeAnalysis.Diagnostics.AnalyzerReference.GetGenerators(string! language) -> System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.ISourceGenerator!>
virtual Microsoft.CodeAnalysis.Diagnostics.AnalyzerReference.GetGeneratorsForAllLanguages() -> System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.ISourceGenerator!>
abstract Microsoft.CodeAnalysis.Compilation.GetUsedAssemblyReferences(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.MetadataReference!>
22 changes: 22 additions & 0 deletions src/Compilers/Test/Core/Diagnostics/CommonDiagnosticAnalyzers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2364,5 +2364,27 @@ public override void Initialize(AnalysisContext context)
});
}
}

[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
public class MinimumReportedSeverityAnalyzer : DiagnosticAnalyzer
{
public static readonly DiagnosticDescriptor _descriptor = new DiagnosticDescriptor(
"ID1",
"Title1",
"Message1",
"Category1",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);

public DiagnosticSeverity MinimumReportedSeverity { get; private set; }
public bool AnalyzerInvoked { get; private set; }

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(_descriptor);
public override void Initialize(AnalysisContext context)
{
MinimumReportedSeverity = context.MinimumReportedSeverity;
AnalyzerInvoked = true;
}
}
}
}

0 comments on commit d4ecad2

Please sign in to comment.