diff --git a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems
index 8d2ff6b7621d2..eb6a67863e674 100644
--- a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems
+++ b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems
@@ -18,6 +18,7 @@
+
diff --git a/src/Analyzers/CSharp/Analyzers/RemoveUnnecessarySuppressions/CSharpRemoveUnnecessarySuppressionsDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/RemoveUnnecessarySuppressions/CSharpRemoveUnnecessarySuppressionsDiagnosticAnalyzer.cs
new file mode 100644
index 0000000000000..c883b8c69c96c
--- /dev/null
+++ b/src/Analyzers/CSharp/Analyzers/RemoveUnnecessarySuppressions/CSharpRemoveUnnecessarySuppressionsDiagnosticAnalyzer.cs
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.RemoveUnnecessarySuppressions;
+
+#nullable enable
+
+namespace Microsoft.CodeAnalysis.CSharp.RemoveUnnecessarySuppressions
+{
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ internal sealed class CSharpRemoveUnnecessarySuppressionsDiagnosticAnalyzer
+ : AbstractRemoveUnnecessarySuppressionsDiagnosticAnalyzer
+ {
+ protected override void RegisterAttributeSyntaxAction(CompilationStartAnalysisContext context, CompilationAnalyzer compilationAnalyzer)
+ {
+ context.RegisterSyntaxNodeAction(context =>
+ {
+ var attributeList = (AttributeListSyntax)context.Node;
+ switch (attributeList.Target?.Identifier.Kind())
+ {
+ case SyntaxKind.AssemblyKeyword:
+ case SyntaxKind.ModuleKeyword:
+ foreach (var attribute in attributeList.Attributes)
+ {
+ compilationAnalyzer.AnalyzeAssemblyOrModuleAttribute(attribute, context.SemanticModel, context.ReportDiagnostic, context.CancellationToken);
+ }
+
+ break;
+ }
+ }, SyntaxKind.AttributeList);
+ }
+ }
+}
diff --git a/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems b/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems
index c4b14d815d077..378c593c2b82e 100644
--- a/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems
+++ b/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems
@@ -25,6 +25,7 @@
+
diff --git a/src/Analyzers/CSharp/Tests/RemoveUnnecessarySuppressions/RemoveUnnecessarySuppressionsTests.cs b/src/Analyzers/CSharp/Tests/RemoveUnnecessarySuppressions/RemoveUnnecessarySuppressionsTests.cs
new file mode 100644
index 0000000000000..42054bd9b541c
--- /dev/null
+++ b/src/Analyzers/CSharp/Tests/RemoveUnnecessarySuppressions/RemoveUnnecessarySuppressionsTests.cs
@@ -0,0 +1,186 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#nullable enable
+
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Test.Utilities;
+using Roslyn.Test.Utilities;
+using Xunit;
+using VerifyCS = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.CSharpCodeFixVerifier<
+ Microsoft.CodeAnalysis.CSharp.RemoveUnnecessarySuppressions.CSharpRemoveUnnecessarySuppressionsDiagnosticAnalyzer,
+ Microsoft.CodeAnalysis.RemoveUnnecessarySuppressions.RemoveUnnecessarySuppressionsCodeFixProvider>;
+
+namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.RemoveUnnecessarySuppressions
+{
+ [Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessarySuppressions)]
+ [WorkItem(44176, "https://github.com/dotnet/roslyn/issues/44176")]
+ public class RemoveUnnecessarySuppressionsTests
+ {
+ [Fact]
+ public void TestStandardProperties()
+ => VerifyCS.VerifyStandardProperties();
+
+ [Theory]
+ // Field
+ [InlineData(@"Scope = ""member""", @"Target = ""~F:N.C.F""", "assembly")]
+ // Property
+ [InlineData(@"Scope = ""member""", @"Target = ""~P:N.C.P""", "assembly")]
+ // Method
+ [InlineData(@"Scope = ""member""", @"Target = ""~M:N.C.M()""", "assembly")]
+ // Type
+ [InlineData(@"Scope = ""member""", @"Target = ""~T:N.C""", "assembly")]
+ // Namespace
+ [InlineData(@"Scope = ""namespace""", @"Target = ""~N:N""", "assembly")]
+ // NamespaceAndDescendants
+ [InlineData(@"Scope = ""namespaceanddescendants""", @"Target = ""~N:N""", "assembly")]
+ // Module - no scope, no target
+ [InlineData(null, null, "assembly")]
+ // Module - no target
+ [InlineData(@"Scope = ""module""", null, "assembly")]
+ // Module - null target
+ [InlineData(@"Scope = ""module""", @"Target = null", "assembly")]
+ // Resource - not handled
+ [InlineData(@"Scope = ""resource""", @"Target = """"", "assembly")]
+ // 'module' attribute target
+ [InlineData(@"Scope = ""member""", @"Target = ""~M:N.C.M()""", "module")]
+ // Member with non-matching scope (seems to be respected by suppression decoder)
+ [InlineData(@"Scope = ""type""", @"Target = ""~M:N.C.M()""", "assembly")]
+ [InlineData(@"Scope = ""namespace""", @"Target = ""~F:N.C.F""", "assembly")]
+ // Case insensitive scope
+ [InlineData(@"Scope = ""Member""", @"Target = ""~F:N.C.F""", "assembly")]
+ [InlineData(@"Scope = ""MEMBER""", @"Target = ""~F:N.C.F""", "assembly")]
+ public async Task ValidSuppressions(string? scope, string? target, string attributeTarget)
+ {
+ var scopeString = scope != null ? $@", {scope}" : string.Empty;
+ var targetString = target != null ? $@", {target}" : string.Empty;
+
+ var input = $@"
+[{attributeTarget}: System.Diagnostics.CodeAnalysis.SuppressMessage(""Category"", ""Id: Title"", Justification = ""Pending""{scopeString}{targetString})]
+
+namespace N
+{{
+ class C
+ {{
+ public int F;
+ public int P {{ get; }}
+ public void M() {{ }}
+ }}
+}}";
+ await VerifyCS.VerifyCodeFixAsync(input, input);
+ }
+
+ [Theory]
+ // Field - no matching symbol
+ [InlineData(@"Scope = ""member""", @"Target = ""~F:N.C.F2""", "assembly")]
+ // Field - no matching symbol (case insensitive)
+ [InlineData(@"Scope = ""Member""", @"Target = ""~F:N.C.F2""", "assembly")]
+ [InlineData(@"Scope = ""MEMBER""", @"Target = ""~F:N.C.F2""", "assembly")]
+ // Property - invalid scope
+ [InlineData(@"Scope = ""invalid""", @"Target = ""~P:N.C.P""", "assembly")]
+ // Method - wrong signature
+ [InlineData(@"Scope = ""member""", @"Target = ""~M:N.C.M(System.Int32)""", "assembly")]
+ // Method - module scope
+ [InlineData(@"Scope = ""module""", @"Target = ""~M:N.C.M()""", "assembly")]
+ // Method - null scope
+ [InlineData(@"Scope = null", @"Target = ""~M:N.C.M()""", "assembly")]
+ // Method - no scope
+ [InlineData(null, @"Target = ""~M:N.C.M()""", "assembly")]
+ // Member scope - null target
+ [InlineData(@"Scope = ""member""", @"Target = null", "assembly")]
+ // Member scope - no target
+ [InlineData(@"Scope = ""member""", null, "assembly")]
+ // Type - no matching namespace
+ [InlineData(@"Scope = ""type""", @"Target = ""~T:N2.C""", "assembly")]
+ // Namespace - extra namespace qualification
+ [InlineData(@"Scope = ""namespace""", @"Target = ""~N:N.N2""", "assembly")]
+ // NamespaceAndDescendants - empty target
+ [InlineData(@"Scope = ""namespaceanddescendants""", @"Target = """"", "assembly")]
+ // Module - no scope, empty target
+ [InlineData(null, @"Target = """"", "assembly")]
+ // Module - no scope, non-empty target
+ [InlineData(null, @"Target = ""~T:N.C""", "assembly")]
+ // Module scope, empty target
+ [InlineData(@"Scope = ""module""", @"Target = """"", "assembly")]
+ // Module no scope, non-empty target
+ [InlineData(@"Scope = ""module""", @"Target = ""~T:N.C""", "assembly")]
+ public async Task InvalidSuppressions(string? scope, string? target, string attributeTarget)
+ {
+ var scopeString = scope != null ? $@", {scope}" : string.Empty;
+ var targetString = target != null ? $@", {target}" : string.Empty;
+
+ var input = $@"
+[{attributeTarget}: [|System.Diagnostics.CodeAnalysis.SuppressMessage(""Category"", ""Id: Title"", Justification = ""Pending""{scopeString}{targetString})|]]
+
+namespace N
+{{
+ class C
+ {{
+ public int F;
+ public int P {{ get; }}
+ public void M() {{ }}
+ }}
+}}";
+
+ var fixedCode = $@"
+
+namespace N
+{{
+ class C
+ {{
+ public int F;
+ public int P {{ get; }}
+ public void M() {{ }}
+ }}
+}}";
+ await VerifyCS.VerifyCodeFixAsync(input, fixedCode);
+ }
+
+ [Fact]
+ public async Task ValidAndInvalidSuppressions()
+ {
+ var attributePrefix = @"System.Diagnostics.CodeAnalysis.SuppressMessage(""Category"", ""Id: Title"", Justification = ""Pending""";
+ var validSuppression = $@"{attributePrefix}, Scope = ""member"", Target = ""~T:C"")";
+ var invalidSuppression = $@"[|{attributePrefix}, Scope = ""member"", Target = """")|]";
+
+ var input = $@"
+[assembly: {validSuppression}]
+[assembly: {invalidSuppression}]
+[assembly: {validSuppression}, {validSuppression}]
+[assembly: {invalidSuppression}, {invalidSuppression}]
+[assembly: {validSuppression}, {invalidSuppression}]
+[assembly: {invalidSuppression}, {validSuppression}]
+[assembly: {invalidSuppression}, {validSuppression}, {invalidSuppression}, {validSuppression}]
+
+class C {{ }}
+";
+
+ var fixedCode = $@"
+[assembly: {validSuppression}]
+[assembly: {validSuppression}, {validSuppression}]
+[assembly: {validSuppression}]
+[assembly: {validSuppression}]
+[assembly: {validSuppression}, {validSuppression}]
+
+class C {{ }}
+";
+ await VerifyCS.VerifyCodeFixAsync(input, fixedCode);
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData(@", Scope = ""member"", Target = ""~M:C.M()""")]
+ [InlineData(@", Scope = ""invalid"", Target = ""invalid""")]
+ public async Task LocalSuppressions(string scopeAndTarget)
+ {
+ var input = $@"
+[System.Diagnostics.CodeAnalysis.SuppressMessage(""Category"", ""Id: Title"", Justification = ""Pending""{scopeAndTarget})]
+class C
+{{
+ public void M() {{ }}
+}}";
+ await VerifyCS.VerifyCodeFixAsync(input, input);
+ }
+ }
+}
diff --git a/src/Analyzers/Core/Analyzers/Analyzers.projitems b/src/Analyzers/Core/Analyzers/Analyzers.projitems
index 0a391a8b64fc6..a912dc028db10 100644
--- a/src/Analyzers/Core/Analyzers/Analyzers.projitems
+++ b/src/Analyzers/Core/Analyzers/Analyzers.projitems
@@ -30,6 +30,10 @@
+
+
+
+
diff --git a/src/Analyzers/Core/Analyzers/AnalyzersResources.resx b/src/Analyzers/Core/Analyzers/AnalyzersResources.resx
index 6030852ba2878..bf31c2eeb98f0 100644
--- a/src/Analyzers/Core/Analyzers/AnalyzersResources.resx
+++ b/src/Analyzers/Core/Analyzers/AnalyzersResources.resx
@@ -307,4 +307,13 @@
Conditional expression can be simplified
+
+ Invalid global 'SuppressMessageAttribute'
+
+
+ Invalid scope for 'SuppressMessageAttribute'
+
+
+ Invalid or missing target for 'SuppressMessageAttribute'
+
\ No newline at end of file
diff --git a/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs b/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs
index 44e607aa2046c..87a3f3edabded 100644
--- a/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs
+++ b/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs
@@ -130,6 +130,8 @@ internal static class IDEDiagnosticIds
public const string SimplifyConditionalExpressionDiagnosticId = "IDE0075";
+ public const string InvalidSuppressMessageAttributeDiagnosticId = "IDE0076";
+
// Analyzer error Ids
public const string AnalyzerChangedId = "IDE1001";
public const string AnalyzerDependencyConflictId = "IDE1002";
diff --git a/src/Analyzers/Core/Analyzers/RemoveUnnecessarySuppressions/AbstractRemoveUnnecessarySuppressionsDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/RemoveUnnecessarySuppressions/AbstractRemoveUnnecessarySuppressionsDiagnosticAnalyzer.cs
new file mode 100644
index 0000000000000..b8d7e3f15f832
--- /dev/null
+++ b/src/Analyzers/Core/Analyzers/RemoveUnnecessarySuppressions/AbstractRemoveUnnecessarySuppressionsDiagnosticAnalyzer.cs
@@ -0,0 +1,87 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#nullable enable
+
+using System;
+using System.Collections.Immutable;
+using System.Threading;
+using Microsoft.CodeAnalysis.CodeQuality;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Shared.Extensions;
+
+namespace Microsoft.CodeAnalysis.RemoveUnnecessarySuppressions
+{
+ internal abstract class AbstractRemoveUnnecessarySuppressionsDiagnosticAnalyzer
+ : AbstractCodeQualityDiagnosticAnalyzer
+ {
+ private static readonly LocalizableResourceString s_localizableTitle = new LocalizableResourceString(
+ nameof(AnalyzersResources.Invalid_global_SuppressMessageAttribute), AnalyzersResources.ResourceManager, typeof(AnalyzersResources));
+ private static readonly LocalizableResourceString s_localizableInvalidScopeMessage = new LocalizableResourceString(
+ nameof(AnalyzersResources.Invalid_scope_for_SuppressMessageAttribute), AnalyzersResources.ResourceManager, typeof(AnalyzersResources));
+ private static readonly LocalizableResourceString s_localizableInvalidOrMissingTargetMessage = new LocalizableResourceString(
+ nameof(AnalyzersResources.Invalid_or_missing_target_for_SuppressMessageAttribute), AnalyzersResources.ResourceManager, typeof(AnalyzersResources));
+
+ private static readonly DiagnosticDescriptor s_invalidScopeDescriptor = CreateDescriptor(
+ IDEDiagnosticIds.InvalidSuppressMessageAttributeDiagnosticId, s_localizableTitle, s_localizableInvalidScopeMessage, isUnnecessary: true);
+ private static readonly DiagnosticDescriptor s_invalidOrMissingTargetDescriptor = CreateDescriptor(
+ IDEDiagnosticIds.InvalidSuppressMessageAttributeDiagnosticId, s_localizableTitle, s_localizableInvalidOrMissingTargetMessage, isUnnecessary: true);
+
+ public AbstractRemoveUnnecessarySuppressionsDiagnosticAnalyzer()
+ : base(ImmutableArray.Create(s_invalidScopeDescriptor, s_invalidOrMissingTargetDescriptor), GeneratedCodeAnalysisFlags.None)
+ {
+ }
+
+ protected abstract void RegisterAttributeSyntaxAction(CompilationStartAnalysisContext context, CompilationAnalyzer compilationAnalyzer);
+ public sealed override DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SemanticDocumentAnalysis;
+
+ protected sealed override void InitializeWorker(AnalysisContext context)
+ {
+ context.RegisterCompilationStartAction(context =>
+ {
+ var suppressMessageAttributeType = context.Compilation.SuppressMessageAttributeType();
+ if (suppressMessageAttributeType == null)
+ {
+ return;
+ }
+
+ RegisterAttributeSyntaxAction(context, new CompilationAnalyzer(context.Compilation, suppressMessageAttributeType));
+ });
+ }
+
+ protected sealed class CompilationAnalyzer
+ {
+ private readonly SuppressMessageAttributeState _state;
+
+ public CompilationAnalyzer(Compilation compilation, INamedTypeSymbol suppressMessageAttributeType)
+ {
+ _state = new SuppressMessageAttributeState(compilation, suppressMessageAttributeType);
+ }
+
+ public void AnalyzeAssemblyOrModuleAttribute(SyntaxNode attributeSyntax, SemanticModel model, Action reportDiagnostic, CancellationToken cancellationToken)
+ {
+ if (!_state.IsSuppressMessageAttributeWithNamedArguments(attributeSyntax, model, cancellationToken, out var namedAttributeArguments))
+ {
+ return;
+ }
+
+ DiagnosticDescriptor rule;
+ if (_state.HasInvalidScope(namedAttributeArguments, out var targetScope))
+ {
+ rule = s_invalidScopeDescriptor;
+ }
+ else if (_state.HasInvalidOrMissingTarget(namedAttributeArguments, targetScope))
+ {
+ rule = s_invalidOrMissingTargetDescriptor;
+ }
+ else
+ {
+ return;
+ }
+
+ reportDiagnostic(Diagnostic.Create(rule, attributeSyntax.GetLocation()));
+ }
+ }
+ }
+}
diff --git a/src/Analyzers/Core/Analyzers/RemoveUnnecessarySuppressions/SuppressMessageAttributeState.cs b/src/Analyzers/Core/Analyzers/RemoveUnnecessarySuppressions/SuppressMessageAttributeState.cs
new file mode 100644
index 0000000000000..991319011c48e
--- /dev/null
+++ b/src/Analyzers/Core/Analyzers/RemoveUnnecessarySuppressions/SuppressMessageAttributeState.cs
@@ -0,0 +1,152 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Threading;
+using Microsoft.CodeAnalysis.Operations;
+using Microsoft.CodeAnalysis.PooledObjects;
+using Roslyn.Utilities;
+
+namespace Microsoft.CodeAnalysis.Diagnostics
+{
+ internal partial class SuppressMessageAttributeState
+ {
+ internal const string SuppressMessageScope = "Scope";
+ internal const string SuppressMessageTarget = "Target";
+
+ private static readonly ImmutableDictionary s_targetScopesMap = CreateTargetScopesMap();
+ private static readonly ObjectPool> s_listPool = new ObjectPool>(() => new List());
+
+ private readonly Compilation _compilation;
+ private readonly INamedTypeSymbol _suppressMessageAttributeType;
+
+ public SuppressMessageAttributeState(Compilation compilation, INamedTypeSymbol suppressMessageAttributeType)
+ {
+ _compilation = compilation;
+ _suppressMessageAttributeType = suppressMessageAttributeType;
+ }
+
+ private static ImmutableDictionary CreateTargetScopesMap()
+ {
+ var builder = ImmutableDictionary.CreateBuilder(StringComparer.OrdinalIgnoreCase);
+
+#pragma warning disable CS8605 // Unboxing a possibly null value.
+ foreach (TargetScope targetScope in Enum.GetValues(typeof(TargetScope)))
+#pragma warning restore CS8605 // Unboxing a possibly null value.
+ {
+ if (targetScope == TargetScope.None)
+ {
+ continue;
+ }
+
+ builder.Add(targetScope.ToString(), targetScope);
+ }
+
+ return builder.ToImmutable();
+ }
+
+ public bool IsSuppressMessageAttributeWithNamedArguments(
+ SyntaxNode attributeSyntax,
+ SemanticModel model,
+ CancellationToken cancellationToken,
+ out ImmutableArray<(string name, IOperation value)> namedAttributeArguments)
+ {
+ var attribute = model.GetOperation(attributeSyntax, cancellationToken);
+ if (attribute == null)
+ {
+ namedAttributeArguments = ImmutableArray<(string name, IOperation value)>.Empty;
+ return false;
+ }
+
+ // Workaround for https://github.com/dotnet/roslyn/issues/18198
+ // Use 'IOperation.Children' to get named attribute arguments.
+ // Each named attribute argument is represented as an 'ISimpleAssignmentOperation'
+ // with a constant value assignment to an 'IPropertyReferenceOperation' in the operation tree.
+ using var _ = ArrayBuilder<(string name, IOperation value)>.GetInstance(out var builder);
+ foreach (var childOperation in attribute.Children)
+ {
+ if (childOperation is ISimpleAssignmentOperation simpleAssignment &&
+ simpleAssignment.Target is IPropertyReferenceOperation propertyReference &&
+ _suppressMessageAttributeType.Equals(propertyReference.Property.ContainingType))
+ {
+ builder.Add((propertyReference.Property.Name, simpleAssignment.Value));
+ }
+ }
+
+ namedAttributeArguments = builder.ToImmutable();
+ return namedAttributeArguments.Length > 0;
+ }
+
+ public bool HasInvalidScope(ImmutableArray<(string name, IOperation value)> namedAttributeArguments, out TargetScope targetScope)
+ {
+ if (!TryGetNamedArgument(namedAttributeArguments, SuppressMessageScope, out var scopeString) ||
+ RoslynString.IsNullOrEmpty(scopeString))
+ {
+ // Missing/Null/Empty scope values are treated equivalent to a compilation wide suppression.
+ targetScope = TargetScope.Module;
+ }
+ else if (!s_targetScopesMap.TryGetValue(scopeString, out targetScope))
+ {
+ targetScope = TargetScope.None;
+ return true;
+ }
+
+ return false;
+ }
+
+ public bool HasInvalidOrMissingTarget(ImmutableArray<(string name, IOperation value)> namedAttributeArguments, TargetScope targetScope)
+ {
+ if (targetScope == TargetScope.Resource)
+ {
+ // Legacy scope which we do not handle.
+ return false;
+ }
+
+ if (!TryGetNamedArgument(namedAttributeArguments, SuppressMessageTarget, out var targetSymbolString))
+ {
+ targetSymbolString = null;
+ }
+
+ if (targetScope == TargetScope.Module)
+ {
+ // Compilation wide suppression with a non-null target is considered invalid.
+ return targetSymbolString != null;
+ }
+
+ var resolvedSymbols = s_listPool.Allocate();
+ try
+ {
+ var resolver = new TargetSymbolResolver(_compilation, targetScope, targetSymbolString);
+ resolvedSymbols.Clear();
+ resolver.Resolve(resolvedSymbols);
+ return resolvedSymbols.Count == 0;
+ }
+ finally
+ {
+ s_listPool.Free(resolvedSymbols);
+ }
+ }
+
+ private bool TryGetNamedArgument(ImmutableArray<(string name, IOperation value)> namedAttributeArguments, string argumentName, out string? argumentValue)
+ {
+ foreach (var (name, value) in namedAttributeArguments)
+ {
+ if (name == argumentName &&
+ value.ConstantValue.HasValue &&
+ value.ConstantValue.Value is string stringValue)
+ {
+ argumentValue = stringValue;
+ return true;
+ }
+ }
+
+ argumentValue = null;
+ return false;
+ }
+ }
+}
diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.cs.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.cs.xlf
index fba882240e19e..ddb52544bb1e9 100644
--- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.cs.xlf
+++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.cs.xlf
@@ -97,6 +97,21 @@
Neplatný formátovací řetězec
+
+ Invalid global 'SuppressMessageAttribute'
+ Invalid global 'SuppressMessageAttribute'
+
+
+
+ Invalid or missing target for 'SuppressMessageAttribute'
+ Invalid or missing target for 'SuppressMessageAttribute'
+
+
+
+ Invalid scope for 'SuppressMessageAttribute'
+ Invalid scope for 'SuppressMessageAttribute'
+
+
Make field readonly
Nastavit pole jen pro čtení
diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.de.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.de.xlf
index 5b9ebb6083d28..5f110fdcbeba5 100644
--- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.de.xlf
+++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.de.xlf
@@ -97,6 +97,21 @@
Ungültige Formatzeichenfolge
+
+ Invalid global 'SuppressMessageAttribute'
+ Invalid global 'SuppressMessageAttribute'
+
+
+
+ Invalid or missing target for 'SuppressMessageAttribute'
+ Invalid or missing target for 'SuppressMessageAttribute'
+
+
+
+ Invalid scope for 'SuppressMessageAttribute'
+ Invalid scope for 'SuppressMessageAttribute'
+
+
Make field readonly
Schreibschutz für Feld festlegen
diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.es.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.es.xlf
index c6c454ff79ed1..b0fa1d99f1127 100644
--- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.es.xlf
+++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.es.xlf
@@ -97,6 +97,21 @@
Cadena de formato no válida
+
+ Invalid global 'SuppressMessageAttribute'
+ Invalid global 'SuppressMessageAttribute'
+
+
+
+ Invalid or missing target for 'SuppressMessageAttribute'
+ Invalid or missing target for 'SuppressMessageAttribute'
+
+
+
+ Invalid scope for 'SuppressMessageAttribute'
+ Invalid scope for 'SuppressMessageAttribute'
+
+
Make field readonly
Hacer el archivo de solo lectura
diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.fr.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.fr.xlf
index 08c7dd8d9c932..b3083a37453fb 100644
--- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.fr.xlf
+++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.fr.xlf
@@ -97,6 +97,21 @@
Chaîne de format non valide
+
+ Invalid global 'SuppressMessageAttribute'
+ Invalid global 'SuppressMessageAttribute'
+
+
+
+ Invalid or missing target for 'SuppressMessageAttribute'
+ Invalid or missing target for 'SuppressMessageAttribute'
+
+
+
+ Invalid scope for 'SuppressMessageAttribute'
+ Invalid scope for 'SuppressMessageAttribute'
+
+
Make field readonly
Rendre le champ readonly
diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.it.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.it.xlf
index 02da6ab387605..44270f01ca6a9 100644
--- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.it.xlf
+++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.it.xlf
@@ -97,6 +97,21 @@
Stringa di formato non valida
+
+ Invalid global 'SuppressMessageAttribute'
+ Invalid global 'SuppressMessageAttribute'
+
+
+
+ Invalid or missing target for 'SuppressMessageAttribute'
+ Invalid or missing target for 'SuppressMessageAttribute'
+
+
+
+ Invalid scope for 'SuppressMessageAttribute'
+ Invalid scope for 'SuppressMessageAttribute'
+
+
Make field readonly
Imposta il campo come di sola lettura
diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ja.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ja.xlf
index ddad97b99e00d..d19aaf2ad71a1 100644
--- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ja.xlf
+++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ja.xlf
@@ -97,6 +97,21 @@
無効な書式設定文字列
+
+ Invalid global 'SuppressMessageAttribute'
+ Invalid global 'SuppressMessageAttribute'
+
+
+
+ Invalid or missing target for 'SuppressMessageAttribute'
+ Invalid or missing target for 'SuppressMessageAttribute'
+
+
+
+ Invalid scope for 'SuppressMessageAttribute'
+ Invalid scope for 'SuppressMessageAttribute'
+
+
Make field readonly
フィールドを読み取り専用にします
diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ko.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ko.xlf
index c3069acc385aa..55ad31a5de636 100644
--- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ko.xlf
+++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ko.xlf
@@ -97,6 +97,21 @@
잘못된 형식 문자열
+
+ Invalid global 'SuppressMessageAttribute'
+ Invalid global 'SuppressMessageAttribute'
+
+
+
+ Invalid or missing target for 'SuppressMessageAttribute'
+ Invalid or missing target for 'SuppressMessageAttribute'
+
+
+
+ Invalid scope for 'SuppressMessageAttribute'
+ Invalid scope for 'SuppressMessageAttribute'
+
+
Make field readonly
필드를 읽기 전용으로 만들기
diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pl.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pl.xlf
index 82ad65b1e535b..1005362beab5b 100644
--- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pl.xlf
+++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pl.xlf
@@ -97,6 +97,21 @@
Nieprawidłowy ciąg formatu
+
+ Invalid global 'SuppressMessageAttribute'
+ Invalid global 'SuppressMessageAttribute'
+
+
+
+ Invalid or missing target for 'SuppressMessageAttribute'
+ Invalid or missing target for 'SuppressMessageAttribute'
+
+
+
+ Invalid scope for 'SuppressMessageAttribute'
+ Invalid scope for 'SuppressMessageAttribute'
+
+
Make field readonly
Ustaw pole jako tylko do odczytu
diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pt-BR.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pt-BR.xlf
index 6c6b3b47c0173..0565b05376f56 100644
--- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pt-BR.xlf
+++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pt-BR.xlf
@@ -97,6 +97,21 @@
Cadeia de formato inválida
+
+ Invalid global 'SuppressMessageAttribute'
+ Invalid global 'SuppressMessageAttribute'
+
+
+
+ Invalid or missing target for 'SuppressMessageAttribute'
+ Invalid or missing target for 'SuppressMessageAttribute'
+
+
+
+ Invalid scope for 'SuppressMessageAttribute'
+ Invalid scope for 'SuppressMessageAttribute'
+
+
Make field readonly
Tornar campo somente leitura
diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ru.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ru.xlf
index ac1cd8a0eb568..192b60c36e57e 100644
--- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ru.xlf
+++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ru.xlf
@@ -97,6 +97,21 @@
Недопустимая строка формата
+
+ Invalid global 'SuppressMessageAttribute'
+ Invalid global 'SuppressMessageAttribute'
+
+
+
+ Invalid or missing target for 'SuppressMessageAttribute'
+ Invalid or missing target for 'SuppressMessageAttribute'
+
+
+
+ Invalid scope for 'SuppressMessageAttribute'
+ Invalid scope for 'SuppressMessageAttribute'
+
+
Make field readonly
Сделать поле доступным только для чтения
diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.tr.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.tr.xlf
index 626caa6b55256..7c883b3c49845 100644
--- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.tr.xlf
+++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.tr.xlf
@@ -97,6 +97,21 @@
Geçersiz biçim dizesi
+
+ Invalid global 'SuppressMessageAttribute'
+ Invalid global 'SuppressMessageAttribute'
+
+
+
+ Invalid or missing target for 'SuppressMessageAttribute'
+ Invalid or missing target for 'SuppressMessageAttribute'
+
+
+
+ Invalid scope for 'SuppressMessageAttribute'
+ Invalid scope for 'SuppressMessageAttribute'
+
+
Make field readonly
Alanı salt okunur yap
diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.zh-Hans.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.zh-Hans.xlf
index 44280509715f4..4a99f9d6f4200 100644
--- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.zh-Hans.xlf
+++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.zh-Hans.xlf
@@ -97,6 +97,21 @@
无效的格式字符串
+
+ Invalid global 'SuppressMessageAttribute'
+ Invalid global 'SuppressMessageAttribute'
+
+
+
+ Invalid or missing target for 'SuppressMessageAttribute'
+ Invalid or missing target for 'SuppressMessageAttribute'
+
+
+
+ Invalid scope for 'SuppressMessageAttribute'
+ Invalid scope for 'SuppressMessageAttribute'
+
+
Make field readonly
将字段设置为只读
diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.zh-Hant.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.zh-Hant.xlf
index ac716dafaa760..67a7fbfe54df9 100644
--- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.zh-Hant.xlf
+++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.zh-Hant.xlf
@@ -97,6 +97,21 @@
格式字串無效
+
+ Invalid global 'SuppressMessageAttribute'
+ Invalid global 'SuppressMessageAttribute'
+
+
+
+ Invalid or missing target for 'SuppressMessageAttribute'
+ Invalid or missing target for 'SuppressMessageAttribute'
+
+
+
+ Invalid scope for 'SuppressMessageAttribute'
+ Invalid scope for 'SuppressMessageAttribute'
+
+
Make field readonly
使欄位唯讀
diff --git a/src/Analyzers/Core/CodeFixes/CodeFixes.projitems b/src/Analyzers/Core/CodeFixes/CodeFixes.projitems
index 6718d89ff6d2f..098c29321c07a 100644
--- a/src/Analyzers/Core/CodeFixes/CodeFixes.projitems
+++ b/src/Analyzers/Core/CodeFixes/CodeFixes.projitems
@@ -25,6 +25,7 @@
+
diff --git a/src/Analyzers/Core/CodeFixes/CodeFixesResources.resx b/src/Analyzers/Core/CodeFixes/CodeFixesResources.resx
index 5bdb2f6c6af87..3c5b2db86ac11 100644
--- a/src/Analyzers/Core/CodeFixes/CodeFixesResources.resx
+++ b/src/Analyzers/Core/CodeFixes/CodeFixesResources.resx
@@ -138,4 +138,7 @@
Remove redundant assignment
+
+ Remove redundant suppression
+
\ No newline at end of file
diff --git a/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs b/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs
index 5208831aa4bc5..dedb006d4f0c2 100644
--- a/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs
+++ b/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs
@@ -51,6 +51,7 @@ internal static class PredefinedCodeFixProviderNames
public const string RemoveUnnecessaryCast = nameof(RemoveUnnecessaryCast);
public const string DeclareAsNullable = nameof(DeclareAsNullable);
public const string RemoveUnnecessaryImports = nameof(RemoveUnnecessaryImports);
+ public const string RemoveUnnecessarySuppressions = nameof(RemoveUnnecessarySuppressions);
public const string RemoveUnreachableCode = nameof(RemoveUnreachableCode);
public const string RemoveUnusedValues = nameof(RemoveUnusedValues);
public const string RemoveUnusedLocalFunction = nameof(RemoveUnusedLocalFunction);
diff --git a/src/Analyzers/Core/CodeFixes/RemoveUnnecessarySuppressions/RemoveUnnecessarySuppressionsCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/RemoveUnnecessarySuppressions/RemoveUnnecessarySuppressionsCodeFixProvider.cs
new file mode 100644
index 0000000000000..d593c687a6ecc
--- /dev/null
+++ b/src/Analyzers/Core/CodeFixes/RemoveUnnecessarySuppressions/RemoveUnnecessarySuppressionsCodeFixProvider.cs
@@ -0,0 +1,67 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Immutable;
+using System.Composition;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Editing;
+using Microsoft.CodeAnalysis.Host.Mef;
+
+namespace Microsoft.CodeAnalysis.RemoveUnnecessarySuppressions
+{
+ [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic, Name = PredefinedCodeFixProviderNames.RemoveUnnecessarySuppressions), Shared]
+ internal sealed class RemoveUnnecessarySuppressionsCodeFixProvider : SyntaxEditorBasedCodeFixProvider
+ {
+ [ImportingConstructor]
+ [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
+ public RemoveUnnecessarySuppressionsCodeFixProvider()
+ {
+ }
+
+ public override ImmutableArray FixableDiagnosticIds
+ => ImmutableArray.Create(IDEDiagnosticIds.InvalidSuppressMessageAttributeDiagnosticId);
+
+ internal override CodeFixCategory CodeFixCategory
+ => CodeFixCategory.CodeQuality;
+
+ public override Task RegisterCodeFixesAsync(CodeFixContext context)
+ {
+ foreach (var diagnostic in context.Diagnostics)
+ {
+ context.RegisterCodeFix(
+ new MyCodeAction(c => FixAsync(context.Document, diagnostic, c)),
+ diagnostic);
+ }
+
+ return Task.CompletedTask;
+ }
+
+ protected override Task FixAllAsync(Document document, ImmutableArray diagnostics, SyntaxEditor editor, CancellationToken cancellationToken)
+ {
+ foreach (var diagnostic in diagnostics)
+ {
+ var node = editor.OriginalRoot.FindNode(diagnostic.Location.SourceSpan);
+ if (node != null)
+ {
+ editor.RemoveNode(node);
+ }
+ }
+
+ return Task.CompletedTask;
+ }
+
+ private class MyCodeAction : CustomCodeActions.DocumentChangeAction
+ {
+ public MyCodeAction(Func> createChangedDocument)
+ : base(CodeFixesResources.Remove_redundant_suppression, createChangedDocument, nameof(RemoveUnnecessarySuppressionsCodeFixProvider))
+ {
+ }
+ }
+ }
+}
diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.cs.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.cs.xlf
index 1687052effd26..9782d34ed8dbc 100644
--- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.cs.xlf
+++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.cs.xlf
@@ -27,6 +27,11 @@
Odebrat redundantní přiřazení
+
+ Remove redundant suppression
+ Remove redundant suppression
+
+
Use discard '_'
Použít zahození _
diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.de.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.de.xlf
index e3d31ed4cfbcd..baeb79d1b82b8 100644
--- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.de.xlf
+++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.de.xlf
@@ -27,6 +27,11 @@
Überflüssige Zuweisung entfernen
+
+ Remove redundant suppression
+ Remove redundant suppression
+
+
Use discard '_'
Ausschussvariable "_" verwenden
diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.es.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.es.xlf
index 4609dcdd02d79..b2d9787ff38a0 100644
--- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.es.xlf
+++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.es.xlf
@@ -27,6 +27,11 @@
Quitar asignación redundante
+
+ Remove redundant suppression
+ Remove redundant suppression
+
+
Use discard '_'
Usar opción de descarte "_"
diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.fr.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.fr.xlf
index 47e4869a7de0a..e680cc3ab327c 100644
--- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.fr.xlf
+++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.fr.xlf
@@ -27,6 +27,11 @@
Supprimer l'attribution redondante
+
+ Remove redundant suppression
+ Remove redundant suppression
+
+
Use discard '_'
Utiliser Ignorer '_'
diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.it.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.it.xlf
index 4113ee1aad6c4..4fd93b3cc818c 100644
--- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.it.xlf
+++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.it.xlf
@@ -27,6 +27,11 @@
Rimuovi l'assegnazione ridondante
+
+ Remove redundant suppression
+ Remove redundant suppression
+
+
Use discard '_'
Usa '_' rimosso
diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ja.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ja.xlf
index fa4c68d6c283c..201e81e18adb5 100644
--- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ja.xlf
+++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ja.xlf
@@ -27,6 +27,11 @@
冗長な代入を削除します
+
+ Remove redundant suppression
+ Remove redundant suppression
+
+
Use discard '_'
破棄 '_' を使用
diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ko.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ko.xlf
index c5bd567358c55..e8b0e0f2d7025 100644
--- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ko.xlf
+++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ko.xlf
@@ -27,6 +27,11 @@
중복 할당 제거
+
+ Remove redundant suppression
+ Remove redundant suppression
+
+
Use discard '_'
무시 항목 '_' 사용
diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.pl.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.pl.xlf
index 44f1163141834..41ac3d2d1f08f 100644
--- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.pl.xlf
+++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.pl.xlf
@@ -27,6 +27,11 @@
Usuń nadmiarowe przypisanie
+
+ Remove redundant suppression
+ Remove redundant suppression
+
+
Use discard '_'
Użyj odrzucenia „_”
diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.pt-BR.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.pt-BR.xlf
index 05f339705ff11..577d6b2cdc442 100644
--- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.pt-BR.xlf
+++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.pt-BR.xlf
@@ -27,6 +27,11 @@
Remover a atribuição redundante
+
+ Remove redundant suppression
+ Remove redundant suppression
+
+
Use discard '_'
Usar o descarte de '_'
diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ru.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ru.xlf
index 9affea992cc8a..4f6af8852265c 100644
--- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ru.xlf
+++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ru.xlf
@@ -27,6 +27,11 @@
Удалить избыточные операторы назначения
+
+ Remove redundant suppression
+ Remove redundant suppression
+
+
Use discard '_'
Используйте символ удаления "_"
diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.tr.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.tr.xlf
index ab1f78f837d1f..b3e81d92db44b 100644
--- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.tr.xlf
+++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.tr.xlf
@@ -27,6 +27,11 @@
Gereksiz atamayı kaldır
+
+ Remove redundant suppression
+ Remove redundant suppression
+
+
Use discard '_'
'_' atmasını kullan
diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.zh-Hans.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.zh-Hans.xlf
index f057be0cc6057..ab5fc6f2e54ec 100644
--- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.zh-Hans.xlf
+++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.zh-Hans.xlf
@@ -27,6 +27,11 @@
删除冗余分配
+
+ Remove redundant suppression
+ Remove redundant suppression
+
+
Use discard '_'
使用丢弃 "_"
diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.zh-Hant.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.zh-Hant.xlf
index 9b0670b1634cc..8295210e99ca7 100644
--- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.zh-Hant.xlf
+++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.zh-Hant.xlf
@@ -27,6 +27,11 @@
移除多餘的指派
+
+ Remove redundant suppression
+ Remove redundant suppression
+
+
Use discard '_'
使用捨棄 '_’
diff --git a/src/Analyzers/VisualBasic/Analyzers/RemoveUnnecessarySuppressions/VisualBasicRemoveUnnecessarySuppressionsDiagnosticAnalyzer.vb b/src/Analyzers/VisualBasic/Analyzers/RemoveUnnecessarySuppressions/VisualBasicRemoveUnnecessarySuppressionsDiagnosticAnalyzer.vb
new file mode 100644
index 0000000000000..cdb222679a7da
--- /dev/null
+++ b/src/Analyzers/VisualBasic/Analyzers/RemoveUnnecessarySuppressions/VisualBasicRemoveUnnecessarySuppressionsDiagnosticAnalyzer.vb
@@ -0,0 +1,27 @@
+' Licensed to the .NET Foundation under one or more agreements.
+' The .NET Foundation licenses this file to you under the MIT license.
+' See the LICENSE file in the project root for more information.
+
+Imports Microsoft.CodeAnalysis.Diagnostics
+Imports Microsoft.CodeAnalysis.RemoveUnnecessarySuppressions
+Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
+
+Namespace Microsoft.CodeAnalysis.VisualBasic.RemoveUnnecessarySuppressions
+
+
+ Friend NotInheritable Class VisualBasicRemoveUnnecessarySuppressionsDiagnosticAnalyzer
+ Inherits AbstractRemoveUnnecessarySuppressionsDiagnosticAnalyzer
+
+ Protected Overrides Sub RegisterAttributeSyntaxAction(context As CompilationStartAnalysisContext, compilationAnalyzer As CompilationAnalyzer)
+ context.RegisterSyntaxNodeAction(
+ Sub(syntaxContext As SyntaxNodeAnalysisContext)
+ Dim attribute = DirectCast(syntaxContext.Node, AttributeSyntax)
+ Select Case attribute.Target?.AttributeModifier.Kind()
+ Case SyntaxKind.AssemblyKeyword, SyntaxKind.ModuleKeyword
+ compilationAnalyzer.AnalyzeAssemblyOrModuleAttribute(attribute, syntaxContext.SemanticModel, AddressOf syntaxContext.ReportDiagnostic, syntaxContext.CancellationToken)
+ End Select
+ End Sub,
+ SyntaxKind.Attribute)
+ End Sub
+ End Class
+End Namespace
diff --git a/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzers.projitems b/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzers.projitems
index d4b839a4ffd39..f8dd5a3132dbb 100644
--- a/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzers.projitems
+++ b/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzers.projitems
@@ -24,6 +24,7 @@
+
diff --git a/src/Analyzers/VisualBasic/Tests/RemoveUnnecessarySuppressions/RemoveUnnecessarySuppressionsTests.vb b/src/Analyzers/VisualBasic/Tests/RemoveUnnecessarySuppressions/RemoveUnnecessarySuppressionsTests.vb
new file mode 100644
index 0000000000000..8c335c034ac12
--- /dev/null
+++ b/src/Analyzers/VisualBasic/Tests/RemoveUnnecessarySuppressions/RemoveUnnecessarySuppressionsTests.vb
@@ -0,0 +1,153 @@
+' Licensed to the .NET Foundation under one or more agreements.
+' The .NET Foundation licenses this file to you under the MIT license.
+' See the LICENSE file in the project root for more information.
+
+Imports VerifyVB = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.VisualBasicCodeFixVerifier(Of
+ Microsoft.CodeAnalysis.VisualBasic.RemoveUnnecessarySuppressions.VisualBasicRemoveUnnecessarySuppressionsDiagnosticAnalyzer,
+ Microsoft.CodeAnalysis.RemoveUnnecessarySuppressions.RemoveUnnecessarySuppressionsCodeFixProvider)
+
+Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.RemoveUnnecessarySuppressions
+
+
+
+ Public Class RemoveUnnecessarySuppressionsTests
+
+ Public Sub TestStandardProperties()
+ VerifyVB.VerifyStandardProperties()
+ End Sub
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Public Async Function ValidSuppressions(scope As String, target As String, attributeTarget As String) As Task
+ Dim scopeString = If(scope IsNot Nothing, $", {scope}", String.Empty)
+ Dim targetString = If(target IsNot Nothing, $", {target}", String.Empty)
+
+ Dim input = $"
+<{attributeTarget}: System.Diagnostics.CodeAnalysis.SuppressMessage(""Category"", ""Id: Title"", Justification:=""Pending""{scopeString}{targetString})>
+
+Namespace N
+ Class C
+ Public F As Integer
+
+ Public ReadOnly Property P As Integer
+
+ Public Sub M()
+ End Sub
+ End Class
+End Namespace
+"
+ Await VerifyVB.VerifyCodeFixAsync(input, input)
+ End Function
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Public Async Function InvalidSuppressions(scope As String, target As String, attributeTarget As String) As Task
+ Dim scopeString = If(scope IsNot Nothing, $", {scope}", String.Empty)
+ Dim targetString = If(target IsNot Nothing, $", {target}", String.Empty)
+
+ Dim input = $"
+<[|{attributeTarget}: System.Diagnostics.CodeAnalysis.SuppressMessage(""Category"", ""Id: Title"", Justification:=""Pending""{scopeString}{targetString})|]>
+
+Namespace N
+ Class C
+ Public F As Integer
+
+ Public ReadOnly Property P As Integer
+
+ Public Sub M()
+ End Sub
+ End Class
+End Namespace
+"
+ Dim fixedCode = $"
+
+Namespace N
+ Class C
+ Public F As Integer
+
+ Public ReadOnly Property P As Integer
+
+ Public Sub M()
+ End Sub
+ End Class
+End Namespace
+"
+ Await VerifyVB.VerifyCodeFixAsync(input, fixedCode)
+ End Function
+
+
+ Public Async Function ValidAndInvalidSuppressions() As Task
+ Dim attributePrefix = "Assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""Category"", ""Id: Title"", Justification:=""Pending"""
+ Dim validSuppression = $"{attributePrefix}, Scope:=""member"", Target:=""~T:C"")"
+ Dim invalidSuppression = $"[|{attributePrefix}, Scope:=""member"", Target:="""")|]"
+
+ Dim input = $"
+<{validSuppression}>
+<{invalidSuppression}>
+<{validSuppression}, {validSuppression}>
+<{invalidSuppression}, {invalidSuppression}>
+<{validSuppression}, {invalidSuppression}>
+<{invalidSuppression}, {validSuppression}>
+<{invalidSuppression}, {validSuppression}, {invalidSuppression}, {validSuppression}>
+
+Class C
+End Class
+"
+ Dim fixedCode = $"
+<{validSuppression}>
+<{validSuppression}, {validSuppression}>
+<{validSuppression}>
+<{validSuppression}>
+<{validSuppression}, {validSuppression}>
+
+Class C
+End Class
+"
+ Await VerifyVB.VerifyCodeFixAsync(input, fixedCode)
+ End Function
+
+
+
+
+
+ Public Async Function LocalSuppressions(ByVal scopeAndTarget As String) As Task
+ Dim input = $"
+
+Class C
+ Sub M()
+ End Sub
+End Class"
+ Await VerifyVB.VerifyCodeFixAsync(input, input)
+ End Function
+ End Class
+End Namespace
diff --git a/src/Analyzers/VisualBasic/Tests/VisualBasicAnalyzers.UnitTests.projitems b/src/Analyzers/VisualBasic/Tests/VisualBasicAnalyzers.UnitTests.projitems
index 5c05683b2eae8..c32285c78af73 100644
--- a/src/Analyzers/VisualBasic/Tests/VisualBasicAnalyzers.UnitTests.projitems
+++ b/src/Analyzers/VisualBasic/Tests/VisualBasicAnalyzers.UnitTests.projitems
@@ -13,6 +13,7 @@
+
diff --git a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs
index f14907018027c..fb43e19804213 100644
--- a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs
+++ b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs
@@ -12234,6 +12234,80 @@ public ValueTuple(T1 item1)
Assert.Empty(output);
CleanupAllGeneratedFiles(srcFile.Path);
}
+
+ [Fact]
+ public void GlobalAnalyzerConfigsAllowedInSameDir()
+ {
+ var dir = Temp.CreateDirectory();
+ var src = dir.CreateFile("test.cs").WriteAllText(@"
+class C
+{
+ int _f;
+}");
+ var configText = @"
+is_global = true
+";
+
+ var analyzerConfig1 = dir.CreateFile("analyzerconfig1").WriteAllText(configText);
+ var analyzerConfig2 = dir.CreateFile("analyzerconfig2").WriteAllText(configText);
+
+ var cmd = CreateCSharpCompiler(null, dir.Path, new[] {
+ "/nologo",
+ "/t:library",
+ "/preferreduilang:en",
+ "/analyzerconfig:" + analyzerConfig1.Path,
+ "/analyzerconfig:" + analyzerConfig2.Path,
+ src.Path
+ });
+
+ var outWriter = new StringWriter(CultureInfo.InvariantCulture);
+ var exitCode = cmd.Run(outWriter);
+ Assert.Equal(0, exitCode);
+ }
+
+ [Fact]
+ public void GlobalAnalyzerConfigMultipleSetKeys()
+ {
+ var dir = Temp.CreateDirectory();
+ var src = dir.CreateFile("temp.cs").WriteAllText(@"
+class C
+{
+}");
+ var analyzerConfigFile = dir.CreateFile(".globalconfig");
+ var analyzerConfig = analyzerConfigFile.WriteAllText(@"
+is_global = true
+option1 = abc");
+
+ var analyzerConfigFile2 = dir.CreateFile(".globalconfig2");
+ var analyzerConfig2 = analyzerConfigFile2.WriteAllText(@"
+is_global = true
+option1 = def");
+
+ var output = VerifyOutput(dir, src, additionalFlags: new[] { "/analyzerconfig:" + analyzerConfig.Path + "," + analyzerConfig2.Path }, expectedWarningCount: 1, includeCurrentAssemblyAsAnalyzerReference: false);
+
+ // warning MultipleGlobalAnalyzerKeys: Multiple global analyzer config files set the same key 'option1' in section 'Global Section'. It has been unset. Key was set by the following files: ...
+ Assert.Contains("MultipleGlobalAnalyzerKeys:", output, StringComparison.Ordinal);
+ Assert.Contains("'option1'", output, StringComparison.Ordinal);
+ Assert.Contains("'Global Section'", output, StringComparison.Ordinal);
+
+
+ analyzerConfig = analyzerConfigFile.WriteAllText(@"
+is_global = true
+[file.cs]
+option1 = abc");
+
+ analyzerConfig2 = analyzerConfigFile2.WriteAllText(@"
+is_global = true
+[file.cs]
+option1 = def");
+
+ output = VerifyOutput(dir, src, additionalFlags: new[] { "/analyzerconfig:" + analyzerConfig.Path + "," + analyzerConfig2.Path }, expectedWarningCount: 1, includeCurrentAssemblyAsAnalyzerReference: false);
+
+ // warning MultipleGlobalAnalyzerKeys: Multiple global analyzer config files set the same key 'option1' in section 'file.cs'. It has been unset. Key was set by the following files: ...
+ Assert.Contains("MultipleGlobalAnalyzerKeys:", output, StringComparison.Ordinal);
+ Assert.Contains("'option1'", output, StringComparison.Ordinal);
+ Assert.Contains("'file.cs'", output, StringComparison.Ordinal);
+ }
}
[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
diff --git a/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs b/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs
index 01a59c67757d9..030544304c2dd 100644
--- a/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs
+++ b/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs
@@ -1471,5 +1471,557 @@ public void TreesShareOptionsInstances()
}
#endregion
+
+ #region Processing of Global configs
+
+ [Fact]
+ public void IsReportedAsGlobal()
+ {
+ var configs = ArrayBuilder.GetInstance();
+ configs.Add(Parse(@"is_global = true ", "/.editorconfig"));
+
+ var globalConfig = AnalyzerConfigSet.MergeGlobalConfigs(configs, out _);
+
+ Assert.Empty(configs);
+ Assert.NotNull(globalConfig);
+ configs.Free();
+ }
+
+ [Fact]
+ public void IsNotGlobalIfInSection()
+ {
+ var configs = ArrayBuilder.GetInstance();
+ configs.Add(Parse(@"
+[*.cs]
+is_global = true ", "/.editorconfig"));
+ var globalConfig = AnalyzerConfigSet.MergeGlobalConfigs(configs, out _);
+
+ Assert.Single(configs);
+ Assert.Null(globalConfig);
+ configs.Free();
+ }
+
+ [Fact]
+ public void FilterReturnsSingleGlobalConfig()
+ {
+ var configs = ArrayBuilder.GetInstance();
+ configs.Add(Parse(@"is_global = true
+option1 = value1", "/.globalconfig1"));
+
+ configs.Add(Parse(@"option2 = value2", "/.editorconfig1"));
+ configs.Add(Parse(@"option3 = value3", "/.editorconfig2"));
+
+ var globalConfig = AnalyzerConfigSet.MergeGlobalConfigs(configs, out var diagnostics);
+
+ diagnostics.Verify();
+ Assert.Equal(2, configs.Count);
+ Assert.NotNull(globalConfig);
+ Assert.Equal("value1", globalConfig.GlobalSection.Properties["option1"]);
+ configs.Free();
+ }
+
+ [Fact]
+ public void FilterReturnsSingleCombinedGlobalConfig()
+ {
+ var configs = ArrayBuilder.GetInstance();
+ configs.Add(Parse(@"is_global = true
+option1 = value1", "/.globalconfig1"));
+
+ configs.Add(Parse(@"is_global = true
+option2 = value2", "/.globalconfig2"));
+
+ configs.Add(Parse(@"option3 = value3", "/.editorconfig1"));
+ configs.Add(Parse(@"option4 = value4", "/.editorconfig2"));
+
+ var globalConfig = AnalyzerConfigSet.MergeGlobalConfigs(configs, out var diagnostics);
+
+ diagnostics.Verify();
+ Assert.Equal(2, configs.Count);
+ Assert.NotNull(globalConfig);
+ Assert.Equal("value1", globalConfig.GlobalSection.Properties["option1"]);
+ Assert.Equal("value2", globalConfig.GlobalSection.Properties["option2"]);
+ configs.Free();
+ }
+
+ [Fact]
+ public void FilterCombinesSections()
+ {
+ var configs = ArrayBuilder.GetInstance();
+ configs.Add(Parse(@"is_global = true
+option1 = value1
+
+[c:/path/to/file1.cs]
+option1 = value1
+
+[c:/path/to/file2.cs]
+option1 = value1
+", "/.globalconfig1"));
+
+ configs.Add(Parse(@"is_global = true
+option2 = value2
+
+[c:/path/to/file1.cs]
+option2 = value2
+
+[c:/path/to/file3.cs]
+option1 = value1",
+"/.globalconfig2"));
+
+ var globalConfig = AnalyzerConfigSet.MergeGlobalConfigs(configs, out var diagnostics);
+
+ diagnostics.Verify();
+ Assert.Empty(configs);
+ Assert.NotNull(globalConfig);
+ Assert.Equal("value1", globalConfig.GlobalSection.Properties["option1"]);
+ Assert.Equal("value2", globalConfig.GlobalSection.Properties["option2"]);
+
+ var file1Section = globalConfig.NamedSections[0];
+ var file2Section = globalConfig.NamedSections[1];
+ var file3Section = globalConfig.NamedSections[2];
+
+ Assert.Equal(@"c:/path/to/file1.cs", file1Section.Name);
+ Assert.Equal(2, file1Section.Properties.Count);
+ Assert.Equal("value1", file1Section.Properties["option1"]);
+ Assert.Equal("value2", file1Section.Properties["option2"]);
+
+ Assert.Equal(@"c:/path/to/file2.cs", file2Section.Name);
+ Assert.Equal(1, file2Section.Properties.Count);
+ Assert.Equal("value1", file2Section.Properties["option1"]);
+
+ Assert.Equal(@"c:/path/to/file3.cs", file3Section.Name);
+ Assert.Equal(1, file3Section.Properties.Count);
+ Assert.Equal("value1", file3Section.Properties["option1"]);
+ configs.Free();
+ }
+
+ [Fact]
+ public void DuplicateOptionsInGlobalConfigsAreUnset()
+ {
+ var configs = ArrayBuilder.GetInstance();
+ configs.Add(Parse(@"is_global = true
+option1 = value1", "/.globalconfig1"));
+
+ configs.Add(Parse(@"is_global = true
+option1 = value2", "/.globalconfig2"));
+
+ var globalConfig = AnalyzerConfigSet.MergeGlobalConfigs(configs, out var diagnostics);
+
+ diagnostics.Verify(
+ Diagnostic("MultipleGlobalAnalyzerKeys").WithArguments("option1", "Global Section", "/.globalconfig1, /.globalconfig2").WithLocation(1, 1)
+ );
+ }
+
+ [Fact]
+ public void DuplicateOptionsInGlobalConfigsSectionsAreUnset()
+ {
+ var configs = ArrayBuilder.GetInstance();
+ configs.Add(Parse(@"is_global = true
+[c:/path/to/file1.cs]
+option1 = value1
+", "/.globalconfig1"));
+
+ configs.Add(Parse(@"is_global = true
+[c:/path/to/file1.cs]
+option1 = value2",
+"/.globalconfig2"));
+
+ var globalConfig = AnalyzerConfigSet.MergeGlobalConfigs(configs, out var diagnostics);
+
+ diagnostics.Verify(
+ Diagnostic("MultipleGlobalAnalyzerKeys").WithArguments("option1", "c:/path/to/file1.cs", "/.globalconfig1, /.globalconfig2").WithLocation(1, 1)
+ );
+ }
+
+ [Fact]
+ public void DuplicateGlobalOptionsInNonGlobalConfigsAreKept()
+ {
+ var configs = ArrayBuilder.GetInstance();
+ configs.Add(Parse(@"is_global = true
+option1 = value1", "/.globalconfig1"));
+
+ configs.Add(Parse(@"
+option1 = value2", "/.globalconfig2"));
+
+ var globalConfig = AnalyzerConfigSet.MergeGlobalConfigs(configs, out var diagnostics);
+ diagnostics.Verify();
+ }
+
+ [Fact]
+ public void DuplicateSectionOptionsInNonGlobalConfigsAreKept()
+ {
+ var configs = ArrayBuilder.GetInstance();
+ configs.Add(Parse(@"is_global = true
+[c:/path/to/file1.cs]
+option1 = value1
+", "/.globalconfig1"));
+
+ configs.Add(Parse(@"
+[c:/path/to/file1.cs]
+option1 = value2",
+"/.globalconfig2"));
+
+ var globalConfig = AnalyzerConfigSet.MergeGlobalConfigs(configs, out var diagnostics);
+ diagnostics.Verify();
+ }
+
+ [Fact]
+ public void GlobalConfigsPropertiesAreGlobal()
+ {
+ var configs = ArrayBuilder.GetInstance();
+ configs.Add(Parse(@"is_global = true
+option1 = value1
+", "/.globalconfig1"));
+
+ var options = GetAnalyzerConfigOptions(
+ new[] { "/file1.cs", "/path/to/file1.cs", "c:/path/to/file1.cs", "/file1.vb" },
+ configs);
+ configs.Free();
+
+ VerifyAnalyzerOptions(
+ new[]
+ {
+ new[] { ("option1", "value1") },
+ new[] { ("option1", "value1") },
+ new[] { ("option1", "value1") },
+ new[] { ("option1", "value1") }
+ },
+ options);
+ }
+
+ [Fact]
+ public void GlobalConfigsSectionsMustBeFullPath()
+ {
+ var configs = ArrayBuilder.GetInstance();
+ configs.Add(Parse(@"is_global = true
+[c:/path/to/file1.cs]
+option1 = value1
+
+[*.cs]
+option2 = value2
+
+[.*/path/*.cs]
+option3 = value3
+
+[c:/.*/*.cs]
+option4 = value4
+", "/.globalconfig1"));
+
+ var options = GetAnalyzerConfigOptions(
+ new[] { "/file1.cs", "/path/to/file1.cs", "c:/path/to/file1.cs", "/file1.vb" },
+ configs);
+ configs.Free();
+
+ VerifyAnalyzerOptions(
+ new[]
+ {
+ new (string, string)[] { },
+ new (string, string)[] { },
+ new (string, string)[]
+ {
+ ("option1", "value1")
+ },
+ new (string, string)[] { }
+ },
+ options);
+ }
+
+ [Fact]
+ public void GlobalConfigsSectionsAreOverriddenByNonGlobal()
+ {
+ var configs = ArrayBuilder.GetInstance();
+ configs.Add(Parse(@"is_global = true
+option1 = global
+
+[c:/path/to/file1.cs]
+option2 = global
+option3 = global
+", "/.globalconfig1"));
+
+ configs.Add(Parse(@"
+[*.cs]
+option2 = config1
+", "/.editorconfig"));
+
+ configs.Add(Parse(@"
+[*.cs]
+option3 = config2
+", "/path/.editorconfig"));
+
+ configs.Add(Parse(@"
+[*.cs]
+option2 = config3
+", "/path/to/.editorconfig"));
+
+
+ var options = GetAnalyzerConfigOptions(
+ new[] { "/path/to/file1.cs", "/path/file1.cs", "/file1.cs" },
+ configs);
+ configs.Free();
+
+ VerifyAnalyzerOptions(
+ new[]
+ {
+ new []
+ {
+ ("option1", "global"),
+ ("option2", "config3"), // overridden by config3
+ ("option3", "config2") // overridden by config2
+ },
+ new []
+ {
+ ("option1", "global"),
+ ("option2", "config1"),
+ ("option3", "config2")
+ },
+ new []
+ {
+ ("option1", "global"),
+ ("option2", "config1")
+ }
+ },
+ options);
+ }
+
+ [Fact]
+ public void GlobalConfigSectionsAreCaseSensitive()
+ {
+ var configs = ArrayBuilder.GetInstance();
+ configs.Add(Parse(@"is_global = true
+[c:/path/to/file1.cs]
+option1 = value1
+", "/.globalconfig1"));
+
+ configs.Add(Parse(@"is_global = true
+[c:/pAth/To/fiLe1.cs]
+option1 = value2",
+"/.globalconfig2"));
+
+ var globalConfig = AnalyzerConfigSet.MergeGlobalConfigs(configs, out var diagnostics);
+ diagnostics.Verify();
+
+ Assert.Equal(2, globalConfig.NamedSections.Length);
+ configs.Free();
+ }
+
+ [Fact]
+ public void GlobalConfigSectionsPopertiesAreNotCaseSensitive()
+ {
+ var configs = ArrayBuilder.GetInstance();
+ configs.Add(Parse(@"is_global = true
+[c:/path/to/file1.cs]
+option1 = value1
+", "/.globalconfig1"));
+
+ configs.Add(Parse(@"is_global = true
+[c:/path/to/file1.cs]
+opTioN1 = value2",
+"/.globalconfig2"));
+
+ var globalConfig = AnalyzerConfigSet.MergeGlobalConfigs(configs, out var diagnostics);
+ diagnostics.Verify(
+ Diagnostic("MultipleGlobalAnalyzerKeys").WithArguments("option1", "c:/path/to/file1.cs", "/.globalconfig1, /.globalconfig2").WithLocation(1, 1)
+ );
+ configs.Free();
+ }
+
+ [Fact]
+ public void GlobalConfigPropertiesAreNotCaseSensitive()
+ {
+ var configs = ArrayBuilder.GetInstance();
+ configs.Add(Parse(@"is_global = true
+option1 = value1
+", "/.globalconfig1"));
+
+ configs.Add(Parse(@"is_global = true
+opTioN1 = value2",
+"/.globalconfig2"));
+
+ var globalConfig = AnalyzerConfigSet.MergeGlobalConfigs(configs, out var diagnostics);
+ diagnostics.Verify(
+ Diagnostic("MultipleGlobalAnalyzerKeys").WithArguments("option1", "Global Section", "/.globalconfig1, /.globalconfig2").WithLocation(1, 1)
+ );
+ configs.Free();
+ }
+
+ [Fact]
+ public void GlobalConfigSectionPathsMustBeNormalized()
+ {
+ var configs = ArrayBuilder.GetInstance();
+ configs.Add(Parse(@"is_global = true
+[c:/path/to/file1.cs]
+option1 = value1
+
+[c:\path\to\file2.cs]
+option1 = value1
+
+", "/.globalconfig1"));
+
+ var options = GetAnalyzerConfigOptions(
+ new[] { "c:/path/to/file1.cs", "c:/path/to/file2.cs" },
+ configs);
+ configs.Free();
+
+ VerifyAnalyzerOptions(
+ new[]
+ {
+ new []
+ {
+ ("option1", "value1")
+ },
+ new (string, string) [] { }
+ },
+ options);
+ }
+
+ [Fact]
+ public void GlobalConfigCanSetSeverity()
+ {
+ var configs = ArrayBuilder.GetInstance();
+ configs.Add(Parse(@"
+is_global = true
+dotnet_diagnostic.cs000.severity = none
+dotnet_diagnostic.cs001.severity = error
+", "/.editorconfig"));
+
+ var options = GetAnalyzerConfigOptions(
+ new[] { "/test.cs" },
+ configs);
+ configs.Free();
+
+ Assert.Equal(CreateImmutableDictionary(("cs000", ReportDiagnostic.Suppress),
+ ("cs001", ReportDiagnostic.Error)),
+ options[0].TreeOptions);
+ }
+
+ [Fact]
+ public void GlobalConfigCanSetSeverityInSection()
+ {
+ var configs = ArrayBuilder.GetInstance();
+ configs.Add(Parse(@"
+is_global = true
+
+[c:/path/to/file.cs]
+dotnet_diagnostic.cs000.severity = error
+", "/.editorconfig"));
+
+ var options = GetAnalyzerConfigOptions(
+ new[] { "/test.cs", "c:/path/to/file.cs" },
+ configs);
+ configs.Free();
+
+
+ Assert.Equal(new[] {
+ SyntaxTree.EmptyDiagnosticOptions,
+ CreateImmutableDictionary(("cs000", ReportDiagnostic.Error))
+ }, options.Select(o => o.TreeOptions).ToArray());
+ }
+
+ [Fact]
+ public void GlobalConfigInvalidSeverity()
+ {
+ var configs = ArrayBuilder.GetInstance();
+ configs.Add(Parse(@"
+is_global = true
+dotnet_diagnostic.cs000.severity = foo
+
+[c:/path/to/file.cs]
+dotnet_diagnostic.cs001.severity = bar
+", "/.editorconfig"));
+
+ var options = GetAnalyzerConfigOptions(
+ new[] { "/test.cs", "c:/path/to/file.cs" },
+ configs);
+ configs.Free();
+
+ options[0].Diagnostics.Verify(
+ Diagnostic("InvalidSeverityInAnalyzerConfig").WithArguments("cs000", "foo", "").WithLocation(1, 1)
+ );
+
+ options[1].Diagnostics.Verify(
+ Diagnostic("InvalidSeverityInAnalyzerConfig").WithArguments("cs000", "foo", "").WithLocation(1, 1),
+ Diagnostic("InvalidSeverityInAnalyzerConfig").WithArguments("cs001", "bar", "").WithLocation(1, 1)
+ );
+ }
+
+
+ [Fact]
+ public void GlobalConfigCanSeverityInSectionOverridesGlobal()
+ {
+ var configs = ArrayBuilder.GetInstance();
+ configs.Add(Parse(@"
+is_global = true
+dotnet_diagnostic.cs000.severity = none
+
+[c:/path/to/file.cs]
+dotnet_diagnostic.cs000.severity = error
+", "/.editorconfig"));
+
+ var options = GetAnalyzerConfigOptions(
+ new[] { "c:/path/to/file.cs" },
+ configs);
+ configs.Free();
+
+ Assert.Equal(
+ CreateImmutableDictionary(("cs000", ReportDiagnostic.Error)),
+ options[0].TreeOptions);
+ }
+
+
+ [Fact]
+ public void GlobalConfigSeverityIsOverridenByEditorConfig()
+ {
+ var configs = ArrayBuilder.GetInstance();
+ configs.Add(Parse(@"
+is_global = true
+dotnet_diagnostic.cs000.severity = error
+", "/.globalconfig"));
+
+ configs.Add(Parse(@"
+[*.cs]
+dotnet_diagnostic.cs000.severity = none
+", "/.editorconfig"));
+
+ configs.Add(Parse(@"
+[*.cs]
+dotnet_diagnostic.cs000.severity = warning
+", "/path/.editorconfig"));
+
+ var options = GetAnalyzerConfigOptions(
+ new[] { "/test.cs", "/path/file.cs" },
+ configs);
+ configs.Free();
+
+
+ Assert.Equal(new[] {
+ CreateImmutableDictionary(("cs000", ReportDiagnostic.Suppress)),
+ CreateImmutableDictionary(("cs000", ReportDiagnostic.Warn))
+ }, options.Select(o => o.TreeOptions).ToArray());
+ }
+
+ [Fact]
+ public void GlobalKeyIsNotSkippedIfInSection()
+ {
+ var configs = ArrayBuilder.GetInstance();
+ configs.Add(Parse(@"
+is_global = true
+[/path/to/file.cs]
+is_global = true
+", "/.globalconfig"));
+
+ var options = GetAnalyzerConfigOptions(
+ new[] { "/file.cs", "/path/to/file.cs" },
+ configs);
+ configs.Free();
+
+ VerifyAnalyzerOptions(
+ new[]
+ {
+ new (string,string)[] { },
+ new[] { ("is_global", "true") }
+ },
+ options);
+ }
+
+ #endregion
}
}
diff --git a/src/Compilers/Core/Portable/CodeAnalysisResources.resx b/src/Compilers/Core/Portable/CodeAnalysisResources.resx
index 7cc860ce51c3f..16c96ddfd61fc 100644
--- a/src/Compilers/Core/Portable/CodeAnalysisResources.resx
+++ b/src/Compilers/Core/Portable/CodeAnalysisResources.resx
@@ -690,4 +690,10 @@
Only a single {0} can be registered per generator.
+
+ Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'
+
+
+ Multiple global analyzer config files set the same key. It has been unset.
+
\ No newline at end of file
diff --git a/src/Compilers/Core/Portable/CommandLine/AnalyzerConfig.cs b/src/Compilers/Core/Portable/CommandLine/AnalyzerConfig.cs
index 809ca12a103c5..2419f268fa5eb 100644
--- a/src/Compilers/Core/Portable/CommandLine/AnalyzerConfig.cs
+++ b/src/Compilers/Core/Portable/CommandLine/AnalyzerConfig.cs
@@ -25,6 +25,11 @@ public sealed partial class AnalyzerConfig
// Matches EditorConfig property such as "indent_style = space", see http://editorconfig.org for details
private static readonly Regex s_propertyMatcher = new Regex(@"^\s*([\w\.\-_]+)\s*[=:]\s*(.*?)\s*([#;].*)?$", RegexOptions.Compiled);
+ ///
+ /// Key that indicates if this config is a global config
+ ///
+ internal const string GlobalKey = "is_global";
+
///
/// A set of keys that are reserved for special interpretation for the editorconfig specification.
/// All values corresponding to reserved keys in a (key,value) property pair are always lowercased
@@ -80,6 +85,11 @@ public sealed partial class AnalyzerConfig
///
internal bool IsRoot => GlobalSection.Properties.TryGetValue("root", out string val) && val == "true";
+ ///
+ /// Gets whether this editorconfig is a global editorconfig.
+ ///
+ internal bool IsGlobal => GlobalSection.Properties.ContainsKey(GlobalKey);
+
private AnalyzerConfig(
Section globalSection,
ImmutableArray namedSections,
@@ -222,6 +232,12 @@ internal sealed class Section
///
public static StringComparison NameComparer { get; } = StringComparison.Ordinal;
+ ///
+ /// Used to compare s of sections. Specified by editorconfig to
+ /// be a case-sensitive comparison.
+ ///
+ public static IEqualityComparer NameEqualityComparer { get; } = StringComparer.Ordinal;
+
///
/// Used to compare keys in . The editorconfig spec defines property
/// keys as being compared case-insensitively according to Unicode lower-case rules.
diff --git a/src/Compilers/Core/Portable/CommandLine/AnalyzerConfigSet.cs b/src/Compilers/Core/Portable/CommandLine/AnalyzerConfigSet.cs
index 0685ae9192d83..f5cf4d601baf4 100644
--- a/src/Compilers/Core/Portable/CommandLine/AnalyzerConfigSet.cs
+++ b/src/Compilers/Core/Portable/CommandLine/AnalyzerConfigSet.cs
@@ -30,6 +30,8 @@ public sealed class AnalyzerConfigSet
///
private readonly ImmutableArray _analyzerConfigs;
+ private readonly GlobalAnalyzerConfig? _globalConfig;
+
///
/// s for each section. The entries in the outer array correspond to entries in , and each inner array
/// corresponds to each .
@@ -93,18 +95,34 @@ private readonly static DiagnosticDescriptor InvalidAnalyzerConfigSeverityDescri
DiagnosticSeverity.Warning,
isEnabledByDefault: true);
+ private readonly static DiagnosticDescriptor MultipleGlobalAnalyzerKeysDescriptor
+ = new DiagnosticDescriptor(
+ "MultipleGlobalAnalyzerKeys",
+ CodeAnalysisResources.WRN_MultipleGlobalAnalyzerKeys_Title,
+ CodeAnalysisResources.WRN_MultipleGlobalAnalyzerKeys,
+ "AnalyzerConfig",
+ DiagnosticSeverity.Warning,
+ isEnabledByDefault: true);
+
public static AnalyzerConfigSet Create(TList analyzerConfigs) where TList : IReadOnlyCollection
+ {
+ return Create(analyzerConfigs, out _);
+ }
+
+ public static AnalyzerConfigSet Create(TList analyzerConfigs, out ImmutableArray diagnostics) where TList : IReadOnlyCollection
{
var sortedAnalyzerConfigs = ArrayBuilder.GetInstance(analyzerConfigs.Count);
sortedAnalyzerConfigs.AddRange(analyzerConfigs);
sortedAnalyzerConfigs.Sort(AnalyzerConfig.DirectoryLengthComparer);
- return new AnalyzerConfigSet(sortedAnalyzerConfigs.ToImmutableAndFree());
+ var globalConfig = MergeGlobalConfigs(sortedAnalyzerConfigs, out diagnostics);
+ return new AnalyzerConfigSet(sortedAnalyzerConfigs.ToImmutableAndFree(), globalConfig);
}
- private AnalyzerConfigSet(ImmutableArray analyzerConfigs)
+ private AnalyzerConfigSet(ImmutableArray analyzerConfigs, GlobalAnalyzerConfig? globalConfig)
{
_analyzerConfigs = analyzerConfigs;
+ _globalConfig = globalConfig;
var allMatchers = ArrayBuilder>.GetInstance(_analyzerConfigs.Length);
@@ -146,6 +164,18 @@ public AnalyzerConfigOptionsResult GetOptionsForSourcePath(string sourcePath)
var normalizedPath = PathUtilities.NormalizeWithForwardSlash(sourcePath);
+ // If we have a global config, add any sections that match the full path
+ if (_globalConfig is object)
+ {
+ foreach (var section in _globalConfig.NamedSections)
+ {
+ if (normalizedPath.Equals(section.Name, Section.NameComparer))
+ {
+ sectionKey.Add(section);
+ }
+ }
+ }
+
// The editorconfig paths are sorted from shortest to longest, so matches
// are resolved from most nested to least nested, where last setting wins
for (int analyzerConfigIndex = 0; analyzerConfigIndex < _analyzerConfigs.Length; analyzerConfigIndex++)
@@ -192,6 +222,36 @@ public AnalyzerConfigOptionsResult GetOptionsForSourcePath(string sourcePath)
var diagnosticBuilder = ArrayBuilder.GetInstance();
int sectionKeyIndex = 0;
+
+ if (_globalConfig is object)
+ {
+ addOptions(_globalConfig.GlobalSection,
+ treeOptionsBuilder,
+ analyzerOptionsBuilder,
+ diagnosticBuilder,
+ GlobalAnalyzerConfigBuilder.GlobalConfigPath,
+ _diagnosticIdCache);
+
+ foreach (var configSection in _globalConfig.NamedSections)
+ {
+ if (sectionKey.Count > 0 && configSection == sectionKey[sectionKeyIndex])
+ {
+ addOptions(
+ sectionKey[sectionKeyIndex],
+ treeOptionsBuilder,
+ analyzerOptionsBuilder,
+ diagnosticBuilder,
+ GlobalAnalyzerConfigBuilder.GlobalConfigPath,
+ _diagnosticIdCache);
+ sectionKeyIndex++;
+ if (sectionKeyIndex == sectionKey.Count)
+ {
+ break;
+ }
+ }
+ }
+ }
+
for (int analyzerConfigIndex = 0;
analyzerConfigIndex < _analyzerConfigs.Length && sectionKeyIndex < sectionKey.Count;
analyzerConfigIndex++)
@@ -349,5 +409,180 @@ internal static bool TryParseSeverity(string value, out ReportDiagnostic severit
severity = default;
return false;
}
+
+ ///
+ /// Merge any partial global configs into a single global config, and remove the partial configs
+ ///
+ /// An of containing a mix of regular and unmerged partial global configs
+ /// Diagnostics produced during merge will be added to this bag
+ /// A that contains the merged partial configs, or null if there were no partial configs
+ internal static GlobalAnalyzerConfig? MergeGlobalConfigs(ArrayBuilder analyzerConfigs, out ImmutableArray diagnostics)
+ {
+ GlobalAnalyzerConfigBuilder globalAnalyzerConfigBuilder = new GlobalAnalyzerConfigBuilder();
+ for (int i = 0; i < analyzerConfigs.Count; i++)
+ {
+ if (analyzerConfigs[i].IsGlobal)
+ {
+ globalAnalyzerConfigBuilder.MergeIntoGlobalConfig(analyzerConfigs[i]);
+ analyzerConfigs.RemoveAt(i);
+ i--;
+ }
+ }
+
+ DiagnosticBag diagnosticBag = DiagnosticBag.GetInstance();
+ var globalConfig = globalAnalyzerConfigBuilder.Build(diagnosticBag);
+ diagnostics = diagnosticBag.ToReadOnlyAndFree();
+ return globalConfig;
+ }
+
+ ///
+ /// Builds a global analyzer config from a series of partial configs
+ ///
+ internal struct GlobalAnalyzerConfigBuilder
+ {
+ private ImmutableDictionary.Builder>.Builder? _values;
+ private ImmutableDictionary>.Builder>.Builder? _duplicates;
+
+ internal const string GlobalConfigPath = "";
+ internal const string GlobalSectionName = "Global Section";
+
+ internal void MergeIntoGlobalConfig(AnalyzerConfig config)
+ {
+ if (_values is null)
+ {
+ _values = ImmutableDictionary.CreateBuilder.Builder>(Section.NameEqualityComparer);
+ _duplicates = ImmutableDictionary.CreateBuilder>.Builder>(Section.NameEqualityComparer);
+ }
+
+ MergeSection(config.PathToFile, config.GlobalSection, isGlobalSection: true);
+ foreach (var section in config.NamedSections)
+ {
+ MergeSection(config.PathToFile, section, isGlobalSection: false);
+ }
+ }
+
+ internal GlobalAnalyzerConfig? Build(DiagnosticBag diagnostics)
+ {
+ if (_values is null || _duplicates is null)
+ {
+ return null;
+ }
+
+ // issue diagnostics for any duplicate keys
+ foreach ((var section, var keys) in _duplicates)
+ {
+ bool isGlobalSection = string.IsNullOrWhiteSpace(section);
+ string sectionName = isGlobalSection ? GlobalSectionName : section;
+ foreach ((var keyName, var configPaths) in keys)
+ {
+ diagnostics.Add(Diagnostic.Create(
+ MultipleGlobalAnalyzerKeysDescriptor,
+ Location.None,
+ keyName,
+ sectionName,
+ string.Join(", ", configPaths)));
+ }
+ }
+ _duplicates = null;
+
+ // gather the global and named sections
+ Section globalSection = GetSection(string.Empty);
+ _values.Remove(string.Empty);
+
+ ArrayBuilder namedSectionBuilder = new ArrayBuilder(_values.Count);
+ foreach (var sectionName in _values.Keys.Order())
+ {
+ namedSectionBuilder.Add(GetSection(sectionName));
+ }
+
+ // create the global config
+ GlobalAnalyzerConfig globalConfig = new GlobalAnalyzerConfig(globalSection, namedSectionBuilder.ToImmutableAndFree());
+ _values = null;
+ return globalConfig;
+ }
+
+ private Section GetSection(string sectionName)
+ {
+ Debug.Assert(_values is object);
+
+ var dict = _values[sectionName];
+ var result = dict.ToImmutableDictionary(d => d.Key, d => d.Value.value, Section.PropertiesKeyComparer);
+ return new Section(sectionName, result);
+ }
+
+ private void MergeSection(string configPath, Section section, bool isGlobalSection)
+ {
+ Debug.Assert(_values is object);
+ Debug.Assert(_duplicates is object);
+
+ if (!_values.TryGetValue(section.Name, out var sectionDict))
+ {
+ sectionDict = ImmutableDictionary.CreateBuilder(Section.PropertiesKeyComparer);
+ _values.Add(section.Name, sectionDict);
+ }
+
+ _duplicates.TryGetValue(section.Name, out var duplicateDict);
+ foreach ((var key, var value) in section.Properties)
+ {
+ if (isGlobalSection && Section.PropertiesKeyComparer.Equals(key, GlobalKey))
+ {
+ continue;
+ }
+
+ bool keyInSection = sectionDict.ContainsKey(key);
+ bool keyDuplicated = duplicateDict?.ContainsKey(key) ?? false;
+
+ // if this key is neither already present, or already duplicate, we can add it
+ if (!keyInSection && !keyDuplicated)
+ {
+ sectionDict.Add(key, (value, configPath));
+ }
+ else
+ {
+ if (duplicateDict is null)
+ {
+ duplicateDict = ImmutableDictionary.CreateBuilder>(Section.PropertiesKeyComparer);
+ _duplicates.Add(section.Name, duplicateDict);
+ }
+
+ // record that this key is now a duplicate
+ ArrayBuilder configList = keyDuplicated ? duplicateDict[key] : ArrayBuilder.GetInstance();
+ configList.Add(configPath);
+ duplicateDict[key] = configList;
+
+ // if we'd previously added this key, remove it and remember the extra duplicate location
+ if (keyInSection)
+ {
+ var originalConfigPath = sectionDict[key].configPath;
+ sectionDict.Remove(key);
+ duplicateDict[key].Insert(0, originalConfigPath);
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Represents a combined global analyzer config.
+ ///
+ ///
+ /// We parse all s as individual files, according to the editorconfig spec.
+ ///
+ /// However, when viewing the configs as an if multiple files have the
+ /// is_global property set to true we combine those files and treat them as a single
+ /// 'logical' global config file. This type represents that combined file.
+ ///
+ internal sealed class GlobalAnalyzerConfig
+ {
+ internal AnalyzerConfig.Section GlobalSection { get; }
+
+ internal ImmutableArray NamedSections { get; }
+
+ public GlobalAnalyzerConfig(AnalyzerConfig.Section globalSection, ImmutableArray namedSections)
+ {
+ GlobalSection = globalSection;
+ NamedSections = namedSections;
+ }
+ }
}
}
diff --git a/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs b/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs
index 015cf2c133b0c..8fda24cffc1eb 100644
--- a/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs
+++ b/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs
@@ -299,18 +299,20 @@ internal bool TryGetAnalyzerConfigSet(
}
var directory = Path.GetDirectoryName(normalizedPath) ?? normalizedPath;
+ var editorConfig = AnalyzerConfig.Parse(fileContent, normalizedPath);
- if (processedDirs.Contains(directory))
+ if (!editorConfig.IsGlobal)
{
- diagnostics.Add(Diagnostic.Create(
- MessageProvider,
- MessageProvider.ERR_MultipleAnalyzerConfigsInSameDir,
- directory));
- break;
+ if (processedDirs.Contains(directory))
+ {
+ diagnostics.Add(Diagnostic.Create(
+ MessageProvider,
+ MessageProvider.ERR_MultipleAnalyzerConfigsInSameDir,
+ directory));
+ break;
+ }
+ processedDirs.Add(directory);
}
- processedDirs.Add(directory);
-
- var editorConfig = AnalyzerConfig.Parse(fileContent, normalizedPath);
configs.Add(editorConfig);
}
@@ -323,7 +325,8 @@ internal bool TryGetAnalyzerConfigSet(
return false;
}
- analyzerConfigSet = AnalyzerConfigSet.Create(configs);
+ analyzerConfigSet = AnalyzerConfigSet.Create(configs, out var setDiagnostics);
+ diagnostics.AddRange(setDiagnostics);
return true;
}
diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/SuppressMessageAttributeState.TargetScope.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/SuppressMessageAttributeState.TargetScope.cs
new file mode 100644
index 0000000000000..76e68c4088adf
--- /dev/null
+++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/SuppressMessageAttributeState.TargetScope.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace Microsoft.CodeAnalysis.Diagnostics
+{
+ internal partial class SuppressMessageAttributeState
+ {
+ internal enum TargetScope
+ {
+ None,
+ Module,
+ Namespace,
+ Resource,
+ Type,
+ Member,
+ NamespaceAndDescendants
+ }
+ }
+}
diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/SuppressMessageAttributeState.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/SuppressMessageAttributeState.cs
index c6aa4d67f17c9..556f4a5333d57 100644
--- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/SuppressMessageAttributeState.cs
+++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/SuppressMessageAttributeState.cs
@@ -379,16 +379,5 @@ private static bool TryDecodeSuppressMessageAttributeData(AttributeData attribut
return true;
}
-
- internal enum TargetScope
- {
- None,
- Module,
- Namespace,
- Resource,
- Type,
- Member,
- NamespaceAndDescendants
- }
}
}
diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt
index 80f30cbdf49e5..30bc51c95066c 100644
--- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt
+++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt
@@ -19,6 +19,7 @@ Microsoft.CodeAnalysis.Operations.IRelationalPatternOperation.OperatorKind.get -
Microsoft.CodeAnalysis.Operations.IRelationalPatternOperation.Value.get -> Microsoft.CodeAnalysis.IOperation
Microsoft.CodeAnalysis.Operations.ITypePatternOperation
Microsoft.CodeAnalysis.Operations.ITypePatternOperation.MatchedType.get -> Microsoft.CodeAnalysis.ITypeSymbol
+static Microsoft.CodeAnalysis.AnalyzerConfigSet.Create(TList analyzerConfigs, out System.Collections.Immutable.ImmutableArray diagnostics) -> Microsoft.CodeAnalysis.AnalyzerConfigSet
virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitBinaryPattern(Microsoft.CodeAnalysis.Operations.IBinaryPatternOperation operation) -> void
virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitNegatedPattern(Microsoft.CodeAnalysis.Operations.INegatedPatternOperation operation) -> void
virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitRelationalPattern(Microsoft.CodeAnalysis.Operations.IRelationalPatternOperation operation) -> void
diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.cs.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.cs.xlf
index 2a81353809149..3e7658e66fd14 100644
--- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.cs.xlf
+++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.cs.xlf
@@ -554,6 +554,16 @@
Neplatná závažnost v konfiguračním souboru analyzátoru
+
+ Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'
+ Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'
+
+
+
+ Multiple global analyzer config files set the same key. It has been unset.
+ Multiple global analyzer config files set the same key. It has been unset.
+
+
WindowsRuntime identity can't be retargetable
U identity WindowsRuntime se nedá změnit cíl.
diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.de.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.de.xlf
index 3b5234b730dd9..0b7625e689fdf 100644
--- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.de.xlf
+++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.de.xlf
@@ -554,6 +554,16 @@
Ungültiger Schweregrad in der Konfigurationsdatei des Analysetools.
+
+ Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'
+ Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'
+
+
+
+ Multiple global analyzer config files set the same key. It has been unset.
+ Multiple global analyzer config files set the same key. It has been unset.
+
+
WindowsRuntime identity can't be retargetable
WindowsRuntime-Identität darf nicht anzielbar sein
diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.es.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.es.xlf
index 0e1096ce51bb8..f8aa9eb8fa431 100644
--- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.es.xlf
+++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.es.xlf
@@ -554,6 +554,16 @@
Gravedad no válida en el archivo de configuración del analizador.
+
+ Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'
+ Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'
+
+
+
+ Multiple global analyzer config files set the same key. It has been unset.
+ Multiple global analyzer config files set the same key. It has been unset.
+
+
WindowsRuntime identity can't be retargetable
La identidad WindowsRuntime no puede ser redestinable
diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.fr.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.fr.xlf
index 9ce6fde95f370..f611856c7b43e 100644
--- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.fr.xlf
+++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.fr.xlf
@@ -554,6 +554,16 @@
Niveau de gravité non valide dans le fichier config de l'analyseur.
+
+ Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'
+ Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'
+
+
+
+ Multiple global analyzer config files set the same key. It has been unset.
+ Multiple global analyzer config files set the same key. It has been unset.
+
+
WindowsRuntime identity can't be retargetable
L'identité WindowsRuntime ne peut pas être reciblable
diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.it.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.it.xlf
index 2fd8fe0c80e63..185e7895af319 100644
--- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.it.xlf
+++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.it.xlf
@@ -554,6 +554,16 @@
Gravità non valida nel file di configurazione dell'analizzatore.
+
+ Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'
+ Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'
+
+
+
+ Multiple global analyzer config files set the same key. It has been unset.
+ Multiple global analyzer config files set the same key. It has been unset.
+
+
WindowsRuntime identity can't be retargetable
Non è possibile ridefinire la destinazione dell'identità WindowsRuntime
diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ja.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ja.xlf
index e97a9314e389c..fe339de8e2759 100644
--- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ja.xlf
+++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ja.xlf
@@ -554,6 +554,16 @@
アナライザー構成ファイルの重大度が無効です。
+
+ Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'
+ Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'
+
+
+
+ Multiple global analyzer config files set the same key. It has been unset.
+ Multiple global analyzer config files set the same key. It has been unset.
+
+
WindowsRuntime identity can't be retargetable
WindowsRuntime の ID を再ターゲット可能にすることはできません
diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ko.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ko.xlf
index 7a748cac9401e..3e664357b3a93 100644
--- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ko.xlf
+++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ko.xlf
@@ -554,6 +554,16 @@
분석기 구성 파일의 심각도가 잘못되었습니다.
+
+ Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'
+ Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'
+
+
+
+ Multiple global analyzer config files set the same key. It has been unset.
+ Multiple global analyzer config files set the same key. It has been unset.
+
+
WindowsRuntime identity can't be retargetable
WindowsRuntime ID는 대상으로 다시 지정할 수 없습니다.
diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pl.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pl.xlf
index 78faaf3672fc2..5abb562102bc2 100644
--- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pl.xlf
+++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pl.xlf
@@ -554,6 +554,16 @@
Nieprawidłowa ważność w pliku konfiguracji analizatora.
+
+ Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'
+ Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'
+
+
+
+ Multiple global analyzer config files set the same key. It has been unset.
+ Multiple global analyzer config files set the same key. It has been unset.
+
+
WindowsRuntime identity can't be retargetable
Tożsamość WindowsRuntime nie może być elementem, który można ponownie ustawić jako cel
diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pt-BR.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pt-BR.xlf
index 18eb888cda6a8..c3017baec640e 100644
--- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pt-BR.xlf
+++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pt-BR.xlf
@@ -554,6 +554,16 @@
Gravidade inválida no arquivo de configuração do analisador.
+
+ Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'
+ Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'
+
+
+
+ Multiple global analyzer config files set the same key. It has been unset.
+ Multiple global analyzer config files set the same key. It has been unset.
+
+
WindowsRuntime identity can't be retargetable
Identidade de WindowsRuntime não pode ser redirecionável
diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ru.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ru.xlf
index c940ebd97a3cd..ce51f46a4717e 100644
--- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ru.xlf
+++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ru.xlf
@@ -554,6 +554,16 @@
Недопустимая серьезность в файле конфигурации анализатора.
+
+ Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'
+ Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'
+
+
+
+ Multiple global analyzer config files set the same key. It has been unset.
+ Multiple global analyzer config files set the same key. It has been unset.
+
+
WindowsRuntime identity can't be retargetable
Идентификатор WindowsRuntime не может быть перенацеливаемым
diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.tr.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.tr.xlf
index 3b2cf034530be..af67e003dba69 100644
--- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.tr.xlf
+++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.tr.xlf
@@ -554,6 +554,16 @@
Çözümleyici yapılandırma dosyasında geçersiz önem derecesi.
+
+ Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'
+ Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'
+
+
+
+ Multiple global analyzer config files set the same key. It has been unset.
+ Multiple global analyzer config files set the same key. It has been unset.
+
+
WindowsRuntime identity can't be retargetable
WindowsRuntime kimliği yeniden hedeflendirilebilir olamaz
diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hans.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hans.xlf
index ab89dd9138ae4..fa693e92569c7 100644
--- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hans.xlf
+++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hans.xlf
@@ -554,6 +554,16 @@
分析器配置文件中的严重性无效。
+
+ Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'
+ Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'
+
+
+
+ Multiple global analyzer config files set the same key. It has been unset.
+ Multiple global analyzer config files set the same key. It has been unset.
+
+
WindowsRuntime identity can't be retargetable
WindowsRuntime 标识不可重定目标
diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hant.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hant.xlf
index f12290034b3d8..ca6170f3be406 100644
--- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hant.xlf
+++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hant.xlf
@@ -554,6 +554,16 @@
分析器組態檔中的嚴重性無效。
+
+ Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'
+ Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'
+
+
+
+ Multiple global analyzer config files set the same key. It has been unset.
+ Multiple global analyzer config files set the same key. It has been unset.
+
+
WindowsRuntime identity can't be retargetable
無法重新指定 WindowsRuntime 身分識別目標
diff --git a/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs b/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs
index d4ad315b20bd8..9256a9e593d31 100644
--- a/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs
+++ b/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs
@@ -376,6 +376,9 @@ public void CSharp_VerifyIDEDiagnosticSeveritiesAreConfigurable()
# IDE0075
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+# IDE0076
+dotnet_diagnostic.IDE0076.severity = %value%
+
# IDE1005
csharp_style_conditional_delegate_call = true:suggestion
@@ -516,6 +519,9 @@ public void VisualBasic_VerifyIDEDiagnosticSeveritiesAreConfigurable()
# IDE0075
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+# IDE0076
+dotnet_diagnostic.IDE0076.severity = %value%
+
# IDE1006
dotnet_diagnostic.IDE1006.severity = %value%
@@ -882,6 +888,9 @@ No editorconfig based code style option
# IDE0075, PreferSimplifiedBooleanExpressions
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+# IDE0076
+No editorconfig based code style option
+
# IDE1005, PreferConditionalDelegateCall
csharp_style_conditional_delegate_call = true:suggestion
@@ -1055,6 +1064,9 @@ No editorconfig based code style option
# IDE0075, PreferSimplifiedBooleanExpressions
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+# IDE0076
+No editorconfig based code style option
+
# IDE1006
No editorconfig based code style option
diff --git a/src/Test/Utilities/Portable/Traits/Traits.cs b/src/Test/Utilities/Portable/Traits/Traits.cs
index 33d699c42c749..361ddb14bb240 100644
--- a/src/Test/Utilities/Portable/Traits/Traits.cs
+++ b/src/Test/Utilities/Portable/Traits/Traits.cs
@@ -133,6 +133,7 @@ public static class Features
public const string CodeActionsQualifyMemberAccess = "CodeActions.QualifyMemberAccess";
public const string CodeActionsRemoveByVal = "CodeActions.RemoveByVal";
public const string CodeActionsRemoveDocCommentNode = "CodeActions.RemoveDocCommentNode";
+ public const string CodeActionsRemoveUnnecessarySuppressions = "CodeActions.RemoveUnnecessarySuppressions";
public const string CodeActionsRemoveUnnecessaryCast = "CodeActions.RemoveUnnecessaryCast";
public const string CodeActionsRemoveUnnecessaryImports = "CodeActions.RemoveUnnecessaryImports";
public const string CodeActionsRemoveUnnecessaryParentheses = "CodeActions.RemoveUnnecessaryParentheses";