未验证 提交 031cd753 编写于 作者: M Manish Vasani 提交者: GitHub

Merge pull request #47819 from mavasani/CategoryExclusions

Support category-based exclusions in user option for unnecessary supp…
......@@ -99,10 +99,8 @@ protected sealed override void InitializeWorker(AnalysisContext context)
// Bail out if analyzer is suppressed on this file or project.
// NOTE: Normally, we would not require this check in the analyzer as the analyzer driver has this optimization.
// However, this is a special analyzer that is directly invoked by the analysis host (IDE), so we do this check here.
ReportDiagnostic severity;
if (
compilationWithAnalyzers.Compilation.Options.SyntaxTreeOptionsProvider != null &&
compilationWithAnalyzers.Compilation.Options.SyntaxTreeOptionsProvider.TryGetDiagnosticValue(tree, IDEDiagnosticIds.RemoveUnnecessarySuppressionDiagnosticId, cancellationToken, out severity) ||
if (compilationWithAnalyzers.Compilation.Options.SyntaxTreeOptionsProvider != null &&
compilationWithAnalyzers.Compilation.Options.SyntaxTreeOptionsProvider.TryGetDiagnosticValue(tree, IDEDiagnosticIds.RemoveUnnecessarySuppressionDiagnosticId, cancellationToken, out var severity) ||
compilationWithAnalyzers.Compilation.Options.SpecificDiagnosticOptions.TryGetValue(IDEDiagnosticIds.RemoveUnnecessarySuppressionDiagnosticId, out severity))
{
if (severity == ReportDiagnostic.Suppress)
......@@ -114,7 +112,7 @@ protected sealed override void InitializeWorker(AnalysisContext context)
// Bail out if analyzer has been turned off through options.
var option = compilationWithAnalyzers.AnalysisOptions.Options?.GetOption(
CodeStyleOptions2.RemoveUnnecessarySuppressionExclusions, tree, cancellationToken).Trim();
var (userExclusions, analyzerDisabled) = ParseUserExclusions(option);
var (userIdExclusions, userCategoryExclusions, analyzerDisabled) = ParseUserExclusions(option);
if (analyzerDisabled)
{
return;
......@@ -161,14 +159,14 @@ protected sealed override void InitializeWorker(AnalysisContext context)
using var _3 = PooledDictionary<SyntaxTrivia, bool>.GetInstance(out var pragmasToIsUsedMap);
using var _4 = PooledHashSet<string>.GetInstance(out var compilerDiagnosticIds);
var hasPragmaInAnalysisSpan = ProcessPragmaDirectives(root, span, idToPragmasMap,
pragmasToIsUsedMap, sortedPragmasWithIds, compilerDiagnosticIds, userExclusions);
pragmasToIsUsedMap, sortedPragmasWithIds, compilerDiagnosticIds, userIdExclusions);
cancellationToken.ThrowIfCancellationRequested();
using var _5 = PooledDictionary<string, List<SyntaxNode>>.GetInstance(out var idToSuppressMessageAttributesMap);
using var _6 = PooledDictionary<SyntaxNode, bool>.GetInstance(out var suppressMessageAttributesToIsUsedMap);
var hasAttributeInAnalysisSpan = await ProcessSuppressMessageAttributesAsync(root, semanticModel, span,
idToSuppressMessageAttributesMap, suppressMessageAttributesToIsUsedMap, userExclusions, cancellationToken).ConfigureAwait(false);
idToSuppressMessageAttributesMap, suppressMessageAttributesToIsUsedMap, userIdExclusions, userCategoryExclusions, cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
......@@ -345,35 +343,47 @@ private static bool IsSupportedAnalyzerDiagnosticId(string id)
}
}
private static (ImmutableArray<string> userExclusions, bool analyzerDisabled) ParseUserExclusions(string? userExclusions)
private static (ImmutableArray<string> userIdExclusions, ImmutableArray<string> userCategoryExclusions, bool analyzerDisabled) ParseUserExclusions(string? userExclusions)
{
// Option value must be a comma separate list of diagnostic IDs to exclude from unnecessary pragma analysis.
// Option value must be a comma separate list of diagnostic IDs or categories (with a "category:" prefix) to exclude from unnecessary pragma analysis.
// We also allow a special keyword "all" to disable the analyzer completely.
switch (userExclusions)
{
case "":
case null:
return (userExclusions: ImmutableArray<string>.Empty, analyzerDisabled: false);
return (userIdExclusions: ImmutableArray<string>.Empty, userCategoryExclusions: ImmutableArray<string>.Empty, analyzerDisabled: false);
case "all":
return (userExclusions: ImmutableArray<string>.Empty, analyzerDisabled: true);
return (userIdExclusions: ImmutableArray<string>.Empty, userCategoryExclusions: ImmutableArray<string>.Empty, analyzerDisabled: true);
default:
// Default string representation for unconfigured option value should be treated as no exclusions.
if (userExclusions == CodeStyleOptions2.RemoveUnnecessarySuppressionExclusions.DefaultValue)
return (userExclusions: ImmutableArray<string>.Empty, analyzerDisabled: false);
return (userIdExclusions: ImmutableArray<string>.Empty, userCategoryExclusions: ImmutableArray<string>.Empty, analyzerDisabled: false);
break;
}
using var _ = ArrayBuilder<string>.GetInstance(out var builder);
// We allow excluding category of diagnostics with a category prefix, for example "category: ExcludedCategory".
const string categoryPrefix = "category:";
using var _1 = ArrayBuilder<string>.GetInstance(out var idBuilder);
using var _2 = ArrayBuilder<string>.GetInstance(out var categoryBuilder);
foreach (var part in userExclusions.Split(','))
{
var trimmedPart = part.Trim();
builder.Add(trimmedPart);
if (trimmedPart.StartsWith(categoryPrefix, StringComparison.OrdinalIgnoreCase))
{
trimmedPart = trimmedPart[categoryPrefix.Length..].Trim();
categoryBuilder.Add(trimmedPart);
}
else
{
idBuilder.Add(trimmedPart);
}
}
return (userExclusions: builder.ToImmutable(), analyzerDisabled: false);
return (userIdExclusions: idBuilder.ToImmutable(), userCategoryExclusions: categoryBuilder.ToImmutable(), analyzerDisabled: false);
}
private static async Task<(ImmutableArray<Diagnostic> reportedDiagnostics, ImmutableArray<string> unhandledIds)> GetReportedDiagnosticsForIdsAsync(
......@@ -695,7 +705,8 @@ private static (ImmutableArray<string> userExclusions, bool analyzerDisabled) Pa
TextSpan? span,
PooledDictionary<string, List<SyntaxNode>> idToSuppressMessageAttributesMap,
PooledDictionary<SyntaxNode, bool> suppressMessageAttributesToIsUsedMap,
ImmutableArray<string> userExclusions,
ImmutableArray<string> userIdExclusions,
ImmutableArray<string> userCategoryExclusions,
CancellationToken cancellationToken)
{
var suppressMessageAttributeType = semanticModel.Compilation.SuppressMessageAttributeType();
......@@ -742,11 +753,12 @@ private static (ImmutableArray<string> userExclusions, bool analyzerDisabled) Pa
foreach (var attribute in symbol.GetAttributes())
{
if (attribute.ApplicationSyntaxReference != null &&
TryGetSuppressedDiagnosticId(attribute, suppressMessageAttributeType, out var id))
TryGetSuppressedDiagnosticId(attribute, suppressMessageAttributeType, out var id, out var category))
{
// Ignore unsupported IDs and those excluded through user option.
if (!IsSupportedAnalyzerDiagnosticId(id) ||
userExclusions.Contains(id, StringComparer.OrdinalIgnoreCase))
userIdExclusions.Contains(id, StringComparer.OrdinalIgnoreCase) ||
category?.Length > 0 && userCategoryExclusions.Contains(category, StringComparer.OrdinalIgnoreCase))
{
continue;
}
......@@ -779,21 +791,38 @@ private static (ImmutableArray<string> userExclusions, bool analyzerDisabled) Pa
private static bool TryGetSuppressedDiagnosticId(
AttributeData attribute,
INamedTypeSymbol suppressMessageAttributeType,
[NotNullWhen(returnValue: true)] out string? id)
[NotNullWhen(returnValue: true)] out string? id,
out string? category)
{
category = null;
if (suppressMessageAttributeType.Equals(attribute.AttributeClass) &&
attribute.AttributeConstructor?.Parameters.Length >= 2 &&
attribute.AttributeConstructor.Parameters[1].Name == "checkId" &&
attribute.AttributeConstructor.Parameters[1].Type.SpecialType == SpecialType.System_String &&
attribute.ConstructorArguments.Length >= 2 &&
attribute.ConstructorArguments[1] is { } typedConstant &&
typedConstant.Kind == TypedConstantKind.Primitive &&
typedConstant.Value is string checkId)
attribute.ConstructorArguments[1] is
{
Kind: TypedConstantKind.Primitive,
Value: string checkId
})
{
// CheckId represents diagnostic ID, followed by an option ':' and name.
// For example, "CA1801:ReviewUnusedParameters"
var index = checkId.IndexOf(':');
id = index > 0 ? checkId.Substring(0, index) : checkId;
if (attribute.AttributeConstructor.Parameters[0].Name == "category" &&
attribute.AttributeConstructor.Parameters[0].Type.SpecialType == SpecialType.System_String &&
attribute.ConstructorArguments[0] is
{
Kind: TypedConstantKind.Primitive,
Value: string categoryArg
})
{
category = categoryArg;
}
return id.Length > 0;
}
......
......@@ -412,6 +412,33 @@ void M()
|]", new TestParameters(options: options));
}
[Fact, WorkItem(47288, "https://github.com/dotnet/roslyn/issues/47288")]
public async Task TestDoNotRemoveExcludedDiagnosticCategorySuppression()
{
var options = new OptionsCollection(LanguageNames.CSharp)
{
{ CodeStyleOptions2.RemoveUnnecessarySuppressionExclusions, "category: ExcludedCategory" }
};
await TestMissingInRegularAndScriptAsync(
$@"
[|
class Class
{{
[System.Diagnostics.CodeAnalysis.SuppressMessage(""ExcludedCategory"", ""{VariableDeclaredButNotUsedDiagnosticId}"")]
[System.Diagnostics.CodeAnalysis.SuppressMessage(""ExcludedCategory"", ""{VariableAssignedButNotUsedDiagnosticId}"")]
void M()
{{
int y;
y = 1;
int z = 1;
z++;
}}
}}
|]", new TestParameters(options: options));
}
[Fact]
public async Task TestDoNotRemoveDiagnosticSuppression_Attribute_OnPartialDeclarations()
{
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册