From ab23c4f5517434a26122f2fb9090b2a478d409cc Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Fri, 15 May 2020 08:07:10 -0700 Subject: [PATCH 1/7] Add analyzer/fixer for global SuppressMessageAttributes with invalid Scope and/or Target These suppressions are redundant and can be removed. Closes #44176 --- .../Analyzers/CSharpAnalyzers.projitems | 1 + ...necessarySuppressionsDiagnosticAnalyzer.cs | 36 ++++ .../Tests/CSharpAnalyzers.UnitTests.projitems | 1 + .../RemoveUnnecessarySuppressionsTests.cs | 186 ++++++++++++++++++ .../Core/Analyzers/Analyzers.projitems | 4 + .../Core/Analyzers/AnalyzersResources.resx | 9 + .../Core/Analyzers/IDEDiagnosticIds.cs | 2 + ...necessarySuppressionsDiagnosticAnalyzer.cs | 87 ++++++++ .../SuppressMessageAttributeState.cs | 152 ++++++++++++++ .../Core/CodeFixes/CodeFixes.projitems | 1 + .../Core/CodeFixes/CodeFixesResources.resx | 3 + .../PredefinedCodeFixProviderNames.cs | 1 + ...eUnnecessarySuppressionsCodeFixProvider.cs | 66 +++++++ ...necessarySuppressionsDiagnosticAnalyzer.vb | 27 +++ .../Analyzers/VisualBasicAnalyzers.projitems | 1 + .../RemoveUnnecessarySuppressionsTests.vb | 153 ++++++++++++++ .../VisualBasicAnalyzers.UnitTests.projitems | 1 + ...ppressMessageAttributeState.TargetScope.cs | 20 ++ .../SuppressMessageAttributeState.cs | 11 -- src/Test/Utilities/Portable/Traits/Traits.cs | 1 + 20 files changed, 752 insertions(+), 11 deletions(-) create mode 100644 src/Analyzers/CSharp/Analyzers/RemoveUnnecessarySuppressions/CSharpRemoveUnnecessarySuppressionsDiagnosticAnalyzer.cs create mode 100644 src/Analyzers/CSharp/Tests/RemoveUnnecessarySuppressions/RemoveUnnecessarySuppressionsTests.cs create mode 100644 src/Analyzers/Core/Analyzers/RemoveUnnecessarySuppressions/AbstractRemoveUnnecessarySuppressionsDiagnosticAnalyzer.cs create mode 100644 src/Analyzers/Core/Analyzers/RemoveUnnecessarySuppressions/SuppressMessageAttributeState.cs create mode 100644 src/Analyzers/Core/CodeFixes/RemoveUnnecessarySuppressions/RemoveUnnecessarySuppressionsCodeFixProvider.cs create mode 100644 src/Analyzers/VisualBasic/Analyzers/RemoveUnnecessarySuppressions/VisualBasicRemoveUnnecessarySuppressionsDiagnosticAnalyzer.vb create mode 100644 src/Analyzers/VisualBasic/Tests/RemoveUnnecessarySuppressions/RemoveUnnecessarySuppressionsTests.vb create mode 100644 src/Compilers/Core/Portable/DiagnosticAnalyzer/SuppressMessageAttributeState.TargetScope.cs 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..0f3c47d64d1af --- /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.ReportDiagnostic); + } + + 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..a25fdbc9e9db5 --- /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 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, Action reportDiagnostic) + { + if (!_state.IsGlobalSuppressMessageAttribute(attributeSyntax, out var attribute) || + attribute.NamedArguments.IsEmpty) + { + return; + } + + DiagnosticDescriptor rule; + if (_state.HasInvalidScope(attribute, out var targetScope)) + { + rule = s_invalidScopeDescriptor; + } + else if (_state.HasInvalidOrMissingTarget(attribute, 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..78a1dc592943b --- /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.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis.PooledObjects; + +#if NETSTANDARD2_0 +using Roslyn.Utilities; +#endif + +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; + private readonly Lazy> _lazySuppressMessageAttributesBySyntax; + + public SuppressMessageAttributeState(Compilation compilation, INamedTypeSymbol suppressMessageAttributeType) + { + _compilation = compilation; + _suppressMessageAttributeType = suppressMessageAttributeType; + _lazySuppressMessageAttributesBySyntax = new Lazy>(CreateAttributesBySyntaxMap); + } + + 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(); + } + + private ImmutableDictionary CreateAttributesBySyntaxMap() + { + var builder = ImmutableDictionary.CreateBuilder(); + AddAttributes(_compilation.Assembly, _suppressMessageAttributeType, builder); + foreach (var module in _compilation.Assembly.Modules) + { + AddAttributes(module, _suppressMessageAttributeType, builder); + } + + return builder.ToImmutable(); + + // Local functions. + static void AddAttributes(ISymbol symbol, INamedTypeSymbol suppressMessageAttributeType, ImmutableDictionary.Builder builder) + { + foreach (var attribute in symbol.GetAttributes()) + { + if (suppressMessageAttributeType.Equals(attribute.AttributeClass) && + attribute.ApplicationSyntaxReference?.GetSyntax() is SyntaxNode node) + { + builder.Add(node, attribute); + } + } + } + } + + public bool IsGlobalSuppressMessageAttribute(SyntaxNode attributeSyntax, [NotNullWhen(returnValue: true)] out AttributeData? attribute) + => _lazySuppressMessageAttributesBySyntax.Value.TryGetValue(attributeSyntax, out attribute); + + public bool HasInvalidScope(AttributeData attribute, out TargetScope targetScope) + { + if (!TryGetNamedArgument(attribute, SuppressMessageScope, out var scopeString) || + string.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(AttributeData attribute, TargetScope targetScope) + { + if (targetScope == TargetScope.Resource) + { + // Legacy scope which we do not handle. + return false; + } + + if (!TryGetNamedArgument(attribute, 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 static bool TryGetNamedArgument(AttributeData attribute, string argumentName, out string? argumentValue) + { + foreach (var (name, value) in attribute.NamedArguments) + { + if (argumentName.Equals(name, StringComparison.OrdinalIgnoreCase) && + value.Kind == TypedConstantKind.Primitive && + value.Value is string stringValue) + { + argumentValue = stringValue; + return true; + } + } + + argumentValue = null; + return false; + } + } +} 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..8950b54b5eafc --- /dev/null +++ b/src/Analyzers/Core/CodeFixes/RemoveUnnecessarySuppressions/RemoveUnnecessarySuppressionsCodeFixProvider.cs @@ -0,0 +1,66 @@ +// 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) + { + context.RegisterCodeFix( + new MyCodeAction( + CodeFixesResources.Remove_redundant_suppression, + c => FixAsync(context.Document, context.Diagnostics[0], c)), + context.Diagnostics); + + 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(string title, Func> createChangedDocument) + : base(title, createChangedDocument, equivalenceKey: title) + { + } + } + } +} diff --git a/src/Analyzers/VisualBasic/Analyzers/RemoveUnnecessarySuppressions/VisualBasicRemoveUnnecessarySuppressionsDiagnosticAnalyzer.vb b/src/Analyzers/VisualBasic/Analyzers/RemoveUnnecessarySuppressions/VisualBasicRemoveUnnecessarySuppressionsDiagnosticAnalyzer.vb new file mode 100644 index 0000000000000..d34b69696ff64 --- /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, AddressOf syntaxContext.ReportDiagnostic) + 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/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/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"; From bae73c807fbc0c63c55a23cb59571b6dc077cab4 Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Fri, 15 May 2020 08:07:19 -0700 Subject: [PATCH 2/7] xlf file changes --- .../Core/Analyzers/xlf/AnalyzersResources.cs.xlf | 15 +++++++++++++++ .../Core/Analyzers/xlf/AnalyzersResources.de.xlf | 15 +++++++++++++++ .../Core/Analyzers/xlf/AnalyzersResources.es.xlf | 15 +++++++++++++++ .../Core/Analyzers/xlf/AnalyzersResources.fr.xlf | 15 +++++++++++++++ .../Core/Analyzers/xlf/AnalyzersResources.it.xlf | 15 +++++++++++++++ .../Core/Analyzers/xlf/AnalyzersResources.ja.xlf | 15 +++++++++++++++ .../Core/Analyzers/xlf/AnalyzersResources.ko.xlf | 15 +++++++++++++++ .../Core/Analyzers/xlf/AnalyzersResources.pl.xlf | 15 +++++++++++++++ .../Analyzers/xlf/AnalyzersResources.pt-BR.xlf | 15 +++++++++++++++ .../Core/Analyzers/xlf/AnalyzersResources.ru.xlf | 15 +++++++++++++++ .../Core/Analyzers/xlf/AnalyzersResources.tr.xlf | 15 +++++++++++++++ .../Analyzers/xlf/AnalyzersResources.zh-Hans.xlf | 15 +++++++++++++++ .../Analyzers/xlf/AnalyzersResources.zh-Hant.xlf | 15 +++++++++++++++ .../Core/CodeFixes/xlf/CodeFixesResources.cs.xlf | 5 +++++ .../Core/CodeFixes/xlf/CodeFixesResources.de.xlf | 5 +++++ .../Core/CodeFixes/xlf/CodeFixesResources.es.xlf | 5 +++++ .../Core/CodeFixes/xlf/CodeFixesResources.fr.xlf | 5 +++++ .../Core/CodeFixes/xlf/CodeFixesResources.it.xlf | 5 +++++ .../Core/CodeFixes/xlf/CodeFixesResources.ja.xlf | 5 +++++ .../Core/CodeFixes/xlf/CodeFixesResources.ko.xlf | 5 +++++ .../Core/CodeFixes/xlf/CodeFixesResources.pl.xlf | 5 +++++ .../CodeFixes/xlf/CodeFixesResources.pt-BR.xlf | 5 +++++ .../Core/CodeFixes/xlf/CodeFixesResources.ru.xlf | 5 +++++ .../Core/CodeFixes/xlf/CodeFixesResources.tr.xlf | 5 +++++ .../CodeFixes/xlf/CodeFixesResources.zh-Hans.xlf | 5 +++++ .../CodeFixes/xlf/CodeFixesResources.zh-Hant.xlf | 5 +++++ 26 files changed, 260 insertions(+) diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.cs.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.cs.xlf index d9118964f3990..7085bcfae8a83 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.cs.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.cs.xlf @@ -97,6 +97,21 @@ Invalid format string + + 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 Make field readonly diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.de.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.de.xlf index 8467b3fd978e9..23ff1897c4983 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.de.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.de.xlf @@ -97,6 +97,21 @@ Invalid format string + + 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 Make field readonly diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.es.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.es.xlf index 3876930c70688..80103ca4d76b9 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.es.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.es.xlf @@ -97,6 +97,21 @@ Invalid format string + + 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 Make field readonly diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.fr.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.fr.xlf index 44b3612a476eb..37617141359ae 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.fr.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.fr.xlf @@ -97,6 +97,21 @@ Invalid format string + + 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 Make field readonly diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.it.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.it.xlf index 5ad565a27af51..a2e7aedcda65e 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.it.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.it.xlf @@ -97,6 +97,21 @@ Invalid format string + + 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 Make field readonly diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ja.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ja.xlf index 5962934627a76..a008c97b766f3 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ja.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ja.xlf @@ -97,6 +97,21 @@ Invalid format string + + 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 Make field readonly diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ko.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ko.xlf index 6a6ebafe8d106..eb65163a496e3 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ko.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ko.xlf @@ -97,6 +97,21 @@ Invalid format string + + 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 Make field readonly diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pl.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pl.xlf index a7d1baf063a57..4433e0b536aba 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pl.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pl.xlf @@ -97,6 +97,21 @@ Invalid format string + + 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 Make field readonly diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pt-BR.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pt-BR.xlf index 925fb23a07766..f12bf87293bb7 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 @@ Invalid format string + + 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 Make field readonly diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ru.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ru.xlf index 0d61a3ea9ea79..4ba746a9055b3 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ru.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ru.xlf @@ -97,6 +97,21 @@ Invalid format string + + 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 Make field readonly diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.tr.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.tr.xlf index 3579addc2288c..725c1dd59a162 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.tr.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.tr.xlf @@ -97,6 +97,21 @@ Invalid format string + + 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 Make field readonly diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.zh-Hans.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.zh-Hans.xlf index d3976a66d4ec8..42038d3406c4d 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 format string + + 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 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 c5eec79c6215a..b9cbdcbe08d8d 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 format string + + 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 Make field readonly diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.cs.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.cs.xlf index 6f11d37b4cbc5..7202a596691ce 100644 --- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.cs.xlf +++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.cs.xlf @@ -27,6 +27,11 @@ Remove redundant assignment + + Remove redundant suppression + Remove redundant suppression + + Use discard '_' Use discard '_' diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.de.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.de.xlf index 3701701ef386c..d278a4de85c3d 100644 --- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.de.xlf +++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.de.xlf @@ -27,6 +27,11 @@ Remove redundant assignment + + Remove redundant suppression + Remove redundant suppression + + Use discard '_' Use discard '_' diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.es.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.es.xlf index 4f1e197f941ee..d2abae7c8538c 100644 --- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.es.xlf +++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.es.xlf @@ -27,6 +27,11 @@ Remove redundant assignment + + Remove redundant suppression + Remove redundant suppression + + Use discard '_' Use discard '_' diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.fr.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.fr.xlf index e0db753ea478d..15e4e23dd36de 100644 --- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.fr.xlf +++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.fr.xlf @@ -27,6 +27,11 @@ Remove redundant assignment + + Remove redundant suppression + Remove redundant suppression + + Use discard '_' Use discard '_' diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.it.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.it.xlf index 7fa97dc3cdb9d..39394d88d9fbd 100644 --- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.it.xlf +++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.it.xlf @@ -27,6 +27,11 @@ Remove redundant assignment + + Remove redundant suppression + Remove redundant suppression + + Use discard '_' Use discard '_' diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ja.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ja.xlf index c8d40f1d0bfdf..b522d02360cb0 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 assignment + + Remove redundant suppression + Remove redundant suppression + + Use discard '_' Use discard '_' diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ko.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ko.xlf index d8c4614b97a72..507726a1a5b12 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 assignment + + Remove redundant suppression + Remove redundant suppression + + Use discard '_' Use discard '_' diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.pl.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.pl.xlf index c50ddde355a6c..4639c7004ccdf 100644 --- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.pl.xlf +++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.pl.xlf @@ -27,6 +27,11 @@ Remove redundant assignment + + Remove redundant suppression + Remove redundant suppression + + Use discard '_' Use discard '_' diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.pt-BR.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.pt-BR.xlf index c8e97cbc123b7..b4de6b43fd66e 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 @@ Remove redundant assignment + + Remove redundant suppression + Remove redundant suppression + + Use discard '_' Use discard '_' diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ru.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.ru.xlf index 2590540d64b78..b0c4d77b26fc0 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 assignment + + Remove redundant suppression + Remove redundant suppression + + Use discard '_' Use discard '_' diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.tr.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.tr.xlf index 000354d3a2b99..659e24b57f147 100644 --- a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.tr.xlf +++ b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.tr.xlf @@ -27,6 +27,11 @@ Remove redundant assignment + + Remove redundant suppression + Remove redundant suppression + + Use discard '_' Use discard '_' diff --git a/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.zh-Hans.xlf b/src/Analyzers/Core/CodeFixes/xlf/CodeFixesResources.zh-Hans.xlf index 45129d23f1da2..85d3e56e7af80 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 assignment + + Remove redundant suppression + Remove redundant suppression + + Use discard '_' 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 990af55cfab20..caca14adbd56e 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 assignment + + Remove redundant suppression + Remove redundant suppression + + Use discard '_' Use discard '_' From 08bb6c39728b6e0ff3f8476ad5fa8d4659a9b452 Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Fri, 15 May 2020 10:26:36 -0700 Subject: [PATCH 3/7] Add the new diagnostic ID to IDE diagnostic configuration tests --- .../Diagnostics/IDEDiagnosticIDConfigurationTests.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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 From b9956091c8c7283207a16ba40fcf35611264944d Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Mon, 18 May 2020 09:52:37 -0700 Subject: [PATCH 4/7] Use an IOperation based approach for computing attribute named arguments --- ...necessarySuppressionsDiagnosticAnalyzer.cs | 2 +- ...necessarySuppressionsDiagnosticAnalyzer.cs | 10 +-- .../SuppressMessageAttributeState.cs | 65 ++++++++++--------- ...necessarySuppressionsDiagnosticAnalyzer.vb | 2 +- 4 files changed, 41 insertions(+), 38 deletions(-) diff --git a/src/Analyzers/CSharp/Analyzers/RemoveUnnecessarySuppressions/CSharpRemoveUnnecessarySuppressionsDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/RemoveUnnecessarySuppressions/CSharpRemoveUnnecessarySuppressionsDiagnosticAnalyzer.cs index 0f3c47d64d1af..c883b8c69c96c 100644 --- a/src/Analyzers/CSharp/Analyzers/RemoveUnnecessarySuppressions/CSharpRemoveUnnecessarySuppressionsDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/RemoveUnnecessarySuppressions/CSharpRemoveUnnecessarySuppressionsDiagnosticAnalyzer.cs @@ -25,7 +25,7 @@ protected override void RegisterAttributeSyntaxAction(CompilationStartAnalysisCo case SyntaxKind.ModuleKeyword: foreach (var attribute in attributeList.Attributes) { - compilationAnalyzer.AnalyzeAssemblyOrModuleAttribute(attribute, context.ReportDiagnostic); + compilationAnalyzer.AnalyzeAssemblyOrModuleAttribute(attribute, context.SemanticModel, context.ReportDiagnostic, context.CancellationToken); } break; diff --git a/src/Analyzers/Core/Analyzers/RemoveUnnecessarySuppressions/AbstractRemoveUnnecessarySuppressionsDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/RemoveUnnecessarySuppressions/AbstractRemoveUnnecessarySuppressionsDiagnosticAnalyzer.cs index a25fdbc9e9db5..b8d7e3f15f832 100644 --- a/src/Analyzers/Core/Analyzers/RemoveUnnecessarySuppressions/AbstractRemoveUnnecessarySuppressionsDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/RemoveUnnecessarySuppressions/AbstractRemoveUnnecessarySuppressionsDiagnosticAnalyzer.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Immutable; +using System.Threading; using Microsoft.CodeAnalysis.CodeQuality; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -58,20 +59,19 @@ public CompilationAnalyzer(Compilation compilation, INamedTypeSymbol suppressMes _state = new SuppressMessageAttributeState(compilation, suppressMessageAttributeType); } - public void AnalyzeAssemblyOrModuleAttribute(SyntaxNode attributeSyntax, Action reportDiagnostic) + public void AnalyzeAssemblyOrModuleAttribute(SyntaxNode attributeSyntax, SemanticModel model, Action reportDiagnostic, CancellationToken cancellationToken) { - if (!_state.IsGlobalSuppressMessageAttribute(attributeSyntax, out var attribute) || - attribute.NamedArguments.IsEmpty) + if (!_state.IsSuppressMessageAttributeWithNamedArguments(attributeSyntax, model, cancellationToken, out var namedAttributeArguments)) { return; } DiagnosticDescriptor rule; - if (_state.HasInvalidScope(attribute, out var targetScope)) + if (_state.HasInvalidScope(namedAttributeArguments, out var targetScope)) { rule = s_invalidScopeDescriptor; } - else if (_state.HasInvalidOrMissingTarget(attribute, targetScope)) + else if (_state.HasInvalidOrMissingTarget(namedAttributeArguments, targetScope)) { rule = s_invalidOrMissingTargetDescriptor; } diff --git a/src/Analyzers/Core/Analyzers/RemoveUnnecessarySuppressions/SuppressMessageAttributeState.cs b/src/Analyzers/Core/Analyzers/RemoveUnnecessarySuppressions/SuppressMessageAttributeState.cs index 78a1dc592943b..f51939600d7af 100644 --- a/src/Analyzers/Core/Analyzers/RemoveUnnecessarySuppressions/SuppressMessageAttributeState.cs +++ b/src/Analyzers/Core/Analyzers/RemoveUnnecessarySuppressions/SuppressMessageAttributeState.cs @@ -7,7 +7,8 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; +using System.Threading; +using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.PooledObjects; #if NETSTANDARD2_0 @@ -26,13 +27,11 @@ internal partial class SuppressMessageAttributeState private readonly Compilation _compilation; private readonly INamedTypeSymbol _suppressMessageAttributeType; - private readonly Lazy> _lazySuppressMessageAttributesBySyntax; public SuppressMessageAttributeState(Compilation compilation, INamedTypeSymbol suppressMessageAttributeType) { _compilation = compilation; _suppressMessageAttributeType = suppressMessageAttributeType; - _lazySuppressMessageAttributesBySyntax = new Lazy>(CreateAttributesBySyntaxMap); } private static ImmutableDictionary CreateTargetScopesMap() @@ -54,43 +53,47 @@ private static ImmutableDictionary CreateTargetScopesMap() return builder.ToImmutable(); } - private ImmutableDictionary CreateAttributesBySyntaxMap() + public bool IsSuppressMessageAttributeWithNamedArguments( + SyntaxNode attributeSyntax, + SemanticModel model, + CancellationToken cancellationToken, + out ImmutableArray<(string name, IOperation value)> namedAttributeArguments) { - var builder = ImmutableDictionary.CreateBuilder(); - AddAttributes(_compilation.Assembly, _suppressMessageAttributeType, builder); - foreach (var module in _compilation.Assembly.Modules) + var attribute = model.GetOperation(attributeSyntax, cancellationToken); + if (attribute == null) { - AddAttributes(module, _suppressMessageAttributeType, builder); + namedAttributeArguments = ImmutableArray<(string name, IOperation value)>.Empty; + return false; } - return builder.ToImmutable(); - - // Local functions. - static void AddAttributes(ISymbol symbol, INamedTypeSymbol suppressMessageAttributeType, ImmutableDictionary.Builder builder) + // 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) { - foreach (var attribute in symbol.GetAttributes()) + if (childOperation is ISimpleAssignmentOperation simpleAssignment && + simpleAssignment.Target is IPropertyReferenceOperation propertyReference && + _suppressMessageAttributeType.Equals(propertyReference.Property.ContainingType)) { - if (suppressMessageAttributeType.Equals(attribute.AttributeClass) && - attribute.ApplicationSyntaxReference?.GetSyntax() is SyntaxNode node) - { - builder.Add(node, attribute); - } + builder.Add((propertyReference.Property.Name, simpleAssignment.Value)); } } - } - public bool IsGlobalSuppressMessageAttribute(SyntaxNode attributeSyntax, [NotNullWhen(returnValue: true)] out AttributeData? attribute) - => _lazySuppressMessageAttributesBySyntax.Value.TryGetValue(attributeSyntax, out attribute); + namedAttributeArguments = builder.ToImmutable(); + return namedAttributeArguments.Length > 0; + } - public bool HasInvalidScope(AttributeData attribute, out TargetScope targetScope) + public bool HasInvalidScope(ImmutableArray<(string name, IOperation value)> namedAttributeArguments, out TargetScope targetScope) { - if (!TryGetNamedArgument(attribute, SuppressMessageScope, out var scopeString) || + if (!TryGetNamedArgument(namedAttributeArguments, SuppressMessageScope, out var scopeString) || string.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)) + else if (!s_targetScopesMap.TryGetValue(scopeString, out targetScope)) { targetScope = TargetScope.None; return true; @@ -99,7 +102,7 @@ public bool HasInvalidScope(AttributeData attribute, out TargetScope targetScope return false; } - public bool HasInvalidOrMissingTarget(AttributeData attribute, TargetScope targetScope) + public bool HasInvalidOrMissingTarget(ImmutableArray<(string name, IOperation value)> namedAttributeArguments, TargetScope targetScope) { if (targetScope == TargetScope.Resource) { @@ -107,7 +110,7 @@ public bool HasInvalidOrMissingTarget(AttributeData attribute, TargetScope targe return false; } - if (!TryGetNamedArgument(attribute, SuppressMessageTarget, out var targetSymbolString)) + if (!TryGetNamedArgument(namedAttributeArguments, SuppressMessageTarget, out var targetSymbolString)) { targetSymbolString = null; } @@ -132,13 +135,13 @@ public bool HasInvalidOrMissingTarget(AttributeData attribute, TargetScope targe } } - private static bool TryGetNamedArgument(AttributeData attribute, string argumentName, out string? argumentValue) + private bool TryGetNamedArgument(ImmutableArray<(string name, IOperation value)> namedAttributeArguments, string argumentName, out string? argumentValue) { - foreach (var (name, value) in attribute.NamedArguments) + foreach (var (name, value) in namedAttributeArguments) { - if (argumentName.Equals(name, StringComparison.OrdinalIgnoreCase) && - value.Kind == TypedConstantKind.Primitive && - value.Value is string stringValue) + if (name == argumentName && + value.ConstantValue.HasValue && + value.ConstantValue.Value is string stringValue) { argumentValue = stringValue; return true; diff --git a/src/Analyzers/VisualBasic/Analyzers/RemoveUnnecessarySuppressions/VisualBasicRemoveUnnecessarySuppressionsDiagnosticAnalyzer.vb b/src/Analyzers/VisualBasic/Analyzers/RemoveUnnecessarySuppressions/VisualBasicRemoveUnnecessarySuppressionsDiagnosticAnalyzer.vb index d34b69696ff64..cdb222679a7da 100644 --- a/src/Analyzers/VisualBasic/Analyzers/RemoveUnnecessarySuppressions/VisualBasicRemoveUnnecessarySuppressionsDiagnosticAnalyzer.vb +++ b/src/Analyzers/VisualBasic/Analyzers/RemoveUnnecessarySuppressions/VisualBasicRemoveUnnecessarySuppressionsDiagnosticAnalyzer.vb @@ -18,7 +18,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.RemoveUnnecessarySuppressions Dim attribute = DirectCast(syntaxContext.Node, AttributeSyntax) Select Case attribute.Target?.AttributeModifier.Kind() Case SyntaxKind.AssemblyKeyword, SyntaxKind.ModuleKeyword - compilationAnalyzer.AnalyzeAssemblyOrModuleAttribute(attribute, AddressOf syntaxContext.ReportDiagnostic) + compilationAnalyzer.AnalyzeAssemblyOrModuleAttribute(attribute, syntaxContext.SemanticModel, AddressOf syntaxContext.ReportDiagnostic, syntaxContext.CancellationToken) End Select End Sub, SyntaxKind.Attribute) From 1cc9e03cde071a08e07b24c2cb18f7bc6755d24f Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Mon, 18 May 2020 09:56:53 -0700 Subject: [PATCH 5/7] Feedback for code fix provider --- ...emoveUnnecessarySuppressionsCodeFixProvider.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Analyzers/Core/CodeFixes/RemoveUnnecessarySuppressions/RemoveUnnecessarySuppressionsCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/RemoveUnnecessarySuppressions/RemoveUnnecessarySuppressionsCodeFixProvider.cs index 8950b54b5eafc..d593c687a6ecc 100644 --- a/src/Analyzers/Core/CodeFixes/RemoveUnnecessarySuppressions/RemoveUnnecessarySuppressionsCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/RemoveUnnecessarySuppressions/RemoveUnnecessarySuppressionsCodeFixProvider.cs @@ -32,11 +32,12 @@ internal override CodeFixCategory CodeFixCategory public override Task RegisterCodeFixesAsync(CodeFixContext context) { - context.RegisterCodeFix( - new MyCodeAction( - CodeFixesResources.Remove_redundant_suppression, - c => FixAsync(context.Document, context.Diagnostics[0], c)), - context.Diagnostics); + foreach (var diagnostic in context.Diagnostics) + { + context.RegisterCodeFix( + new MyCodeAction(c => FixAsync(context.Document, diagnostic, c)), + diagnostic); + } return Task.CompletedTask; } @@ -57,8 +58,8 @@ protected override Task FixAllAsync(Document document, ImmutableArray> createChangedDocument) - : base(title, createChangedDocument, equivalenceKey: title) + public MyCodeAction(Func> createChangedDocument) + : base(CodeFixesResources.Remove_redundant_suppression, createChangedDocument, nameof(RemoveUnnecessarySuppressionsCodeFixProvider)) { } } From daa1d2ccbac251927f0c4f2d901e3464e1a0bae2 Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Mon, 18 May 2020 10:02:32 -0700 Subject: [PATCH 6/7] Fix build warnings --- .../SuppressMessageAttributeState.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Analyzers/Core/Analyzers/RemoveUnnecessarySuppressions/SuppressMessageAttributeState.cs b/src/Analyzers/Core/Analyzers/RemoveUnnecessarySuppressions/SuppressMessageAttributeState.cs index f51939600d7af..991319011c48e 100644 --- a/src/Analyzers/Core/Analyzers/RemoveUnnecessarySuppressions/SuppressMessageAttributeState.cs +++ b/src/Analyzers/Core/Analyzers/RemoveUnnecessarySuppressions/SuppressMessageAttributeState.cs @@ -10,10 +10,7 @@ using System.Threading; using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.PooledObjects; - -#if NETSTANDARD2_0 using Roslyn.Utilities; -#endif namespace Microsoft.CodeAnalysis.Diagnostics { @@ -88,7 +85,7 @@ simpleAssignment.Target is IPropertyReferenceOperation propertyReference && public bool HasInvalidScope(ImmutableArray<(string name, IOperation value)> namedAttributeArguments, out TargetScope targetScope) { if (!TryGetNamedArgument(namedAttributeArguments, SuppressMessageScope, out var scopeString) || - string.IsNullOrEmpty(scopeString)) + RoslynString.IsNullOrEmpty(scopeString)) { // Missing/Null/Empty scope values are treated equivalent to a compilation wide suppression. targetScope = TargetScope.Module; From 0246be6c7e58d4693458cb0cc007cb77abce73f9 Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Mon, 18 May 2020 13:26:55 -0700 Subject: [PATCH 7/7] Enable global editor configs: (#43889) * Enable global editor configs: - Allow an editorconfig to be a global config via "is_global = true" - Add a filter builder that reads a set of analyzer configs, filters out the global ones and returns a single merged global config - Only match global sections based on full file path - Issue a warning when two global configs set the same key, and remove the duplicate key - Allow multiple global configs or an editorconfig and global config to exist in the same dir - Add tests --- .../Test/CommandLine/CommandLineTests.cs | 74 +++ .../Analyzers/AnalyzerConfigTests.cs | 552 ++++++++++++++++++ .../Core/Portable/CodeAnalysisResources.resx | 6 + .../Portable/CommandLine/AnalyzerConfig.cs | 16 + .../Portable/CommandLine/AnalyzerConfigSet.cs | 239 +++++++- .../Portable/CommandLine/CommonCompiler.cs | 23 +- .../Core/Portable/PublicAPI.Unshipped.txt | 1 + .../Portable/xlf/CodeAnalysisResources.cs.xlf | 10 + .../Portable/xlf/CodeAnalysisResources.de.xlf | 10 + .../Portable/xlf/CodeAnalysisResources.es.xlf | 10 + .../Portable/xlf/CodeAnalysisResources.fr.xlf | 10 + .../Portable/xlf/CodeAnalysisResources.it.xlf | 10 + .../Portable/xlf/CodeAnalysisResources.ja.xlf | 10 + .../Portable/xlf/CodeAnalysisResources.ko.xlf | 10 + .../Portable/xlf/CodeAnalysisResources.pl.xlf | 10 + .../xlf/CodeAnalysisResources.pt-BR.xlf | 10 + .../Portable/xlf/CodeAnalysisResources.ru.xlf | 10 + .../Portable/xlf/CodeAnalysisResources.tr.xlf | 10 + .../xlf/CodeAnalysisResources.zh-Hans.xlf | 10 + .../xlf/CodeAnalysisResources.zh-Hant.xlf | 10 + 20 files changed, 1029 insertions(+), 12 deletions(-) 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/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 257c65e3664c8..8281fc9fa060e 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 7d7daa88fe6c1..7ad7c0208e905 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 e41789a981bd9..aaa0bd9c900fb 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 5c55eebadf3c1..967abe3754917 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 8e22fc4224b5d..92f519c99fc70 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 b38c3ae72913f..b4a60dd824692 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 9e5a87b0dc210..fd35a53596214 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 a412cdcb6f7e9..a40ffde30897d 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 5bf7bf6688feb..33d0006947979 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 bbae981ac5e85..ffb693846666f 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 6b6c36c428852..0c5a9a8610dab 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 5057780d4c66b..bd7b64c191bae 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 3b684e244c870..164cc619724e6 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 身分識別目標