diff --git a/build/Targets/VSL.Settings.targets b/build/Targets/VSL.Settings.targets
index e6a1a4aa9020f69d4ba09f391b1ed31f8348a886..41143b3d235772dc9f673ce55886d787f66f4130 100644
--- a/build/Targets/VSL.Settings.targets
+++ b/build/Targets/VSL.Settings.targets
@@ -15,7 +15,7 @@
Microsoft.Net.Compilers
$(NuGetPackageRoot)\Microsoft.Net.Compilers\1.1.0-beta1-20150727-01\build\Microsoft.Net.Compilers.props
- $(NuGetPackageRoot)\Microsoft.Net.RoslynDiagnostics\1.1.1-beta1-20150814-01\build\Microsoft.Net.RoslynDiagnostics.props
+ $(NuGetPackageRoot)\Microsoft.Net.RoslynDiagnostics\1.1.1-beta1-20150818-01\build\Microsoft.Net.RoslynDiagnostics.props
$(AdditionalFileItemNames);PublicAPI
diff --git a/build/ToolsetPackages/project.json b/build/ToolsetPackages/project.json
index 604fc211f9b43371d0d0ec18eb168c88b9ed0d4e..150620299a5009a7970d9c646c6e2dce576df925 100644
--- a/build/ToolsetPackages/project.json
+++ b/build/ToolsetPackages/project.json
@@ -4,7 +4,7 @@
"Microsoft.CodeAnalysis.Test.Resources.Proprietary": "1.1.0-beta1-20150817-01",
"Microsoft.DiaSymReader.Native": "1.1.0-alpha2",
"Microsoft.Net.Compilers": "1.1.0-beta1-20150727-01",
- "Microsoft.Net.RoslynDiagnostics": "1.1.1-beta1-20150814-01",
+ "Microsoft.Net.RoslynDiagnostics": "1.1.1-beta1-20150818-01",
"FakeSign": "0.9.2",
"xunit": "1.9.2",
"xunit.runner.console": "2.1.0-beta4-build3109",
diff --git a/build/ToolsetPackages/project.lock.json b/build/ToolsetPackages/project.lock.json
index 0d1513b9f2fe762faeac03653139699f29003a88..a57bd6f82982f40cd86856f272f2571562dbd245 100644
--- a/build/ToolsetPackages/project.lock.json
+++ b/build/ToolsetPackages/project.lock.json
@@ -31,7 +31,7 @@
},
"Microsoft.DiaSymReader.Native/1.1.0-alpha2": {},
"Microsoft.Net.Compilers/1.1.0-beta1-20150727-01": {},
- "Microsoft.Net.RoslynDiagnostics/1.1.1-beta1-20150814-01": {},
+ "Microsoft.Net.RoslynDiagnostics/1.1.1-beta1-20150818-01": {},
"Microsoft.Tpl.Dataflow/4.5.24": {
"compile": {
"lib/portable-net45+win8+wpa81/System.Threading.Tasks.Dataflow.dll": {}
@@ -85,7 +85,7 @@
}
},
"Microsoft.Net.Compilers/1.1.0-beta1-20150727-01": {},
- "Microsoft.Net.RoslynDiagnostics/1.1.1-beta1-20150814-01": {},
+ "Microsoft.Net.RoslynDiagnostics/1.1.1-beta1-20150818-01": {},
"Microsoft.Tpl.Dataflow/4.5.24": {
"compile": {
"lib/portable-net45+win8+wpa81/System.Threading.Tasks.Dataflow.dll": {}
@@ -1036,15 +1036,15 @@
"tools/VBCSCompiler.exe.config"
]
},
- "Microsoft.Net.RoslynDiagnostics/1.1.1-beta1-20150814-01": {
- "sha512": "ZsPiNo4TIlamP3VDJ7f+PzfLM2MwlQPW29Lm4gNn7j3EZvS2wu5RapJe6Edpz2OuFyZSq0U9NvNHmgM9I7W9Og==",
+ "Microsoft.Net.RoslynDiagnostics/1.1.1-beta1-20150818-01": {
+ "sha512": "gKMS7pSOjXejP+S3+pAIShKYOyfneRRZGowDiEGZDHPUZqaof7ccmFkXxWMyTy2hV00exSBqNeRZDL4IpJxuCQ==",
"type": "Package",
"files": [
"[Content_Types].xml",
"_rels/.rels",
"build/Microsoft.Net.RoslynDiagnostics.props",
"Microsoft.Net.RoslynDiagnostics.nuspec",
- "package/services/metadata/core-properties/e01abf88844b465fabea2ac7ad8c8ffb.psmdcp",
+ "package/services/metadata/core-properties/433dcdf8a06f4dcb806e4f7f93ed8bf2.psmdcp",
"ThirdPartyNotices.rtf",
"tools/Roslyn.Diagnostics.Analyzers.CSharp.dll",
"tools/Roslyn.Diagnostics.Analyzers.dll",
@@ -1130,7 +1130,7 @@
"Microsoft.CodeAnalysis.Test.Resources.Proprietary >= 1.1.0-beta1-20150817-01",
"Microsoft.DiaSymReader.Native >= 1.1.0-alpha2",
"Microsoft.Net.Compilers >= 1.1.0-beta1-20150727-01",
- "Microsoft.Net.RoslynDiagnostics >= 1.1.1-beta1-20150814-01",
+ "Microsoft.Net.RoslynDiagnostics >= 1.1.1-beta1-20150818-01",
"xunit >= 1.9.2",
"xunit.runner.console >= 2.1.0-beta4-build3109",
"xunit.runners >= 2.0.0-alpha-build2576"
diff --git a/cibuild.sh b/cibuild.sh
index e1f7679dc35defcf209c8040eb8d38367aa7c764..ecb20178dc1fa4102e200f88598377c75dfa5de5 100755
--- a/cibuild.sh
+++ b/cibuild.sh
@@ -79,7 +79,7 @@ restore_nuget()
{
acquire_sem_or_wait "restore_nuget"
- local package_name="nuget.14.zip"
+ local package_name="nuget.15.zip"
local target="/tmp/$package_name"
echo "Installing NuGet Packages $target"
if [ -f $target ]; then
diff --git a/docs/infrastructure/mono-toolset.md b/docs/infrastructure/mono-toolset.md
index d33255b77b0cf423b4916b82eeff269317716ddb..676929923ce207700658435b5d1ba3a8778ed3af 100644
--- a/docs/infrastructure/mono-toolset.md
+++ b/docs/infrastructure/mono-toolset.md
@@ -34,9 +34,11 @@ The cross platform restore works by downloading the contents of the packages dir
This is done by executing the following on a Windows box.
- Change to the root of the enlistment.
- - Delete the contents of the package directory.
- - Run .nuget/NuGetRestore.ps1
- - Zip the packages directory (via explorer) and name it nuget.X.zip (where X is one higher than the previous number)
+ - delete the contents of the `~\.nuget\packages`
+ - Run
+ - `.\nuget.exe restore Roslyn.sln`
+ - `.\nuget.exe restore build\ToolsetPackages\project.json`
+ - Zip the `~\.nuget` directory (via explorer) and name it nuget.X.zip (where X is one higher than the previous number)
- Use [azcopy](https://azure.microsoft.com/en-us/documentation/articles/storage-use-azcopy) to upload to https://dotnetci.blob.core.windows.net/roslyn
- Change cibuild.sh to reference the new package.
diff --git a/src/Compilers/CSharp/Portable/Lowering/DiagnosticsPass_ExpressionTrees.cs b/src/Compilers/CSharp/Portable/Lowering/DiagnosticsPass_ExpressionTrees.cs
index 587da1caf8c886d228a7fdcb67eb7e27be52a659..0f17d60b6b7ad29e424e86faee2477402829effd 100644
--- a/src/Compilers/CSharp/Portable/Lowering/DiagnosticsPass_ExpressionTrees.cs
+++ b/src/Compilers/CSharp/Portable/Lowering/DiagnosticsPass_ExpressionTrees.cs
@@ -123,7 +123,8 @@ private static Symbol GetLocalOrParameterSymbol(BoundExpression expr)
public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node)
{
- if (!CheckForAssignmentToSelf(node) && _inExpressionLambda && node.Left.Kind != BoundKind.ObjectInitializerMember && node.Left.Kind != BoundKind.DynamicObjectInitializerMember)
+ CheckForAssignmentToSelf(node);
+ if (_inExpressionLambda && node.Left.Kind != BoundKind.ObjectInitializerMember && node.Left.Kind != BoundKind.DynamicObjectInitializerMember)
{
Error(ErrorCode.ERR_ExpressionTreeContainsAssignment, node);
}
diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs
index e68d5e4bbdc123750ff0a90bbe9285a9d5d57a15..b05477e96342794b0302ab4ab2be716e07f3732a 100644
--- a/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs
+++ b/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs
@@ -1295,5 +1295,30 @@ static void stuff()
Assert.Equal("Program a", symbolInfo.Symbol.ToTestDisplayString());
}
+
+ [Fact]
+ [WorkItem(3826, "https://github.com/dotnet/roslyn/issues/3826")]
+ public void ExpressionTreeSelfAssignmentShouldError()
+ {
+ var source = @"
+using System;
+using System.Linq.Expressions;
+
+class Program
+{
+ static void Main()
+ {
+ Expression> x = y => y = y;
+ }
+}";
+ var compilation = CreateCompilationWithMscorlibAndSystemCore(source);
+ compilation.VerifyDiagnostics(
+ // (9,45): warning CS1717: Assignment made to same variable; did you mean to assign something else?
+ // Expression> x = y => y = y;
+ Diagnostic(ErrorCode.WRN_AssignmentToSelf, "y = y").WithLocation(9, 45),
+ // (9,45): error CS0832: An expression tree may not contain an assignment operator
+ // Expression> x = y => y = y;
+ Diagnostic(ErrorCode.ERR_ExpressionTreeContainsAssignment, "y = y").WithLocation(9, 45));
+ }
}
}
diff --git a/src/Compilers/Core/Portable/CodeAnalysis.csproj b/src/Compilers/Core/Portable/CodeAnalysis.csproj
index b6afdd8e49bc2b1643d961cf5b84daf2b7ddd924..8b60c8eb7d6245224cc8b8b0db162ec16412c605 100644
--- a/src/Compilers/Core/Portable/CodeAnalysis.csproj
+++ b/src/Compilers/Core/Portable/CodeAnalysis.csproj
@@ -52,6 +52,7 @@
+
diff --git a/src/Compilers/Core/Portable/Diagnostic/Diagnostic.cs b/src/Compilers/Core/Portable/Diagnostic/Diagnostic.cs
index 582b9b90039e12f40f54c7b3de8cb819ef444db4..8f4e0be8f557ecb66d7164c358a8398124a5425d 100644
--- a/src/Compilers/Core/Portable/Diagnostic/Diagnostic.cs
+++ b/src/Compilers/Core/Portable/Diagnostic/Diagnostic.cs
@@ -283,6 +283,26 @@ internal static Diagnostic Create(DiagnosticInfo info)
///
public abstract bool IsSuppressed { get; }
+ ///
+ /// Gets the for suppressed diagnostics, i.e. = true.
+ /// Otherwise, returns null.
+ ///
+ public SuppressionInfo GetSuppressionInfo(Compilation compilation)
+ {
+ if (!IsSuppressed)
+ {
+ return null;
+ }
+
+ AttributeData attribute;
+ if (!SuppressMessageAttributeState.IsDiagnosticSuppressed(this, compilation, out attribute))
+ {
+ attribute = null;
+ }
+
+ return new SuppressionInfo(this.Id, attribute);
+ }
+
///
/// Returns true if this diagnostic is enabled by default by the author of the diagnostic.
///
diff --git a/src/Compilers/Core/Portable/Diagnostic/SuppressionInfo.cs b/src/Compilers/Core/Portable/Diagnostic/SuppressionInfo.cs
new file mode 100644
index 0000000000000000000000000000000000000000..94b717a2ec18ae8a94b6c21e71a78f80f426da89
--- /dev/null
+++ b/src/Compilers/Core/Portable/Diagnostic/SuppressionInfo.cs
@@ -0,0 +1,27 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.CodeAnalysis.Diagnostics
+{
+ ///
+ /// Contains information about the source of diagnostic suppression.
+ ///
+ public class SuppressionInfo
+ {
+ ///
+ /// of the suppressed diagnostic.
+ ///
+ public string Id { get; }
+
+ ///
+ /// If the diagnostic was suppressed by an attribute, then returns that attribute.
+ /// Otherwise, returns null.
+ ///
+ public AttributeData Attribute { get; }
+
+ internal SuppressionInfo(string id, AttributeData attribute)
+ {
+ Id = id;
+ Attribute = attribute;
+ }
+ }
+}
diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/SuppressMessageAttributeState.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/SuppressMessageAttributeState.cs
index 60c605999d97f0fca2eca7d7a3db027f7ef5278f..49b07e515c57165d8cbf8b74589c5d93957e0044 100644
--- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/SuppressMessageAttributeState.cs
+++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/SuppressMessageAttributeState.cs
@@ -46,7 +46,7 @@ public void AddGlobalSymbolSuppression(ISymbol symbol, SuppressMessageInfo info)
}
else
{
- suppressions = new Dictionary() {{ info.Id, info }};
+ suppressions = new Dictionary() { { info.Id, info } };
_globalSymbolSuppressions.Add(symbol, suppressions);
}
}
@@ -85,10 +85,8 @@ public static Diagnostic ApplySourceSuppressions(Diagnostic diagnostic, Compilat
return diagnostic;
}
- var suppressMessageState = AnalyzerDriver.GetOrCreateCachedCompilationData(compilation).SuppressMessageAttributeState;
-
SuppressMessageInfo info;
- if (suppressMessageState.IsDiagnosticSuppressed(diagnostic, out info))
+ if (IsDiagnosticSuppressed(diagnostic, compilation, out info))
{
// Attach the suppression info to the diagnostic.
diagnostic = diagnostic.WithIsSuppressed(true);
@@ -97,6 +95,25 @@ public static Diagnostic ApplySourceSuppressions(Diagnostic diagnostic, Compilat
return diagnostic;
}
+ public static bool IsDiagnosticSuppressed(Diagnostic diagnostic, Compilation compilation, out AttributeData suppressingAttribute)
+ {
+ SuppressMessageInfo info;
+ if (IsDiagnosticSuppressed(diagnostic, compilation, out info))
+ {
+ suppressingAttribute = info.Attribute;
+ return true;
+ }
+
+ suppressingAttribute = null;
+ return false;
+ }
+
+ private static bool IsDiagnosticSuppressed(Diagnostic diagnostic, Compilation compilation, out SuppressMessageInfo info)
+ {
+ var suppressMessageState = AnalyzerDriver.GetOrCreateCachedCompilationData(compilation).SuppressMessageAttributeState;
+ return suppressMessageState.IsDiagnosticSuppressed(diagnostic, out info);
+ }
+
private bool IsDiagnosticSuppressed(Diagnostic diagnostic, out SuppressMessageInfo info, ISymbol symbolOpt = null)
{
if (symbolOpt != null && IsDiagnosticSuppressed(diagnostic.Id, symbolOpt, out info))
@@ -359,6 +376,7 @@ private static bool TryDecodeSuppressMessageAttributeData(AttributeData attribut
info.Scope = attribute.DecodeNamedArgument("Scope", SpecialType.System_String);
info.Target = attribute.DecodeNamedArgument("Target", SpecialType.System_String);
info.MessageId = attribute.DecodeNamedArgument("MessageId", SpecialType.System_String);
+ info.Attribute = attribute;
return true;
}
diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/SuppressMessageInfo.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/SuppressMessageInfo.cs
index 7bc59b3d9a4d0564725b232bb69e534de95f1d2f..c946c1850f68dd63d2a679655b7e774cf21c5cf9 100644
--- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/SuppressMessageInfo.cs
+++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/SuppressMessageInfo.cs
@@ -8,5 +8,6 @@ internal struct SuppressMessageInfo
public string Scope;
public string Target;
public string MessageId;
+ public AttributeData Attribute;
}
}
diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt
index 802e13a07bbb715de3ede96dd52b27ba27095a6c..593464cee86ea407174b9ac0370c555a28d9ee8f 100644
--- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt
+++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt
@@ -7,6 +7,7 @@ Microsoft.CodeAnalysis.Compilation.ScriptClass.get -> Microsoft.CodeAnalysis.INa
Microsoft.CodeAnalysis.Compilation.WithPreviousSubmission(Microsoft.CodeAnalysis.Compilation newPreviousSubmission) -> Microsoft.CodeAnalysis.Compilation
Microsoft.CodeAnalysis.CompilationOptions.ReportSuppressedDiagnostics.get -> bool
Microsoft.CodeAnalysis.CompilationOptions.WithReportSuppressedDiagnostics(bool value) -> Microsoft.CodeAnalysis.CompilationOptions
+Microsoft.CodeAnalysis.Diagnostic.GetSuppressionInfo(Microsoft.CodeAnalysis.Compilation compilation) -> Microsoft.CodeAnalysis.Diagnostics.SuppressionInfo
Microsoft.CodeAnalysis.DiagnosticDescriptor.GetEffectiveSeverity(Microsoft.CodeAnalysis.CompilationOptions compilationOptions) -> Microsoft.CodeAnalysis.ReportDiagnostic
Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.AnalysisOptions.get -> Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzersOptions
Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.Analyzers.get -> System.Collections.Immutable.ImmutableArray
@@ -29,6 +30,9 @@ Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzersOptions.ConcurrentAna
Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzersOptions.LogAnalyzerExecutionTime.get -> bool
Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzersOptions.OnAnalyzerException.get -> System.Action
Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzersOptions.ReportSuppressedDiagnostics.get -> bool
+Microsoft.CodeAnalysis.Diagnostics.SuppressionInfo
+Microsoft.CodeAnalysis.Diagnostics.SuppressionInfo.Attribute.get -> Microsoft.CodeAnalysis.AttributeData
+Microsoft.CodeAnalysis.Diagnostics.SuppressionInfo.Id.get -> string
Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetry
Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetry.ActionCounts
Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetry.ActionCounts.CodeBlockActionsCount.get -> int
diff --git a/src/EditorFeatures/CSharpTest/CSharpEditorServicesTest.csproj b/src/EditorFeatures/CSharpTest/CSharpEditorServicesTest.csproj
index d699c3030fd088ad55126a2257a794a6e5772e19..457d0173f8c03cce02ff60fdcaca1ca377e36f7d 100644
--- a/src/EditorFeatures/CSharpTest/CSharpEditorServicesTest.csproj
+++ b/src/EditorFeatures/CSharpTest/CSharpEditorServicesTest.csproj
@@ -182,6 +182,7 @@
+
diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/Suppression/RemoveSuppressionTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/Suppression/RemoveSuppressionTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d8067d865a8730d643a59483ac0dec77c71c49e9
--- /dev/null
+++ b/src/EditorFeatures/CSharpTest/Diagnostics/Suppression/RemoveSuppressionTests.cs
@@ -0,0 +1,906 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis.CodeFixes.Suppression;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.CodeFixes.Suppression;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Roslyn.Test.Utilities;
+using Xunit;
+
+namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics.Suppression
+{
+ public partial class CSharpRemoveSuppressionTests : CSharpSuppressionTests
+ {
+ protected override bool IncludeSuppressedDiagnostics => true;
+ protected override bool IncludeUnsuppressedDiagnostics => false;
+ protected override int CodeActionIndex => 0;
+ private string FixAllActionEquivalenceKey => FeaturesResources.RemoveSuppressionEquivalenceKeyPrefix + UserDiagnosticAnalyzer.Decsciptor.Id;
+
+ private class UserDiagnosticAnalyzer : DiagnosticAnalyzer
+ {
+ public static readonly DiagnosticDescriptor Decsciptor =
+ new DiagnosticDescriptor("InfoDiagnostic", "InfoDiagnostic Title", "InfoDiagnostic", "InfoDiagnostic", DiagnosticSeverity.Info, isEnabledByDefault: true);
+
+ public override ImmutableArray SupportedDiagnostics
+ {
+ get
+ {
+ return ImmutableArray.Create(Decsciptor);
+ }
+ }
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ClassDeclaration);
+ }
+
+ public void AnalyzeNode(SyntaxNodeAnalysisContext context)
+ {
+ var classDecl = (ClassDeclarationSyntax)context.Node;
+ context.ReportDiagnostic(Diagnostic.Create(Decsciptor, classDecl.Identifier.GetLocation()));
+ }
+ }
+
+ internal override Tuple CreateDiagnosticProviderAndFixer(Workspace workspace)
+ {
+ return new Tuple(
+ new UserDiagnosticAnalyzer(), new CSharpSuppressionCodeFixProvider());
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsSuppression)]
+ public void TestRemovePragmaSuppression()
+ {
+ Test(
+ @"
+using System;
+
+#pragma warning disable InfoDiagnostic // InfoDiagnostic Title
+[|class Class|]
+#pragma warning restore InfoDiagnostic // InfoDiagnostic Title
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}",
+ @"
+using System;
+
+class Class
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}");
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsSuppression)]
+ public void TestRemovePragmaSuppression_AdjacentTrivia()
+ {
+ Test(
+ @"
+using System;
+
+#pragma warning disable InfoDiagnostic // InfoDiagnostic Title
+class Class1 { }
+#pragma warning restore InfoDiagnostic // InfoDiagnostic Title
+#pragma warning disable InfoDiagnostic // InfoDiagnostic Title
+[|class Class2|]
+#pragma warning restore InfoDiagnostic // InfoDiagnostic Title
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}",
+ @"
+using System;
+
+#pragma warning disable InfoDiagnostic // InfoDiagnostic Title
+class Class1 { }
+#pragma warning restore InfoDiagnostic // InfoDiagnostic Title
+class Class2
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}");
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsSuppression)]
+ public void TestRemovePragmaSuppression_TriviaWithMultipleIDs()
+ {
+ Test(
+ @"
+using System;
+
+#pragma warning disable InfoDiagnostic, SomeOtherDiagnostic
+[|class Class|]
+#pragma warning restore InfoDiagnostic, SomeOtherDiagnostic
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}",
+ @"
+using System;
+
+#pragma warning disable InfoDiagnostic, SomeOtherDiagnostic
+#pragma warning restore InfoDiagnostic // InfoDiagnostic Title
+class Class
+#pragma warning disable InfoDiagnostic // InfoDiagnostic Title
+#pragma warning restore InfoDiagnostic, SomeOtherDiagnostic
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}");
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsSuppression)]
+ public void TestRemovePragmaSuppression_WithEnclosingSuppression()
+ {
+ Test(
+ @"
+#pragma warning disable InfoDiagnostic
+using System;
+
+#pragma warning disable InfoDiagnostic
+[|class Class|]
+#pragma warning restore InfoDiagnostic
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}",
+ @"
+#pragma warning disable InfoDiagnostic
+using System;
+
+#pragma warning restore InfoDiagnostic
+class Class
+#pragma warning disable InfoDiagnostic
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}");
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsSuppression)]
+ public void TestRemoveLocalAttributeSuppression()
+ {
+ Test(
+ $@"
+using System;
+
+[System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"")]
+[|class Class|]
+{{
+ int Method()
+ {{
+ int x = 0;
+ }}
+}}",
+ @"
+using System;
+
+class Class
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}");
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsSuppression)]
+ public void TestRemoveGlobalAttributeSuppression()
+ {
+ Test(
+ $@"
+using System;
+
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"", Scope = ""type"", Target = ""~T:Class"")]
+
+[|class Class|]
+{{
+ int Method()
+ {{
+ int x = 0;
+ }}
+}}",
+ @"
+using System;
+
+class Class
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}");
+ }
+
+ #region "Fix all occurrences tests"
+
+ #region "Pragma disable tests"
+
+ [Fact]
+ [Trait(Traits.Feature, Traits.Features.CodeActionsSuppression)]
+ [Trait(Traits.Feature, Traits.Features.CodeActionsFixAllOccurrences)]
+ public void TestFixAllInDocument_RemovePragmaSuppressions()
+ {
+ var input = @"
+
+
+
+using System;
+
+#pragma warning disable InfoDiagnostic // InfoDiagnostic Title
+{|FixAllInDocument:class Class1|}
+#pragma warning restore InfoDiagnostic // InfoDiagnostic Title
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}
+
+#pragma warning disable InfoDiagnostic // InfoDiagnostic Title
+class Class2
+#pragma warning restore InfoDiagnostic // InfoDiagnostic Title
+{
+}
+
+
+class Class3
+{
+}
+
+
+
+
+class Class1
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}
+
+class Class2
+{
+}
+
+
+";
+
+ var expected = @"
+
+
+
+using System;
+
+class Class1
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}
+
+class Class2
+{
+}
+
+
+class Class3
+{
+}
+
+
+
+
+class Class1
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}
+
+class Class2
+{
+}
+
+
+";
+
+ Test(input, expected, compareTokens: false, fixAllActionEquivalenceKey: FixAllActionEquivalenceKey);
+ }
+
+ [Fact]
+ [Trait(Traits.Feature, Traits.Features.CodeActionsSuppression)]
+ [Trait(Traits.Feature, Traits.Features.CodeActionsFixAllOccurrences)]
+ public void TestFixAllInProject_RemovePragmaSuppressions()
+ {
+ var input = @"
+
+
+
+using System;
+
+#pragma warning disable InfoDiagnostic // InfoDiagnostic Title
+{|FixAllInProject:class Class1|}
+#pragma warning restore InfoDiagnostic // InfoDiagnostic Title
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}
+
+#pragma warning disable InfoDiagnostic // InfoDiagnostic Title
+class Class2
+#pragma warning restore InfoDiagnostic // InfoDiagnostic Title
+{
+}
+
+
+#pragma warning disable InfoDiagnostic // InfoDiagnostic Title
+class Class3
+#pragma warning restore InfoDiagnostic // InfoDiagnostic Title
+{
+}
+
+
+
+
+class Class1
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}
+
+class Class2
+{
+}
+
+
+";
+
+ var expected = @"
+
+
+
+using System;
+
+class Class1
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}
+
+class Class2
+{
+}
+
+
+class Class3
+{
+}
+
+
+
+
+class Class1
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}
+
+class Class2
+{
+}
+
+
+";
+
+ Test(input, expected, compareTokens: false, fixAllActionEquivalenceKey: FixAllActionEquivalenceKey);
+ }
+
+ [Fact]
+ [Trait(Traits.Feature, Traits.Features.CodeActionsSuppression)]
+ [Trait(Traits.Feature, Traits.Features.CodeActionsFixAllOccurrences)]
+ public void TestFixAllInSolution()
+ {
+ var input = @"
+
+
+
+using System;
+
+#pragma warning disable InfoDiagnostic // InfoDiagnostic Title
+{|FixAllInSolution:class Class1|}
+#pragma warning restore InfoDiagnostic // InfoDiagnostic Title
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}
+
+#pragma warning disable InfoDiagnostic // InfoDiagnostic Title
+class Class2
+#pragma warning restore InfoDiagnostic // InfoDiagnostic Title
+{
+}
+
+
+#pragma warning disable InfoDiagnostic // InfoDiagnostic Title
+class Class3
+#pragma warning restore InfoDiagnostic // InfoDiagnostic Title
+{
+}
+
+
+
+
+#pragma warning disable InfoDiagnostic // InfoDiagnostic Title
+class Class1
+#pragma warning restore InfoDiagnostic // InfoDiagnostic Title
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}
+
+#pragma warning disable InfoDiagnostic // InfoDiagnostic Title
+class Class2
+#pragma warning restore InfoDiagnostic // InfoDiagnostic Title
+{
+}
+
+
+";
+
+ var expected = @"
+
+
+
+using System;
+
+class Class1
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}
+
+class Class2
+{
+}
+
+
+class Class3
+{
+}
+
+
+
+
+class Class1
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}
+
+class Class2
+{
+}
+
+
+";
+
+ Test(input, expected, compareTokens: false, fixAllActionEquivalenceKey: FixAllActionEquivalenceKey);
+ }
+
+ #endregion
+
+ #region "SuppressMessageAttribute tests"
+
+ [Fact]
+ [Trait(Traits.Feature, Traits.Features.CodeActionsSuppression)]
+ [Trait(Traits.Feature, Traits.Features.CodeActionsFixAllOccurrences)]
+ public void TestFixAllInDocument_RemoveAttributeSuppressions()
+ {
+ var addedGlobalSuppressions = $@"
+// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+// Project-level suppressions either have no target or are given
+// a specific target and scoped to a namespace, type, member, etc.
+
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"", Scope = ""member"", Target = ""~M:Class1.Method~System.Int32"")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"", Scope = ""type"", Target = ""~T:Class1"")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"", Scope = ""type"", Target = ""~T:Class2"")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"", Scope = ""type"", Target = ""~T:Class3"")]
+
+".Replace("<", "<").Replace(">", ">");
+
+ var input = @"
+
+
+
+using System;
+
+{|FixAllInDocument:class Class1|}
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}
+
+class Class2
+{
+}
+
+
+class Class3
+{
+}
+
+ " + addedGlobalSuppressions +
+@"
+
+
+
+class Class1
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}
+
+class Class2
+{
+}
+
+ " + addedGlobalSuppressions +
+@"
+
+";
+
+ var newGlobalSuppressionsFile = $@"
+// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+// Project-level suppressions either have no target or are given
+// a specific target and scoped to a namespace, type, member, etc.
+
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"", Scope = ""member"", Target = ""~M:Class1.Method~System.Int32"")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"", Scope = ""type"", Target = ""~T:Class3"")]
+
+".Replace("<", "<").Replace(">", ">");
+ var expected = @"
+
+
+
+using System;
+
+class Class1
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}
+
+class Class2
+{
+}
+
+
+class Class3
+{
+}
+
+ " + newGlobalSuppressionsFile +
+@"
+
+
+
+class Class1
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}
+
+class Class2
+{
+}
+
+ " + addedGlobalSuppressions +
+@"
+
+";
+
+ Test(input, expected, compareTokens: false, fixAllActionEquivalenceKey: FixAllActionEquivalenceKey);
+ }
+
+ [Fact]
+ [Trait(Traits.Feature, Traits.Features.CodeActionsSuppression)]
+ [Trait(Traits.Feature, Traits.Features.CodeActionsFixAllOccurrences)]
+ public void TestFixAllInProject_RemoveAttributeSuppressions()
+ {
+ var addedGlobalSuppressions = $@"
+// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+// Project-level suppressions either have no target or are given
+// a specific target and scoped to a namespace, type, member, etc.
+
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"", Scope = ""type"", Target = ""~T:Class1"")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"", Scope = ""type"", Target = ""~T:Class2"")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"", Scope = ""type"", Target = ""~T:Class3"")]
+
+".Replace("<", "<").Replace(">", ">");
+
+ var input = @"
+
+
+
+using System;
+
+{|FixAllInProject:class Class1|}
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}
+
+class Class2
+{
+}
+
+
+class Class3
+{
+}
+
+ " + addedGlobalSuppressions +
+@"
+
+
+
+class Class1
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}
+
+class Class2
+{
+}
+
+ " + addedGlobalSuppressions +
+@"
+
+";
+
+ var newGlobalSuppressionsFile = $@"
+// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+// Project-level suppressions either have no target or are given
+// a specific target and scoped to a namespace, type, member, etc.
+
+
+";
+ var expected = @"
+
+
+
+using System;
+
+class Class1
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}
+
+class Class2
+{
+}
+
+
+class Class3
+{
+}
+
+ " + newGlobalSuppressionsFile +
+@"
+
+
+
+class Class1
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}
+
+class Class2
+{
+}
+
+ " + addedGlobalSuppressions +
+@"
+
+";
+
+
+
+ Test(input, expected, compareTokens: false, fixAllActionEquivalenceKey: FixAllActionEquivalenceKey);
+ }
+
+ [Fact]
+ [Trait(Traits.Feature, Traits.Features.CodeActionsSuppression)]
+ [Trait(Traits.Feature, Traits.Features.CodeActionsFixAllOccurrences)]
+ public void TestFixAllInSolution_RemoveAttributeSuppression()
+ {
+ var addedGlobalSuppressionsProject1 = $@"
+// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+// Project-level suppressions either have no target or are given
+// a specific target and scoped to a namespace, type, member, etc.
+
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"", Scope = ""type"", Target = ""~T:Class1"")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"", Scope = ""type"", Target = ""~T:Class2"")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"", Scope = ""type"", Target = ""~T:Class3"")]
+
+".Replace("<", "<").Replace(">", ">");
+
+ var addedGlobalSuppressionsProject2 = $@"
+// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+// Project-level suppressions either have no target or are given
+// a specific target and scoped to a namespace, type, member, etc.
+
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"", Scope = ""type"", Target = ""~T:Class1"")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"", Scope = ""type"", Target = ""~T:Class2"")]
+
+".Replace("<", "<").Replace(">", ">");
+
+ var input = @"
+
+
+
+using System;
+
+{|FixAllInSolution:class Class1|}
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}
+
+class Class2
+{
+}
+
+
+class Class3
+{
+}
+
+ " + addedGlobalSuppressionsProject1 +
+@"
+
+
+
+class Class1
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}
+
+class Class2
+{
+}
+
+ " + addedGlobalSuppressionsProject2 +
+@"
+
+";
+
+ var newGlobalSuppressionsFile = $@"
+// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+// Project-level suppressions either have no target or are given
+// a specific target and scoped to a namespace, type, member, etc.
+
+
+";
+ var expected = @"
+
+
+
+using System;
+
+class Class1
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}
+
+class Class2
+{
+}
+
+
+class Class3
+{
+}
+
+ " + newGlobalSuppressionsFile +
+@"
+
+
+
+class Class1
+{
+ int Method()
+ {
+ int x = 0;
+ }
+}
+
+class Class2
+{
+}
+
+ " + newGlobalSuppressionsFile +
+@"
+
+";
+
+ Test(input, expected, compareTokens: false, fixAllActionEquivalenceKey: FixAllActionEquivalenceKey);
+ }
+
+ #endregion
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs
index 25d0f5ae909a3ad13def7641db8af2eb58ad6dd0..32cabca365084fb63cc117718d5ad26d508c3d8f 100644
--- a/src/EditorFeatures/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs
+++ b/src/EditorFeatures/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs
@@ -125,8 +125,8 @@ class Class
{{
void Method()
{{
+ // Start comment previous line
#pragma warning disable CS0219 // {CSharpResources.WRN_UnreferencedVarAssg_Title}
- // Start comment previous line
/* Start comment same line */
int x = 0; // End comment same line
#pragma warning restore CS0219 // {CSharpResources.WRN_UnreferencedVarAssg_Title}
@@ -296,9 +296,9 @@ class Class
void Method()
{{
+ // Comment
+ // Comment
#pragma warning disable CS1633 // {CSharpResources.WRN_IllegalPragma_Title}
- // Comment
- // Comment
#pragma abcde
}} // Comment
@@ -325,7 +325,7 @@ public void TestPragmaWarningDirectiveAroundTrivia2()
public void TestPragmaWarningDirectiveAroundTrivia3()
{
Test(
- @" [|#pragma abcde|] ",
+ @"[|#pragma abcde|] ",
$@"#pragma warning disable CS1633 // {CSharpResources.WRN_IllegalPragma_Title}
#pragma abcde
#pragma warning restore CS1633 // {CSharpResources.WRN_IllegalPragma_Title}");
diff --git a/src/EditorFeatures/Core/Implementation/Suggestions/SuggestedActionsSourceProvider.cs b/src/EditorFeatures/Core/Implementation/Suggestions/SuggestedActionsSourceProvider.cs
index 0d82f38cc1b6b0b575ef2303a2f4cd56eb35d77d..dbe64197c4d8df93095d0a6c47a88e77fb439ef0 100644
--- a/src/EditorFeatures/Core/Implementation/Suggestions/SuggestedActionsSourceProvider.cs
+++ b/src/EditorFeatures/Core/Implementation/Suggestions/SuggestedActionsSourceProvider.cs
@@ -316,8 +316,17 @@ private void GroupFixes(Workspace workspace, IEnumerable fixC
{
if (fix.Action is SuppressionCodeAction)
{
- var suggestedAction = new SuppressionSuggestedAction(workspace, _subjectBuffer, _owner._editHandler,
- fix, fixCollection.Provider, getFixAllSuggestedActionSet);
+ SuggestedAction suggestedAction;
+ if (fix.Action.HasCodeActions)
+ {
+ suggestedAction = new SuppressionSuggestedAction(workspace, _subjectBuffer, _owner._editHandler,
+ fix, fixCollection.Provider, getFixAllSuggestedActionSet);
+ }
+ else
+ {
+ suggestedAction = new CodeFixSuggestedAction(workspace, _subjectBuffer, _owner._editHandler,
+ fix, fix.Action, fixCollection.Provider, getFixAllSuggestedActionSet(fix.Action));
+ }
AddFix(fix, suggestedAction, map, order);
}
diff --git a/src/EditorFeatures/Core/Implementation/Suggestions/SuppressionSuggestedAction.cs b/src/EditorFeatures/Core/Implementation/Suggestions/SuppressionSuggestedAction.cs
index 0a747032136ae7adf24a07a6647f78a27770dbab..a7d45d82475829c0a7a5c2f1317fb078457739f9 100644
--- a/src/EditorFeatures/Core/Implementation/Suggestions/SuppressionSuggestedAction.cs
+++ b/src/EditorFeatures/Core/Implementation/Suggestions/SuppressionSuggestedAction.cs
@@ -9,7 +9,6 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
-using Microsoft.CodeAnalysis.CodeFixes.Suppression;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.Text;
using Roslyn.Utilities;
diff --git a/src/EditorFeatures/Test/Diagnostics/AbstractSuppressionAllCodeTests.cs b/src/EditorFeatures/Test/Diagnostics/AbstractSuppressionAllCodeTests.cs
index d8be380ac4e1237e51fba1d1095b82ab7459ac91..47adc094c4ad5aac52c3e1519b84a033972780bf 100644
--- a/src/EditorFeatures/Test/Diagnostics/AbstractSuppressionAllCodeTests.cs
+++ b/src/EditorFeatures/Test/Diagnostics/AbstractSuppressionAllCodeTests.cs
@@ -71,7 +71,7 @@ protected void TestSuppressionWithAttribute(string code, ParseOptions options, F
foreach (var diagnostic in diagnostics)
{
- if (!fixer.CanBeSuppressed(diagnostic))
+ if (!fixer.CanBeSuppressedOrUnsuppressed(diagnostic))
{
continue;
}
diff --git a/src/EditorFeatures/Test/Diagnostics/AbstractSuppressionDiagnosticTest.cs b/src/EditorFeatures/Test/Diagnostics/AbstractSuppressionDiagnosticTest.cs
index 997ef5d097d4a2db54426237600951e76b711b98..e49351be98b43ca411c18db1d823c30c21efb79e 100644
--- a/src/EditorFeatures/Test/Diagnostics/AbstractSuppressionDiagnosticTest.cs
+++ b/src/EditorFeatures/Test/Diagnostics/AbstractSuppressionDiagnosticTest.cs
@@ -16,6 +16,8 @@ namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics
public abstract class AbstractSuppressionDiagnosticTest : AbstractUserDiagnosticTest
{
protected abstract int CodeActionIndex { get; }
+ protected virtual bool IncludeSuppressedDiagnostics => false;
+ protected virtual bool IncludeUnsuppressedDiagnostics => true;
protected void Test(string initial, string expected)
{
@@ -52,13 +54,18 @@ internal override IEnumerable GetDiagnostics(TestWorkspace workspace
document = GetDocumentAndAnnotatedSpan(workspace, out annotation, out span);
}
- using (var testDriver = new TestDiagnosticAnalyzerDriver(document.Project, provider))
+ using (var testDriver = new TestDiagnosticAnalyzerDriver(document.Project, provider, includeSuppressedDiagnostics: IncludeSuppressedDiagnostics))
{
var fixer = providerAndFixer.Item2;
var diagnostics = testDriver.GetAllDiagnostics(provider, document, span)
- .Where(d => fixer.CanBeSuppressed(d))
+ .Where(d => fixer.CanBeSuppressedOrUnsuppressed(d))
.ToImmutableArray();
+ if (!IncludeUnsuppressedDiagnostics)
+ {
+ diagnostics = diagnostics.WhereAsArray(d => d.IsSuppressed);
+ }
+
var wrapperCodeFixer = new WrapperCodeFixProvider(fixer, diagnostics);
return GetDiagnosticAndFixes(diagnostics, provider, wrapperCodeFixer, testDriver, document, span, annotation, fixAllActionId);
}
diff --git a/src/EditorFeatures/VisualBasicTest/Diagnostics/Suppression/SuppressionTests.vb b/src/EditorFeatures/VisualBasicTest/Diagnostics/Suppression/SuppressionTests.vb
index 14870366960e0b9f8fae274cd17d27295295fa5f..5bce81a516347dc3bec00e1e54943d71c91710ed 100644
--- a/src/EditorFeatures/VisualBasicTest/Diagnostics/Suppression/SuppressionTests.vb
+++ b/src/EditorFeatures/VisualBasicTest/Diagnostics/Suppression/SuppressionTests.vb
@@ -402,8 +402,8 @@ End Class]]>
Imports System
Class C
Sub Method()
-#Disable Warning BC42024 ' {WRN_UnusedLocal_Title}
' Trivia previous line
+#Disable Warning BC42024 ' {WRN_UnusedLocal_Title}
Dim x As Integer ' Trivia same line
#Enable Warning BC42024 ' {WRN_UnusedLocal_Title}
' Trivia next line
diff --git a/src/ExpressionEvaluator/CSharp/Test/ResultProvider/FunctionPointerTests.cs b/src/ExpressionEvaluator/CSharp/Test/ResultProvider/FunctionPointerTests.cs
index 8afd5f939e6f1f290a417640c50c7750f8f1062f..c9df0accc30607ab7a014f66922c0bdd4ea0a7dc 100644
--- a/src/ExpressionEvaluator/CSharp/Test/ResultProvider/FunctionPointerTests.cs
+++ b/src/ExpressionEvaluator/CSharp/Test/ResultProvider/FunctionPointerTests.cs
@@ -12,7 +12,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests
{
public class FunctionPointerTests : CSharpResultProviderTestBase
{
- [Fact]
+ [Fact(Skip = "Tests are failing in Jenkins queues")]
public void Root()
{
const int ptr = 0x1234;
@@ -22,7 +22,7 @@ public void Root()
EvalResult("pfn", PointerToString(new IntPtr(ptr)), "System.Object*", "pfn", DkmEvaluationResultFlags.None, DkmEvaluationResultCategory.Other));
}
- [Fact]
+ [Fact(Skip = "Tests are failing in Jenkins queues")]
public void Member()
{
var source =
diff --git a/src/Features/CSharp/Portable/CodeFixes/Suppression/CSharpSuppressionCodeFixProvider.cs b/src/Features/CSharp/Portable/CodeFixes/Suppression/CSharpSuppressionCodeFixProvider.cs
index f9a06efc6752c54363a861c4d60a7253426f8d4b..895bf755c40f56a5c8de64c4e2268f7b8777b3aa 100644
--- a/src/Features/CSharp/Portable/CodeFixes/Suppression/CSharpSuppressionCodeFixProvider.cs
+++ b/src/Features/CSharp/Portable/CodeFixes/Suppression/CSharpSuppressionCodeFixProvider.cs
@@ -1,7 +1,9 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System;
using System.Composition;
using System.Globalization;
+using System.Linq;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CodeFixes.Suppression;
using Microsoft.CodeAnalysis.CSharp.Extensions;
@@ -14,24 +16,25 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeFixes.Suppression
[ExportSuppressionFixProvider(PredefinedCodeFixProviderNames.Suppression, LanguageNames.CSharp), Shared]
internal class CSharpSuppressionCodeFixProvider : AbstractSuppressionCodeFixProvider
{
- protected override SyntaxTriviaList CreatePragmaRestoreDirectiveTrivia(Diagnostic diagnostic, bool needsTrailingEndOfLine)
+ protected override SyntaxTriviaList CreatePragmaRestoreDirectiveTrivia(Diagnostic diagnostic, Func formatNode, bool needsLeadingEndOfLine, bool needsTrailingEndOfLine)
{
var restoreKeyword = SyntaxFactory.Token(SyntaxKind.RestoreKeyword);
- return CreatePragmaDirectiveTrivia(restoreKeyword, diagnostic, true, needsTrailingEndOfLine);
+ return CreatePragmaDirectiveTrivia(restoreKeyword, diagnostic, formatNode, needsLeadingEndOfLine, needsTrailingEndOfLine);
}
- protected override SyntaxTriviaList CreatePragmaDisableDirectiveTrivia(Diagnostic diagnostic, bool needsLeadingEndOfLine)
+ protected override SyntaxTriviaList CreatePragmaDisableDirectiveTrivia(Diagnostic diagnostic, Func formatNode, bool needsLeadingEndOfLine, bool needsTrailingEndOfLine)
{
var disableKeyword = SyntaxFactory.Token(SyntaxKind.DisableKeyword);
- return CreatePragmaDirectiveTrivia(disableKeyword, diagnostic, needsLeadingEndOfLine, true);
+ return CreatePragmaDirectiveTrivia(disableKeyword, diagnostic, formatNode, needsLeadingEndOfLine, needsTrailingEndOfLine);
}
- private SyntaxTriviaList CreatePragmaDirectiveTrivia(SyntaxToken disableOrRestoreKeyword, Diagnostic diagnostic, bool needsLeadingEndOfLine, bool needsTrailingEndOfLine)
+ private SyntaxTriviaList CreatePragmaDirectiveTrivia(SyntaxToken disableOrRestoreKeyword, Diagnostic diagnostic, Func formatNode, bool needsLeadingEndOfLine, bool needsTrailingEndOfLine)
{
var id = SyntaxFactory.IdentifierName(diagnostic.Id);
var ids = new SeparatedSyntaxList().Add(id);
var pragmaDirective = SyntaxFactory.PragmaWarningDirectiveTrivia(disableOrRestoreKeyword, ids, true);
- var pragmaDirectiveTrivia = SyntaxFactory.Trivia(pragmaDirective.WithAdditionalAnnotations(Formatter.Annotation));
+ pragmaDirective = (PragmaWarningDirectiveTriviaSyntax)formatNode(pragmaDirective);
+ var pragmaDirectiveTrivia = SyntaxFactory.Trivia(pragmaDirective);
var endOfLineTrivia = SyntaxFactory.ElasticCarriageReturnLineFeed;
var triviaList = SyntaxFactory.TriviaList(pragmaDirectiveTrivia);
@@ -157,5 +160,42 @@ private AttributeArgumentListSyntax CreateAttributeArguments(ISymbol targetSymbo
return attributeArgumentList;
}
+
+ protected override bool IsSingleAttributeInAttributeList(SyntaxNode attribute)
+ {
+ var attributeSyntax = attribute as AttributeSyntax;
+ if (attributeSyntax != null)
+ {
+ var attributeList = attributeSyntax.Parent as AttributeListSyntax;
+ return attributeList != null && attributeList.Attributes.Count == 1;
+ }
+
+ return false;
+ }
+
+ protected override bool IsAnyPragmaDirectiveForId(SyntaxTrivia trivia, string id, out bool enableDirective, out bool hasMultipleIds)
+ {
+ if (trivia.Kind() == SyntaxKind.PragmaWarningDirectiveTrivia)
+ {
+ var pragmaWarning = (PragmaWarningDirectiveTriviaSyntax)trivia.GetStructure();
+ enableDirective = pragmaWarning.DisableOrRestoreKeyword.Kind() == SyntaxKind.RestoreKeyword;
+ hasMultipleIds = pragmaWarning.ErrorCodes.Count > 1;
+ return pragmaWarning.ErrorCodes.Any(n => n.ToString() == id);
+ }
+
+ enableDirective = false;
+ hasMultipleIds = false;
+ return false;
+ }
+
+ protected override SyntaxTrivia TogglePragmaDirective(SyntaxTrivia trivia)
+ {
+ var pragmaWarning = (PragmaWarningDirectiveTriviaSyntax)trivia.GetStructure();
+ var currentKeyword = pragmaWarning.DisableOrRestoreKeyword;
+ var toggledKeywordKind = currentKeyword.Kind() == SyntaxKind.DisableKeyword ? SyntaxKind.RestoreKeyword : SyntaxKind.DisableKeyword;
+ var toggledToken = SyntaxFactory.Token(currentKeyword.LeadingTrivia, toggledKeywordKind, currentKeyword.TrailingTrivia);
+ var newPragmaWarning = pragmaWarning.WithDisableOrRestoreKeyword(toggledToken);
+ return SyntaxFactory.Trivia(newPragmaWarning);
+ }
}
}
diff --git a/src/Features/Core/Portable/CodeFixes/CodeFixService.cs b/src/Features/Core/Portable/CodeFixes/CodeFixService.cs
index eddae8947e82f6a7efa24a565e93ae4167adcf77..c478f080242e8c92ae317a123186c4b624e789f9 100644
--- a/src/Features/Core/Portable/CodeFixes/CodeFixService.cs
+++ b/src/Features/Core/Portable/CodeFixes/CodeFixService.cs
@@ -243,7 +243,7 @@ public async Task> GetFixesAsync(Document documen
var diagnostics = await DiagnosticData.ToDiagnosticsAsync(document.Project, diagnosticDataCollection, cancellationToken).ConfigureAwait(false);
- Func hasFix = (d) => lazySuppressionProvider.Value.CanBeSuppressed(d);
+ Func hasFix = (d) => lazySuppressionProvider.Value.CanBeSuppressedOrUnsuppressed(d);
Func, Task>> getFixes = (dxs) => lazySuppressionProvider.Value.GetSuppressionsAsync(document, span, dxs, cancellationToken);
await AppendFixesOrSuppressionsAsync(document, span, diagnostics, result, lazySuppressionProvider.Value, hasFix, getFixes, cancellationToken).ConfigureAwait(false);
return result;
@@ -374,7 +374,7 @@ private async Task ContainsAnyFix(Document document, DiagnosticData diagno
var dx = await diagnostic.ToDiagnosticAsync(document.Project, cancellationToken).ConfigureAwait(false);
- if (hasSuppressionFixer && lazySuppressionProvider.Value.CanBeSuppressed(dx))
+ if (hasSuppressionFixer && lazySuppressionProvider.Value.CanBeSuppressedOrUnsuppressed(dx))
{
return true;
}
diff --git a/src/Features/Core/Portable/CodeFixes/FixAllOccurrences/FixAllProviderInfo.cs b/src/Features/Core/Portable/CodeFixes/FixAllOccurrences/FixAllProviderInfo.cs
index 440bf93956f9477a94c993cce779fed567c1f986..ac266a1751875b1dad88959fbe2b9bfda48431be 100644
--- a/src/Features/Core/Portable/CodeFixes/FixAllOccurrences/FixAllProviderInfo.cs
+++ b/src/Features/Core/Portable/CodeFixes/FixAllOccurrences/FixAllProviderInfo.cs
@@ -106,7 +106,7 @@ public override bool CanBeFixed(Diagnostic diagnostic)
private class SuppressionFixerFixAllProviderInfo : FixAllProviderInfo
{
- private readonly Func _canBeSuppressedOrTriaged;
+ private readonly Func _canBeSuppressedOrUnsuppressed;
public SuppressionFixerFixAllProviderInfo(
FixAllProvider fixAllProvider,
@@ -114,12 +114,12 @@ private class SuppressionFixerFixAllProviderInfo : FixAllProviderInfo
IEnumerable supportedScopes)
: base(fixAllProvider, supportedScopes)
{
- this._canBeSuppressedOrTriaged = suppressionFixer.CanBeSuppressed;
+ this._canBeSuppressedOrUnsuppressed = suppressionFixer.CanBeSuppressedOrUnsuppressed;
}
public override bool CanBeFixed(Diagnostic diagnostic)
{
- return _canBeSuppressedOrTriaged(diagnostic);
+ return _canBeSuppressedOrUnsuppressed(diagnostic);
}
}
}
diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.FixAllProvider.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.FixAllProvider.cs
index c3e022ea1355b57b9c1d25a4fd16ad768d2e5b80..2f345c42b99ab01dc61f28b4be95adfb7f76b739 100644
--- a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.FixAllProvider.cs
+++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.FixAllProvider.cs
@@ -33,8 +33,12 @@ public async override Task GetFixAsync(FixAllContext fixAllContext)
var isGlobalSuppression = NestedSuppressionCodeAction.IsEquivalenceKeyForGlobalSuppression(fixAllContext.CodeActionEquivalenceKey);
if (!isGlobalSuppression)
{
- // Pragma warning fix all.
- batchFixer = new PragmaWarningBatchFixAllProvider(suppressionFixer);
+ var isPragmaWarningSuppression = NestedSuppressionCodeAction.IsEquivalenceKeyForPragmaWarning(fixAllContext.CodeActionEquivalenceKey);
+ Contract.ThrowIfFalse(isPragmaWarningSuppression || NestedSuppressionCodeAction.IsEquivalenceKeyForRemoveSuppression(fixAllContext.CodeActionEquivalenceKey));
+
+ batchFixer = isPragmaWarningSuppression ?
+ new PragmaWarningBatchFixAllProvider(suppressionFixer) :
+ RemoveSuppressionCodeAction.GetBatchFixer(suppressionFixer);
}
var title = fixAllContext.CodeActionEquivalenceKey;
diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.GlobalSuppressMessageCodeAction.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.GlobalSuppressMessageCodeAction.cs
index ed79ad6fcb5d546b9446c8a1bdd25cf5440a3982..d51f7078e4ac92d0b1ffc9ba6fd9554c882d6afc 100644
--- a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.GlobalSuppressMessageCodeAction.cs
+++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.GlobalSuppressMessageCodeAction.cs
@@ -13,7 +13,7 @@ internal sealed class GlobalSuppressMessageCodeAction : AbstractGlobalSuppressMe
private readonly ISymbol _targetSymbol;
private readonly Diagnostic _diagnostic;
- public GlobalSuppressMessageCodeAction(AbstractSuppressionCodeFixProvider fixer, ISymbol targetSymbol, Project project, Diagnostic diagnostic)
+ public GlobalSuppressMessageCodeAction(ISymbol targetSymbol, Project project, Diagnostic diagnostic, AbstractSuppressionCodeFixProvider fixer)
: base(fixer, project)
{
_targetSymbol = targetSymbol;
diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.IPragmaBasedCodeAction.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.IPragmaBasedCodeAction.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ec31eb40a7d344c8596929a13054d226e4b1045f
--- /dev/null
+++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.IPragmaBasedCodeAction.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.CodeAnalysis.CodeFixes.Suppression
+{
+ internal partial class AbstractSuppressionCodeFixProvider
+ {
+ ///
+ /// Suppression code action based on pragma add/remove/edit.
+ ///
+ internal interface IPragmaBasedCodeAction
+ {
+ Task GetChangedDocumentAsync(bool includeStartTokenChange, bool includeEndTokenChange, CancellationToken cancellationToken);
+ }
+ }
+}
diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaBatchFixHelpers.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaBatchFixHelpers.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e6510604166bdc3692bf2ca50ac45538b0c630a3
--- /dev/null
+++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaBatchFixHelpers.cs
@@ -0,0 +1,197 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.Formatting;
+using Microsoft.CodeAnalysis.Text;
+using Roslyn.Utilities;
+
+namespace Microsoft.CodeAnalysis.CodeFixes.Suppression
+{
+ internal partial class AbstractSuppressionCodeFixProvider
+ {
+ ///
+ /// Helper methods for pragma suppression add/remove batch fixers.
+ ///
+ private static class PragmaBatchFixHelpers
+ {
+ public static CodeAction CreateBatchPragmaFix(
+ AbstractSuppressionCodeFixProvider suppressionFixProvider,
+ Document document,
+ ImmutableArray pragmaActions,
+ ImmutableArray pragmaDiagnostics,
+ FixAllContext fixAllContext)
+ {
+ // This is a temporary generated code action, which doesn't need telemetry, hence suppressing RS0005.
+#pragma warning disable RS0005 // Do not use generic CodeAction.Create to create CodeAction
+ return CodeAction.Create(
+ ((CodeAction)pragmaActions[0]).Title,
+ createChangedDocument: ct =>
+ BatchPragmaFixesAsync(suppressionFixProvider, document, pragmaActions, pragmaDiagnostics, fixAllContext.CancellationToken),
+ equivalenceKey: fixAllContext.CodeActionEquivalenceKey);
+#pragma warning restore RS0005 // Do not use generic CodeAction.Create to create CodeAction
+ }
+
+ private static async Task BatchPragmaFixesAsync(
+ AbstractSuppressionCodeFixProvider suppressionFixProvider,
+ Document document,
+ ImmutableArray pragmaActions,
+ ImmutableArray diagnostics,
+ CancellationToken cancellationToken)
+ {
+ // We apply all the pragma suppression fixes sequentially.
+ // At every application, we track the updated locations for remaining diagnostics in the document.
+ var currentDiagnosticSpans = new Dictionary();
+ foreach (var diagnostic in diagnostics)
+ {
+ currentDiagnosticSpans.Add(diagnostic, diagnostic.Location.SourceSpan);
+ }
+
+ var currentDocument = document;
+ for (int i = 0; i < pragmaActions.Length; i++)
+ {
+ var originalpragmaAction = pragmaActions[i];
+ var diagnostic = diagnostics[i];
+
+ // Get the diagnostic span for the diagnostic in latest document snapshot.
+ TextSpan currentDiagnosticSpan;
+ if (!currentDiagnosticSpans.TryGetValue(diagnostic, out currentDiagnosticSpan))
+ {
+ // Diagnostic whose location conflicts with a prior fix.
+ continue;
+ }
+
+ // Compute and apply pragma suppression fix.
+ var currentTree = await currentDocument.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
+ var currentLocation = Location.Create(currentTree, currentDiagnosticSpan);
+ diagnostic = Diagnostic.Create(
+ id: diagnostic.Id,
+ category: diagnostic.Descriptor.Category,
+ message: diagnostic.GetMessage(),
+ severity: diagnostic.Severity,
+ defaultSeverity: diagnostic.DefaultSeverity,
+ isEnabledByDefault: diagnostic.Descriptor.IsEnabledByDefault,
+ warningLevel: diagnostic.WarningLevel,
+ title: diagnostic.Descriptor.Title,
+ description: diagnostic.Descriptor.Description,
+ helpLink: diagnostic.Descriptor.HelpLinkUri,
+ location: currentLocation,
+ additionalLocations: diagnostic.AdditionalLocations,
+ customTags: diagnostic.Descriptor.CustomTags,
+ properties: diagnostic.Properties,
+ isSuppressed: diagnostic.IsSuppressed);
+
+ var newSuppressionFixes = await suppressionFixProvider.GetSuppressionsAsync(currentDocument, currentDiagnosticSpan, SpecializedCollections.SingletonEnumerable(diagnostic), cancellationToken).ConfigureAwait(false);
+ var newSuppressionFix = newSuppressionFixes.SingleOrDefault();
+ if (newSuppressionFix != null)
+ {
+ var newPragmaAction = newSuppressionFix.Action as IPragmaBasedCodeAction ??
+ newSuppressionFix.Action.GetCodeActions().OfType().SingleOrDefault();
+ if (newPragmaAction != null)
+ {
+ // Get the changed document with pragma suppression add/removals.
+ // Note: We do it one token at a time to ensure we get single text change in the new document, otherwise UpdateDiagnosticSpans won't function as expected.
+ // Update the diagnostics spans based on the text changes.
+ var startTokenChanges = await GetChangedDocumentAsync(newPragmaAction, currentDocument, diagnostics, currentDiagnosticSpans,
+ includeStartTokenChange: true, includeEndTokenChange: false, cancellationToken: cancellationToken).ConfigureAwait(false);
+
+ var endTokenChanges = await GetChangedDocumentAsync(newPragmaAction, currentDocument, diagnostics, currentDiagnosticSpans,
+ includeStartTokenChange: false, includeEndTokenChange: true, cancellationToken: cancellationToken).ConfigureAwait(false);
+
+ var currentText = await currentDocument.GetTextAsync(cancellationToken).ConfigureAwait(false);
+ var orderedChanges = startTokenChanges.Concat(endTokenChanges).OrderBy(change => change.Span).Distinct();
+ var newText = currentText.WithChanges(orderedChanges);
+ currentDocument = currentDocument.WithText(newText);
+ }
+ }
+ }
+
+ return currentDocument;
+ }
+
+ private static async Task> GetChangedDocumentAsync(
+ IPragmaBasedCodeAction pragmaAction,
+ Document currentDocument,
+ ImmutableArray diagnostics,
+ Dictionary currentDiagnosticSpans,
+ bool includeStartTokenChange,
+ bool includeEndTokenChange,
+ CancellationToken cancellationToken)
+ {
+ var newDocument = await pragmaAction.GetChangedDocumentAsync(includeStartTokenChange, includeEndTokenChange, cancellationToken).ConfigureAwait(false);
+
+ // Update the diagnostics spans based on the text changes.
+ var textChanges = await newDocument.GetTextChangesAsync(currentDocument, cancellationToken).ConfigureAwait(false);
+ foreach (var textChange in textChanges)
+ {
+ UpdateDiagnosticSpans(diagnostics, currentDiagnosticSpans, textChange);
+ }
+
+ return textChanges;
+ }
+
+ private static async Task UpdateDiagnosticSpansAsync(Document currentDocument, Document newDocument, ImmutableArray diagnostics, Dictionary currentDiagnosticSpans, CancellationToken cancellationToken)
+ {
+ // Update the diagnostics spans based on the text changes.
+ var textChanges = await newDocument.GetTextChangesAsync(currentDocument, cancellationToken).ConfigureAwait(false);
+ foreach (var textChange in textChanges)
+ {
+ UpdateDiagnosticSpans(diagnostics, currentDiagnosticSpans, textChange);
+ }
+ }
+
+ private static void UpdateDiagnosticSpans(ImmutableArray diagnostics, Dictionary currentDiagnosticSpans, TextChange textChange)
+ {
+ var isAdd = textChange.Span.Length == 0;
+ Func isPriorSpan = span => span.End <= textChange.Span.Start;
+ Func isFollowingSpan = span => span.Start >= textChange.Span.End;
+ Func isEnclosingSpan = span => span.Contains(textChange.Span);
+
+ foreach (var diagnostic in diagnostics)
+ {
+ TextSpan currentSpan;
+ if (!currentDiagnosticSpans.TryGetValue(diagnostic, out currentSpan))
+ {
+ continue;
+ }
+
+ if (isPriorSpan(currentSpan))
+ {
+ // Prior span, needs no update.
+ continue;
+ }
+
+ var delta = textChange.NewText.Length - textChange.Span.Length;
+ if (delta != 0)
+ {
+ if (isFollowingSpan(currentSpan))
+ {
+ // Following span.
+ var newStart = currentSpan.Start + delta;
+ var newSpan = new TextSpan(newStart, currentSpan.Length);
+ currentDiagnosticSpans[diagnostic] = newSpan;
+ }
+ else if (isEnclosingSpan(currentSpan))
+ {
+ // Enclosing span.
+ var newLength = currentSpan.Length + delta;
+ var newSpan = new TextSpan(currentSpan.Start, newLength);
+ currentDiagnosticSpans[diagnostic] = newSpan;
+ }
+ else
+ {
+ // Overlapping span.
+ // Drop conflicting diagnostics.
+ currentDiagnosticSpans.Remove(diagnostic);
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaHelpers.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaHelpers.cs
new file mode 100644
index 0000000000000000000000000000000000000000..790ce618cca8dcd93709dab4a6c455b1b1a7593b
--- /dev/null
+++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaHelpers.cs
@@ -0,0 +1,174 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Formatting;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.CodeAnalysis.CodeFixes.Suppression
+{
+ internal partial class AbstractSuppressionCodeFixProvider
+ {
+ ///
+ /// Helper methods for pragma based suppression code actions.
+ ///
+ private static class PragmaHelpers
+ {
+ internal async static Task GetChangeDocumentWithPragmaAdjustedAsync(
+ Document document,
+ TextSpan diagnosticSpan,
+ SuppressionTargetInfo suppressionTargetInfo,
+ Func getNewStartToken,
+ Func getNewEndToken,
+ CancellationToken cancellationToken)
+ {
+ var startToken = suppressionTargetInfo.StartToken;
+ var endToken = suppressionTargetInfo.EndToken;
+ var nodeWithTokens = suppressionTargetInfo.NodeWithTokens;
+ var root = await nodeWithTokens.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
+
+ var startAndEndTokenAreTheSame = startToken == endToken;
+ SyntaxToken newStartToken = getNewStartToken(startToken, diagnosticSpan);
+
+ SyntaxToken newEndToken = endToken;
+ if (startAndEndTokenAreTheSame)
+ {
+ var annotation = new SyntaxAnnotation();
+ newEndToken = root.ReplaceToken(startToken, newStartToken.WithAdditionalAnnotations(annotation)).GetAnnotatedTokens(annotation).Single();
+ var spanChange = newStartToken.LeadingTrivia.FullSpan.Length - startToken.LeadingTrivia.FullSpan.Length;
+ diagnosticSpan = new TextSpan(diagnosticSpan.Start + spanChange, diagnosticSpan.Length);
+ }
+
+ newEndToken = getNewEndToken(newEndToken, diagnosticSpan);
+
+ SyntaxNode newNode;
+ if (startAndEndTokenAreTheSame)
+ {
+ newNode = nodeWithTokens.ReplaceToken(startToken, newEndToken);
+ }
+ else
+ {
+ newNode = nodeWithTokens.ReplaceTokens(new[] { startToken, endToken }, (o, n) => o == startToken ? newStartToken : newEndToken);
+ }
+
+ var newRoot = root.ReplaceNode(nodeWithTokens, newNode);
+ return document.WithSyntaxRoot(newRoot);
+ }
+
+ private static int GetPositionForPragmaInsertion(ImmutableArray triviaList, TextSpan currentDiagnosticSpan, AbstractSuppressionCodeFixProvider fixer, bool isStartToken, out SyntaxTrivia triviaAtIndex)
+ {
+ // Start token: Insert the #pragma disable directive just **before** the first end of line trivia prior to diagnostic location.
+ // End token: Insert the #pragma disable directive just **after** the first end of line trivia after diagnostic location.
+
+ Func getNextIndex = cur => isStartToken ? cur - 1 : cur + 1;
+ Func shouldConsiderTrivia = trivia =>
+ isStartToken ?
+ trivia.FullSpan.End <= currentDiagnosticSpan.Start :
+ trivia.FullSpan.Start >= currentDiagnosticSpan.End;
+
+ var walkedPastDiagnosticSpan = false;
+ var seenEndOfLineTrivia = false;
+ var index = isStartToken ? triviaList.Length - 1 : 0;
+ while (index >= 0 && index < triviaList.Length)
+ {
+ var trivia = triviaList[index];
+
+ walkedPastDiagnosticSpan = walkedPastDiagnosticSpan || shouldConsiderTrivia(trivia);
+ seenEndOfLineTrivia = seenEndOfLineTrivia ||
+ (fixer.IsEndOfLine(trivia) ||
+ (trivia.HasStructure &&
+ trivia.GetStructure().DescendantTrivia().Any(t => fixer.IsEndOfLine(t))));
+
+ if (walkedPastDiagnosticSpan && seenEndOfLineTrivia)
+ {
+ break;
+ }
+
+ index = getNextIndex(index);
+ }
+
+ triviaAtIndex = index >= 0 && index < triviaList.Length ?
+ triviaList[index] :
+ default(SyntaxTrivia);
+
+ return index;
+ }
+
+ internal static SyntaxToken GetNewStartTokenWithAddedPragma(SyntaxToken startToken, TextSpan currentDiagnosticSpan, Diagnostic diagnostic, AbstractSuppressionCodeFixProvider fixer, Func formatNode, bool isRemoveSuppression = false)
+ {
+ var trivia = startToken.LeadingTrivia.ToImmutableArray();
+ SyntaxTrivia insertAfterTrivia;
+ var index = GetPositionForPragmaInsertion(trivia, currentDiagnosticSpan, fixer, isStartToken: true, triviaAtIndex: out insertAfterTrivia);
+ index++;
+
+ bool needsLeadingEOL;
+ if (index > 0)
+ {
+ needsLeadingEOL = !fixer.IsEndOfLine(insertAfterTrivia);
+ }
+ else if (startToken.FullSpan.Start == 0)
+ {
+ needsLeadingEOL = false;
+ }
+ else
+ {
+ needsLeadingEOL = true;
+ }
+
+ var pragmaTrivia = !isRemoveSuppression ?
+ fixer.CreatePragmaDisableDirectiveTrivia(diagnostic, formatNode, needsLeadingEOL, needsTrailingEndOfLine: true) :
+ fixer.CreatePragmaRestoreDirectiveTrivia(diagnostic, formatNode, needsLeadingEOL, needsTrailingEndOfLine: true);
+
+ return startToken.WithLeadingTrivia(trivia.InsertRange(index, pragmaTrivia));
+ }
+
+ internal static SyntaxToken GetNewEndTokenWithAddedPragma(SyntaxToken endToken, TextSpan currentDiagnosticSpan, Diagnostic diagnostic, AbstractSuppressionCodeFixProvider fixer, Func formatNode, bool isRemoveSuppression = false)
+ {
+ ImmutableArray trivia;
+ var isEOF = fixer.IsEndOfFileToken(endToken);
+ if (isEOF)
+ {
+ trivia = endToken.LeadingTrivia.ToImmutableArray();
+ }
+ else
+ {
+ trivia = endToken.TrailingTrivia.ToImmutableArray();
+ }
+
+ SyntaxTrivia insertBeforeTrivia;
+ var index = GetPositionForPragmaInsertion(trivia, currentDiagnosticSpan, fixer, isStartToken: false, triviaAtIndex: out insertBeforeTrivia);
+
+ bool needsTrailingEOL;
+ if (index < trivia.Length)
+ {
+ needsTrailingEOL = !fixer.IsEndOfLine(insertBeforeTrivia);
+ }
+ else if (isEOF)
+ {
+ needsTrailingEOL = false;
+ }
+ else
+ {
+ needsTrailingEOL = true;
+ }
+
+ var pragmaTrivia = !isRemoveSuppression ?
+ fixer.CreatePragmaRestoreDirectiveTrivia(diagnostic, formatNode, needsLeadingEndOfLine: true, needsTrailingEndOfLine: needsTrailingEOL) :
+ fixer.CreatePragmaDisableDirectiveTrivia(diagnostic, formatNode, needsLeadingEndOfLine: true, needsTrailingEndOfLine: needsTrailingEOL);
+
+ if (isEOF)
+ {
+ return endToken.WithLeadingTrivia(trivia.InsertRange(index, pragmaTrivia));
+ }
+ else
+ {
+ return endToken.WithTrailingTrivia(trivia.InsertRange(index, pragmaTrivia));
+ };
+ }
+ }
+ }
+}
diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaWarningBatchFixAllProvider.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaWarningBatchFixAllProvider.cs
index 79c7938ade59017fb8dc9750d0fae0bc14d169c7..20744e78382b8dc437f30c372c965083ad6ccf58 100644
--- a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaWarningBatchFixAllProvider.cs
+++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaWarningBatchFixAllProvider.cs
@@ -1,16 +1,22 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.Text;
+using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CodeFixes.Suppression
{
internal abstract partial class AbstractSuppressionCodeFixProvider : ISuppressionFixProvider
{
+ ///
+ /// Batch fixer for pragma suppress code action.
+ ///
internal sealed class PragmaWarningBatchFixAllProvider : BatchFixAllProvider
{
private readonly AbstractSuppressionCodeFixProvider _suppressionFixProvider;
@@ -22,22 +28,34 @@ public PragmaWarningBatchFixAllProvider(AbstractSuppressionCodeFixProvider suppr
public override async Task AddDocumentFixesAsync(Document document, ImmutableArray diagnostics, Action addFix, FixAllContext fixAllContext)
{
- foreach (var diagnosticsForSpan in diagnostics.Where(d => d.Location.IsInSource).GroupBy(d => d.Location.SourceSpan))
+ var pragmaActionsBuilder = ImmutableArray.CreateBuilder();
+ var pragmaDiagnosticsBuilder = ImmutableArray.CreateBuilder();
+
+ foreach (var diagnostic in diagnostics.Where(d => d.Location.IsInSource && !d.IsSuppressed))
{
- var span = diagnosticsForSpan.First().Location.SourceSpan;
- var pragmaSuppressions = await _suppressionFixProvider.GetPragmaSuppressionsAsync(document, span, diagnosticsForSpan, fixAllContext.CancellationToken).ConfigureAwait(false);
- foreach (var pragmaSuppression in pragmaSuppressions)
+ var span = diagnostic.Location.SourceSpan;
+ var pragmaSuppressions = await _suppressionFixProvider.GetPragmaSuppressionsAsync(document, span, SpecializedCollections.SingletonEnumerable(diagnostic), fixAllContext.CancellationToken).ConfigureAwait(false);
+ var pragmaSuppression = pragmaSuppressions.SingleOrDefault();
+ if (pragmaSuppression != null)
{
if (fixAllContext is FixMultipleContext)
{
- addFix(pragmaSuppression.CloneForFixMultipleContext());
- }
- else
- {
- addFix(pragmaSuppression);
+ pragmaSuppression = pragmaSuppression.CloneForFixMultipleContext();
}
+
+ pragmaActionsBuilder.Add(pragmaSuppression);
+ pragmaDiagnosticsBuilder.Add(diagnostic);
}
}
+
+ // Get the pragma batch fix.
+ if (pragmaActionsBuilder.Count > 0)
+ {
+ var pragmaBatchFix = PragmaBatchFixHelpers.CreateBatchPragmaFix(_suppressionFixProvider, document,
+ pragmaActionsBuilder.ToImmutable(), pragmaDiagnosticsBuilder.ToImmutable(), fixAllContext);
+
+ addFix(pragmaBatchFix);
+ }
}
}
}
diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaWarningCodeAction.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaWarningCodeAction.cs
index 0b3902e60621dc1d399d15caf886fd5593eba5c1..69f493fb197ea4fb3063c46c4d86a0709fdcb17d 100644
--- a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaWarningCodeAction.cs
+++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaWarningCodeAction.cs
@@ -1,46 +1,37 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-using System;
-using System.Collections.Immutable;
-using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.Formatting;
namespace Microsoft.CodeAnalysis.CodeFixes.Suppression
{
internal abstract partial class AbstractSuppressionCodeFixProvider : ISuppressionFixProvider
{
- internal sealed class PragmaWarningCodeAction : AbstractSuppressionCodeAction
+ internal sealed class PragmaWarningCodeAction : AbstractSuppressionCodeAction, IPragmaBasedCodeAction
{
- private readonly SyntaxToken _startToken;
- private readonly SyntaxToken _endToken;
- private readonly SyntaxNode _nodeWithTokens;
+ private readonly SuppressionTargetInfo _suppressionTargetInfo;
private readonly Document _document;
private readonly Diagnostic _diagnostic;
private readonly bool _forFixMultipleContext;
- public PragmaWarningCodeAction(
- AbstractSuppressionCodeFixProvider fixer,
- SyntaxToken startToken,
- SyntaxToken endToken,
- SyntaxNode nodeWithTokens,
+ internal PragmaWarningCodeAction(
+ SuppressionTargetInfo suppressionTargetInfo,
Document document,
Diagnostic diagnostic,
+ AbstractSuppressionCodeFixProvider fixer,
bool forFixMultipleContext = false)
- : base (fixer, title: FeaturesResources.SuppressWithPragma)
+ : base(fixer, title: FeaturesResources.SuppressWithPragma)
{
- _startToken = startToken;
- _endToken = endToken;
- _nodeWithTokens = nodeWithTokens;
+ _suppressionTargetInfo = suppressionTargetInfo;
_document = document;
_diagnostic = diagnostic;
_forFixMultipleContext = forFixMultipleContext;
}
-
+
public PragmaWarningCodeAction CloneForFixMultipleContext()
{
- return new PragmaWarningCodeAction(Fixer, _startToken, _endToken, _nodeWithTokens, _document, _diagnostic, forFixMultipleContext: true);
+ return new PragmaWarningCodeAction(_suppressionTargetInfo, _document, _diagnostic, Fixer, forFixMultipleContext: true);
}
protected override string DiagnosticIdForEquivalenceKey =>
@@ -48,121 +39,27 @@ public PragmaWarningCodeAction CloneForFixMultipleContext()
protected async override Task GetChangedDocumentAsync(CancellationToken cancellationToken)
{
- var startAndEndTokenAreTheSame = _startToken == _endToken;
- SyntaxToken newStartToken = GetNewStartToken(_startToken, _diagnostic, Fixer);
-
- SyntaxToken newEndToken = _endToken;
- if (startAndEndTokenAreTheSame)
- {
- newEndToken = newStartToken;
- }
-
- newEndToken = GetNewEndToken(newEndToken, _diagnostic, Fixer);
-
- SyntaxNode newNode;
- if (startAndEndTokenAreTheSame)
- {
- newNode = _nodeWithTokens.ReplaceToken(_startToken, newEndToken);
- }
- else
- {
- newNode = _nodeWithTokens.ReplaceTokens(new[] { _startToken, _endToken }, (o, n) => o == _startToken ? newStartToken : newEndToken);
- }
-
- var root = await _document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
- var newRoot = root.ReplaceNode(_nodeWithTokens, newNode);
- return _document.WithSyntaxRoot(newRoot);
+ return await GetChangedDocumentAsync(includeStartTokenChange: true, includeEndTokenChange: true, cancellationToken: cancellationToken).ConfigureAwait(false);
}
- private static SyntaxToken GetNewStartToken(SyntaxToken startToken, Diagnostic diagnostic, AbstractSuppressionCodeFixProvider fixer)
+ public async Task GetChangedDocumentAsync(bool includeStartTokenChange, bool includeEndTokenChange, CancellationToken cancellationToken)
{
- var trivia = startToken.LeadingTrivia.ToImmutableArray();
-
- // Insert the #pragma disable directive after all leading new line trivia but before first trivia of any other kind.
- int index;
- SyntaxTrivia firstNonEOLTrivia = trivia.FirstOrDefault(t => !fixer.IsEndOfLine(t));
- if (firstNonEOLTrivia == default(SyntaxTrivia))
- {
- index = trivia.Length;
- }
- else
- {
- index = trivia.IndexOf(firstNonEOLTrivia);
- }
-
- bool needsLeadingEOL;
- if (index > 0)
- {
- needsLeadingEOL = !fixer.IsEndOfLine(trivia[index - 1]);
- }
- else if (startToken.FullSpan.Start == 0)
- {
- needsLeadingEOL = false;
- }
- else
- {
- needsLeadingEOL = true;
- }
-
- var pragmaWarningTrivia = fixer.CreatePragmaDisableDirectiveTrivia(diagnostic, needsLeadingEOL);
-
- return startToken.WithLeadingTrivia(trivia.InsertRange(index, pragmaWarningTrivia));
+ return await PragmaHelpers.GetChangeDocumentWithPragmaAdjustedAsync(
+ _document,
+ _diagnostic.Location.SourceSpan,
+ _suppressionTargetInfo,
+ (startToken, currentDiagnosticSpan) => includeStartTokenChange ? PragmaHelpers.GetNewStartTokenWithAddedPragma(startToken, currentDiagnosticSpan, _diagnostic, Fixer, FormatNode) : startToken,
+ (endToken, currentDiagnosticSpan) => includeEndTokenChange ? PragmaHelpers.GetNewEndTokenWithAddedPragma(endToken, currentDiagnosticSpan, _diagnostic, Fixer, FormatNode) : endToken,
+ cancellationToken).ConfigureAwait(false);
}
- private static SyntaxToken GetNewEndToken(SyntaxToken endToken, Diagnostic diagnostic, AbstractSuppressionCodeFixProvider fixer)
- {
- ImmutableArray trivia;
- var isEOF = fixer.IsEndOfFileToken(endToken);
- if (isEOF)
- {
- trivia = endToken.LeadingTrivia.ToImmutableArray();
- }
- else
- {
- trivia = endToken.TrailingTrivia.ToImmutableArray();
- }
-
- SyntaxTrivia lastNonEOLTrivia = trivia.LastOrDefault(t => !fixer.IsEndOfLine(t));
-
- // Insert the #pragma restore directive after the last trailing trivia that is not a new line trivia.
- int index;
- if (lastNonEOLTrivia == default(SyntaxTrivia))
- {
- index = 0;
- }
- else
- {
- index = trivia.IndexOf(lastNonEOLTrivia) + 1;
- }
+ public SyntaxToken StartToken_TestOnly => _suppressionTargetInfo.StartToken;
+ public SyntaxToken EndToken_TestOnly => _suppressionTargetInfo.EndToken;
- bool needsTrailingEOL;
- if (index < trivia.Length)
- {
- needsTrailingEOL = !fixer.IsEndOfLine(trivia[index]);
- }
- else if (isEOF)
- {
- needsTrailingEOL = false;
- }
- else
- {
- needsTrailingEOL = true;
- }
-
- var pragmaRestoreTrivia = fixer.CreatePragmaRestoreDirectiveTrivia(diagnostic, needsTrailingEOL);
-
- if (isEOF)
- {
- return endToken.WithLeadingTrivia(trivia.InsertRange(index, pragmaRestoreTrivia));
- }
- else
- {
- return endToken.WithTrailingTrivia(trivia.InsertRange(index, pragmaRestoreTrivia));
- }
+ private SyntaxNode FormatNode(SyntaxNode node)
+ {
+ return Formatter.Format(node, _document.Project.Solution.Workspace);
}
-
- public SyntaxToken StartToken_TestOnly => _startToken;
- public SyntaxToken EndToken_TestOnly => _endToken;
}
}
}
diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction.BatchFixer.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction.BatchFixer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8946b3233bebd714f78236242d10ad975f6252e6
--- /dev/null
+++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction.BatchFixer.cs
@@ -0,0 +1,147 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.CodeActions;
+using Roslyn.Utilities;
+
+namespace Microsoft.CodeAnalysis.CodeFixes.Suppression
+{
+ internal abstract partial class AbstractSuppressionCodeFixProvider : ISuppressionFixProvider
+ {
+ internal abstract partial class RemoveSuppressionCodeAction
+ {
+ public static BatchFixAllProvider GetBatchFixer(AbstractSuppressionCodeFixProvider suppressionFixProvider)
+ {
+ return new BatchFixer(suppressionFixProvider);
+ }
+
+ ///
+ /// Batch fixer for pragma suppression removal code action.
+ ///
+ private sealed class BatchFixer : BatchFixAllProvider
+ {
+ private readonly AbstractSuppressionCodeFixProvider _suppressionFixProvider;
+
+ public BatchFixer(AbstractSuppressionCodeFixProvider suppressionFixProvider)
+ {
+ _suppressionFixProvider = suppressionFixProvider;
+ }
+
+ public override async Task AddDocumentFixesAsync(Document document, ImmutableArray diagnostics, Action addFix, FixAllContext fixAllContext)
+ {
+ // Batch all the pragma remove suppression fixes by executing them sequentially for the document.
+ var pragmaActionsBuilder = ImmutableArray.CreateBuilder();
+ var pragmaDiagnosticsBuilder = ImmutableArray.CreateBuilder();
+ foreach (var diagnostic in diagnostics.Where(d => d.Location.IsInSource && d.IsSuppressed))
+ {
+ var span = diagnostic.Location.SourceSpan;
+ var removeSuppressionFixes = await _suppressionFixProvider.GetSuppressionsAsync(document, span, SpecializedCollections.SingletonEnumerable(diagnostic), fixAllContext.CancellationToken).ConfigureAwait(false);
+ var removeSuppressionFix = removeSuppressionFixes.SingleOrDefault();
+ if (removeSuppressionFix != null)
+ {
+ var codeAction = removeSuppressionFix.Action as RemoveSuppressionCodeAction;
+ if (codeAction != null)
+ {
+ if (fixAllContext is FixMultipleContext)
+ {
+ codeAction = codeAction.CloneForFixMultipleContext();
+ }
+
+ var pragmaRemoveAction = codeAction as PragmaRemoveAction;
+ if (pragmaRemoveAction != null)
+ {
+ pragmaActionsBuilder.Add(pragmaRemoveAction);
+ pragmaDiagnosticsBuilder.Add(diagnostic);
+ }
+ else
+ {
+ addFix(codeAction);
+ }
+ }
+ }
+ }
+
+ // Get the pragma batch fix.
+ if (pragmaActionsBuilder.Count > 0)
+ {
+ var pragmaBatchFix = PragmaBatchFixHelpers.CreateBatchPragmaFix(_suppressionFixProvider, document,
+ pragmaActionsBuilder.ToImmutable(), pragmaDiagnosticsBuilder.ToImmutable(), fixAllContext);
+
+ addFix(pragmaBatchFix);
+ }
+ }
+
+ public override async Task TryGetMergedFixAsync(IEnumerable batchOfFixes, FixAllContext fixAllContext)
+ {
+ // Batch all the attribute removal fixes into a single fix.
+ // Pragma removal fixes have already been batch for each document AddDocumentFixes method.
+ // This ensures no merge conflicts in merging all fixes by our base implementation.
+
+ var cancellationToken = fixAllContext.CancellationToken;
+ var oldSolution = fixAllContext.Document.Project.Solution;
+ var currentSolution = oldSolution;
+
+ var attributeRemoveFixes = new List();
+ var newBatchOfFixes = new List();
+ foreach (var codeAction in batchOfFixes)
+ {
+ var attributeRemoveFix = codeAction as AttributeRemoveAction;
+ if (attributeRemoveFix != null)
+ {
+ attributeRemoveFixes.Add(attributeRemoveFix);
+ }
+ else
+ {
+ newBatchOfFixes.Add(codeAction);
+ }
+ }
+
+ if (attributeRemoveFixes.Count > 0)
+ {
+ // Batch all of attribute removal fixes.
+ foreach (var removeSuppressionFixesForTree in attributeRemoveFixes.GroupBy(fix => fix.SyntaxTreeToModify))
+ {
+ var tree = removeSuppressionFixesForTree.Key;
+
+ var attributeRemoveFixesForTree = removeSuppressionFixesForTree.OfType().ToImmutableArray();
+ var attributesToRemove = await GetAttributeNodesToFixAsync(attributeRemoveFixesForTree, cancellationToken).ConfigureAwait(false);
+ var document = oldSolution.GetDocument(tree);
+ var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
+ var newRoot = root.RemoveNodes(attributesToRemove, SyntaxRemoveOptions.KeepLeadingTrivia);
+ currentSolution = currentSolution.WithDocumentSyntaxRoot(document.Id, newRoot);
+ }
+
+ // This is a temporary generated code action, which doesn't need telemetry, hence suppressing RS0005.
+#pragma warning disable RS0005 // Do not use generic CodeAction.Create to create CodeAction
+ var batchAttributeRemoveFix = Create(
+ attributeRemoveFixes.First().Title,
+ createChangedSolution: ct => Task.FromResult(currentSolution),
+ equivalenceKey: fixAllContext.CodeActionEquivalenceKey);
+#pragma warning restore RS0005 // Do not use generic CodeAction.Create to create CodeAction
+
+ newBatchOfFixes.Insert(0, batchAttributeRemoveFix);
+ }
+
+ return await base.TryGetMergedFixAsync(newBatchOfFixes, fixAllContext).ConfigureAwait(false);
+ }
+
+ private static async Task> GetAttributeNodesToFixAsync(ImmutableArray attributeRemoveFixes, CancellationToken cancellationToken)
+ {
+ var builder = ImmutableArray.CreateBuilder(attributeRemoveFixes.Length);
+ foreach (var attributeRemoveFix in attributeRemoveFixes)
+ {
+ var attributeToRemove = await attributeRemoveFix.GetAttributeToRemoveAsync(cancellationToken).ConfigureAwait(false);
+ builder.Add(attributeToRemove);
+ }
+
+ return builder.ToImmutable();
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8955cb51d16b2b1b37040e8efe4a1c81b29f1605
--- /dev/null
+++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction.cs
@@ -0,0 +1,58 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.CodeAnalysis.CodeFixes.Suppression
+{
+ internal abstract partial class AbstractSuppressionCodeFixProvider : ISuppressionFixProvider
+ {
+ ///
+ /// Base type for remove suppression code actions.
+ ///
+ internal abstract partial class RemoveSuppressionCodeAction : AbstractSuppressionCodeAction
+ {
+ private readonly Document _document;
+ private readonly Diagnostic _diagnostic;
+ private readonly bool _forFixMultipleContext;
+
+ public static async Task CreateAsync(
+ SuppressionTargetInfo suppressionTargetInfo,
+ Document document,
+ Diagnostic diagnostic,
+ AbstractSuppressionCodeFixProvider fixer,
+ CancellationToken cancellationToken)
+ {
+ var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
+ var attribute = diagnostic.GetSuppressionInfo(compilation).Attribute;
+ if (attribute != null)
+ {
+ return AttributeRemoveAction.Create(attribute, document, diagnostic, fixer);
+ }
+ else
+ {
+ return PragmaRemoveAction.Create(suppressionTargetInfo, document, diagnostic, fixer);
+ }
+ }
+
+ protected RemoveSuppressionCodeAction(
+ Document document,
+ Diagnostic diagnostic,
+ AbstractSuppressionCodeFixProvider fixer,
+ bool forFixMultipleContext = false)
+ : base (fixer, title: string.Format(FeaturesResources.RemoveSuppressionForId, diagnostic.Id))
+ {
+ _document = document;
+ _diagnostic = diagnostic;
+ _forFixMultipleContext = forFixMultipleContext;
+ }
+
+ public abstract RemoveSuppressionCodeAction CloneForFixMultipleContext();
+ public abstract SyntaxTree SyntaxTreeToModify { get; }
+
+ public override string EquivalenceKey => FeaturesResources.RemoveSuppressionEquivalenceKeyPrefix + DiagnosticIdForEquivalenceKey;
+ protected override string DiagnosticIdForEquivalenceKey =>
+ _forFixMultipleContext ? string.Empty : _diagnostic.Id;
+ }
+ }
+}
diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction_Attribute.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction_Attribute.cs
new file mode 100644
index 0000000000000000000000000000000000000000..abdf78cf71dba3510dc53bee5c31b2f108a5dd39
--- /dev/null
+++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction_Attribute.cs
@@ -0,0 +1,83 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Editing;
+
+namespace Microsoft.CodeAnalysis.CodeFixes.Suppression
+{
+ internal abstract partial class AbstractSuppressionCodeFixProvider : ISuppressionFixProvider
+ {
+ internal abstract partial class RemoveSuppressionCodeAction
+ {
+ ///
+ /// Code action to remove suppress message attributes for remove suppression.
+ ///
+ private sealed class AttributeRemoveAction : RemoveSuppressionCodeAction
+ {
+ private readonly AttributeData _attribute;
+
+ public static AttributeRemoveAction Create(
+ AttributeData attribute,
+ Document document,
+ Diagnostic diagnostic,
+ AbstractSuppressionCodeFixProvider fixer)
+ {
+ return new AttributeRemoveAction(attribute, document, diagnostic, fixer);
+ }
+
+ private AttributeRemoveAction(
+ AttributeData attribute,
+ Document document,
+ Diagnostic diagnostic,
+ AbstractSuppressionCodeFixProvider fixer,
+ bool forFixMultipleContext = false)
+ : base(document, diagnostic, fixer, forFixMultipleContext)
+ {
+ _attribute = attribute;
+ }
+
+ public override RemoveSuppressionCodeAction CloneForFixMultipleContext()
+ {
+ return new AttributeRemoveAction(_attribute, _document, _diagnostic, Fixer, forFixMultipleContext: true);
+ }
+
+ public override SyntaxTree SyntaxTreeToModify => _attribute.ApplicationSyntaxReference.SyntaxTree;
+
+ public async Task GetAttributeToRemoveAsync(CancellationToken cancellationToken)
+ {
+ var attributeNode = await _attribute.ApplicationSyntaxReference.GetSyntaxAsync(cancellationToken).ConfigureAwait(false);
+ return Fixer.IsSingleAttributeInAttributeList(attributeNode) ?
+ attributeNode.Parent :
+ attributeNode;
+ }
+
+ protected override async Task GetChangedDocumentAsync(CancellationToken cancellationToken)
+ {
+ var attributeNode = await GetAttributeToRemoveAsync(cancellationToken).ConfigureAwait(false);
+ var document = GetDocumentWithAttribute(attributeNode);
+ if (document == null)
+ {
+ return _document;
+ }
+
+ var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
+ editor.RemoveNode(attributeNode);
+ var newProject = editor.GetChangedDocument().Project;
+ return newProject.GetDocument(_document.Id);
+ }
+
+ private Document GetDocumentWithAttribute(SyntaxNode attributeNode)
+ {
+ var tree = attributeNode.SyntaxTree;
+ if (_document.FilePath == tree.FilePath)
+ {
+ return _document;
+ }
+
+ return _document.Project.GetDocument(tree);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction_Pragma.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction_Pragma.cs
new file mode 100644
index 0000000000000000000000000000000000000000..11b66d167e5574993365e45640b3f6fae34e5047
--- /dev/null
+++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction_Pragma.cs
@@ -0,0 +1,296 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Formatting;
+using Microsoft.CodeAnalysis.Text;
+using Roslyn.Utilities;
+
+namespace Microsoft.CodeAnalysis.CodeFixes.Suppression
+{
+ internal abstract partial class AbstractSuppressionCodeFixProvider : ISuppressionFixProvider
+ {
+ internal abstract partial class RemoveSuppressionCodeAction
+ {
+ ///
+ /// Code action to edit/remove/add the pragma directives for removing diagnostic suppression.
+ ///
+ private class PragmaRemoveAction : RemoveSuppressionCodeAction, IPragmaBasedCodeAction
+ {
+ private readonly SuppressionTargetInfo _suppressionTargetInfo;
+
+ public static PragmaRemoveAction Create(
+ SuppressionTargetInfo suppressionTargetInfo,
+ Document document,
+ Diagnostic diagnostic,
+ AbstractSuppressionCodeFixProvider fixer)
+ {
+ // We need to normalize the leading trivia on start token to account for
+ // the trailing trivia on its previous token (and similarly normalize trailing trivia for end token).
+ NormalizeTriviaOnTokens(fixer, ref document, ref suppressionTargetInfo);
+
+ return new PragmaRemoveAction(suppressionTargetInfo, document, diagnostic, fixer);
+ }
+
+ private PragmaRemoveAction(
+ SuppressionTargetInfo suppressionTargetInfo,
+ Document document,
+ Diagnostic diagnostic,
+ AbstractSuppressionCodeFixProvider fixer,
+ bool forFixMultipleContext = false)
+ : base(document, diagnostic, fixer, forFixMultipleContext)
+ {
+ _suppressionTargetInfo = suppressionTargetInfo;
+ }
+
+ public override RemoveSuppressionCodeAction CloneForFixMultipleContext()
+ {
+ return new PragmaRemoveAction(_suppressionTargetInfo, _document, _diagnostic, Fixer, forFixMultipleContext: true);
+ }
+
+ public override SyntaxTree SyntaxTreeToModify => _suppressionTargetInfo.StartToken.SyntaxTree;
+
+ protected async override Task GetChangedDocumentAsync(CancellationToken cancellationToken)
+ {
+ return await GetChangedDocumentAsync(includeStartTokenChange: true, includeEndTokenChange: true, cancellationToken: cancellationToken).ConfigureAwait(false);
+ }
+
+ public async Task GetChangedDocumentAsync(bool includeStartTokenChange, bool includeEndTokenChange, CancellationToken cancellationToken)
+ {
+ bool add = false;
+ bool toggle = false;
+
+ int indexOfLeadingPragmaDisableToRemove = -1, indexOfTrailingPragmaEnableToRemove = -1;
+ if (CanRemovePragmaTrivia(_suppressionTargetInfo.StartToken, _diagnostic, Fixer, isStartToken: true, indexOfTriviaToRemove: out indexOfLeadingPragmaDisableToRemove) &&
+ CanRemovePragmaTrivia(_suppressionTargetInfo.EndToken, _diagnostic, Fixer, isStartToken: false, indexOfTriviaToRemove: out indexOfTrailingPragmaEnableToRemove))
+ {
+ // Verify if there is no other trivia before the start token would again cause this diagnostic to be suppressed.
+ // If invalidated, then we just toggle existing pragma enable and disable directives before and start of the line.
+ // If not, then we just remove the existing pragma trivia surrounding the line.
+ toggle = await IsDiagnosticSuppressedBeforeLeadingPragmaAsync(indexOfLeadingPragmaDisableToRemove, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ // Otherwise, just add a pragma enable before the start token and a pragma restore after it.
+ add = true;
+ }
+
+ Func getNewStartToken = (startToken, currentDiagnosticSpan) => includeStartTokenChange ?
+ GetNewTokenWithModifiedPragma(startToken, currentDiagnosticSpan, add, toggle, indexOfLeadingPragmaDisableToRemove, isStartToken: true) :
+ startToken;
+
+ Func getNewEndToken = (endToken, currentDiagnosticSpan) => includeEndTokenChange ?
+ GetNewTokenWithModifiedPragma(endToken, currentDiagnosticSpan, add, toggle, indexOfTrailingPragmaEnableToRemove, isStartToken: false) :
+ endToken;
+
+ return await PragmaHelpers.GetChangeDocumentWithPragmaAdjustedAsync(
+ _document,
+ _diagnostic.Location.SourceSpan,
+ _suppressionTargetInfo,
+ getNewStartToken,
+ getNewEndToken,
+ cancellationToken).ConfigureAwait(false);
+ }
+
+ private static SyntaxTriviaList GetTriviaListForSuppression(SyntaxToken token, bool isStartToken, AbstractSuppressionCodeFixProvider fixer)
+ {
+ return isStartToken || fixer.IsEndOfFileToken(token) ?
+ token.LeadingTrivia :
+ token.TrailingTrivia;
+ }
+
+ private static SyntaxToken UpdateTriviaList(SyntaxToken token, bool isStartToken, SyntaxTriviaList triviaList, AbstractSuppressionCodeFixProvider fixer)
+ {
+ return isStartToken || fixer.IsEndOfFileToken(token) ?
+ token.WithLeadingTrivia(triviaList) :
+ token.WithTrailingTrivia(triviaList);
+ }
+
+ private static bool CanRemovePragmaTrivia(SyntaxToken token, Diagnostic diagnostic, AbstractSuppressionCodeFixProvider fixer, bool isStartToken, out int indexOfTriviaToRemove)
+ {
+ indexOfTriviaToRemove = -1;
+
+ var triviaList = GetTriviaListForSuppression(token, isStartToken, fixer);
+
+ var diagnosticSpan = diagnostic.Location.SourceSpan;
+ Func shouldIncludeTrivia = t => isStartToken ? t.FullSpan.End <= diagnosticSpan.Start : t.FullSpan.Start >= diagnosticSpan.End;
+ var filteredTriviaList = triviaList.Where(shouldIncludeTrivia);
+ if (isStartToken)
+ {
+ // Walk bottom up for leading trivia.
+ filteredTriviaList = filteredTriviaList.Reverse();
+ }
+
+ foreach (var trivia in filteredTriviaList)
+ {
+ bool isEnableDirective, hasMultipleIds;
+ if (fixer.IsAnyPragmaDirectiveForId(trivia, diagnostic.Id, out isEnableDirective, out hasMultipleIds))
+ {
+ if (hasMultipleIds)
+ {
+ // Handle only simple cases where we have a single pragma directive with single ID matching ours in the trivia.
+ return false;
+ }
+
+ // We want to look for leading disable directive and trailing enable directive.
+ if ((isStartToken && !isEnableDirective) ||
+ (!isStartToken && isEnableDirective))
+ {
+ indexOfTriviaToRemove = triviaList.IndexOf(trivia);
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ return false;
+ }
+
+ private SyntaxToken GetNewTokenWithModifiedPragma(SyntaxToken token, TextSpan currentDiagnosticSpan, bool add, bool toggle, int indexOfTriviaToRemoveOrToggle, bool isStartToken)
+ {
+ return add ?
+ GetNewTokenWithAddedPragma(token, currentDiagnosticSpan, isStartToken) :
+ GetNewTokenWithRemovedOrToggledPragma(token, indexOfTriviaToRemoveOrToggle, isStartToken, toggle);
+ }
+
+ private SyntaxToken GetNewTokenWithAddedPragma(SyntaxToken token, TextSpan currentDiagnosticSpan, bool isStartToken)
+ {
+ if (isStartToken)
+ {
+ return PragmaHelpers.GetNewStartTokenWithAddedPragma(token, currentDiagnosticSpan, _diagnostic, Fixer, FormatNode, isRemoveSuppression: true);
+ }
+ else
+ {
+ return PragmaHelpers.GetNewEndTokenWithAddedPragma(token, currentDiagnosticSpan, _diagnostic, Fixer, FormatNode, isRemoveSuppression: true);
+ }
+ }
+
+ private SyntaxToken GetNewTokenWithRemovedOrToggledPragma(SyntaxToken token, int indexOfTriviaToRemoveOrToggle, bool isStartToken, bool toggle)
+ {
+ if (isStartToken)
+ {
+ return GetNewTokenWithPragmaUnsuppress(token, indexOfTriviaToRemoveOrToggle, _diagnostic, Fixer, isStartToken, toggle);
+ }
+ else
+ {
+ return GetNewTokenWithPragmaUnsuppress(token, indexOfTriviaToRemoveOrToggle, _diagnostic, Fixer, isStartToken, toggle);
+ }
+ }
+
+ private static SyntaxToken GetNewTokenWithPragmaUnsuppress(SyntaxToken token, int indexOfTriviaToRemoveOrToggle, Diagnostic diagnostic, AbstractSuppressionCodeFixProvider fixer, bool isStartToken, bool toggle)
+ {
+ Contract.ThrowIfFalse(indexOfTriviaToRemoveOrToggle >= 0);
+
+ var triviaList = GetTriviaListForSuppression(token, isStartToken, fixer);
+
+ if (toggle)
+ {
+ var triviaToToggle = triviaList.ElementAt(indexOfTriviaToRemoveOrToggle);
+ Contract.ThrowIfFalse(triviaToToggle != default(SyntaxTrivia));
+ var toggledTrivia = fixer.TogglePragmaDirective(triviaToToggle);
+ triviaList = triviaList.Replace(triviaToToggle, toggledTrivia);
+ }
+ else
+ {
+ triviaList = triviaList.RemoveAt(indexOfTriviaToRemoveOrToggle);
+ }
+
+ return UpdateTriviaList(token, isStartToken, triviaList, fixer);
+ }
+
+ private async Task IsDiagnosticSuppressedBeforeLeadingPragmaAsync(int indexOfPragma, CancellationToken cancellationToken)
+ {
+ var model = await _document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
+ var tree = model.SyntaxTree;
+
+ // get the warning state of this diagnostic ID at the start of the pragma
+ var trivia = _suppressionTargetInfo.StartToken.LeadingTrivia.ElementAt(indexOfPragma);
+ var spanToCheck = new TextSpan(
+ start: Math.Max(0, trivia.Span.Start - 1),
+ length: 1);
+ var locationToCheck = Location.Create(tree, spanToCheck);
+ var dummyDiagnosticWithLocationToCheck = Diagnostic.Create(_diagnostic.Descriptor, locationToCheck);
+ var effectiveDiagnostic = CompilationWithAnalyzers.GetEffectiveDiagnostics(new[] { dummyDiagnosticWithLocationToCheck }, model.Compilation).FirstOrDefault();
+ return effectiveDiagnostic == null || effectiveDiagnostic.IsSuppressed;
+ }
+
+ public SyntaxToken StartToken_TestOnly => _suppressionTargetInfo.StartToken;
+ public SyntaxToken EndToken_TestOnly => _suppressionTargetInfo.EndToken;
+
+ private SyntaxNode FormatNode(SyntaxNode node)
+ {
+ return Formatter.Format(node, _document.Project.Solution.Workspace);
+ }
+
+ private static void NormalizeTriviaOnTokens(AbstractSuppressionCodeFixProvider fixer, ref Document document, ref SuppressionTargetInfo suppressionTargetInfo)
+ {
+ var startToken = suppressionTargetInfo.StartToken;
+ var endToken = suppressionTargetInfo.EndToken;
+ var nodeWithTokens = suppressionTargetInfo.NodeWithTokens;
+ var startAndEndTokensAreSame = startToken == endToken;
+
+ var previousOfStart = startToken.GetPreviousToken();
+ var nextOfEnd = endToken.GetNextToken();
+ if (!previousOfStart.HasTrailingTrivia && !nextOfEnd.HasLeadingTrivia)
+ {
+ return;
+ }
+
+ var root = nodeWithTokens.SyntaxTree.GetRoot();
+ var subtreeRoot = root.FindNode(new TextSpan(previousOfStart.FullSpan.Start, nextOfEnd.FullSpan.End - previousOfStart.FullSpan.Start));
+
+ var currentStartToken = startToken;
+ var currentEndToken = endToken;
+ var newStartToken = startToken.WithLeadingTrivia(previousOfStart.TrailingTrivia.Concat(startToken.LeadingTrivia));
+
+ SyntaxToken newEndToken = currentEndToken;
+ if (startAndEndTokensAreSame)
+ {
+ newEndToken = newStartToken;
+ }
+
+ newEndToken = newEndToken.WithTrailingTrivia(endToken.TrailingTrivia.Concat(nextOfEnd.LeadingTrivia));
+
+ var newPreviousOfStart = previousOfStart.WithTrailingTrivia();
+ var newNextOfEnd = nextOfEnd.WithLeadingTrivia();
+
+ var newSubtreeRoot = subtreeRoot.ReplaceTokens(new[] { startToken, previousOfStart, endToken, nextOfEnd },
+ (o, n) =>
+ {
+ if (o == currentStartToken)
+ {
+ return newStartToken;
+ }
+ else if (o == previousOfStart)
+ {
+ return newPreviousOfStart;
+ }
+ else if (o == currentEndToken)
+ {
+ return newEndToken;
+ }
+ else if (o == nextOfEnd)
+ {
+ return newNextOfEnd;
+ }
+ else
+ {
+ return n;
+ }
+ });
+
+ root = root.ReplaceNode(subtreeRoot, newSubtreeRoot);
+ document = document.WithSyntaxRoot(root);
+ suppressionTargetInfo.StartToken = root.FindToken(startToken.SpanStart);
+ suppressionTargetInfo.EndToken = root.FindToken(endToken.SpanStart);
+ suppressionTargetInfo.NodeWithTokens = fixer.GetNodeWithTokens(suppressionTargetInfo.StartToken, suppressionTargetInfo.EndToken, root);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.cs
index 1968dd42e304985845b584e327ed81674e9afd67..1805696480cd34406cb925a9ba4c7658df7795d3 100644
--- a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.cs
+++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.cs
@@ -1,12 +1,10 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System;
using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
@@ -36,13 +34,13 @@ public FixAllProvider GetFixAllProvider()
return SuppressionFixAllProvider.Instance;
}
- public bool CanBeSuppressed(Diagnostic diagnostic)
+ public bool CanBeSuppressedOrUnsuppressed(Diagnostic diagnostic)
{
- return SuppressionHelpers.CanBeSuppressed(diagnostic);
+ return SuppressionHelpers.CanBeSuppressed(diagnostic) || SuppressionHelpers.CanBeUnsuppressed(diagnostic);
}
- protected abstract SyntaxTriviaList CreatePragmaDisableDirectiveTrivia(Diagnostic diagnostic, bool needsLeadingEndOfLine);
- protected abstract SyntaxTriviaList CreatePragmaRestoreDirectiveTrivia(Diagnostic diagnostic, bool needsTrailingEndOfLine);
+ protected abstract SyntaxTriviaList CreatePragmaDisableDirectiveTrivia(Diagnostic diagnostic, Func formatNode, bool needsLeadingEndOfLine, bool needsTrailingEndOfLine);
+ protected abstract SyntaxTriviaList CreatePragmaRestoreDirectiveTrivia(Diagnostic diagnostic, Func formatNode, bool needsLeadingEndOfLine, bool needsTrailingEndOfLine);
protected abstract SyntaxNode AddGlobalSuppressMessageAttribute(SyntaxNode newRoot, ISymbol targetSymbol, Diagnostic diagnostic);
@@ -51,6 +49,9 @@ public bool CanBeSuppressed(Diagnostic diagnostic)
protected abstract bool IsAttributeListWithAssemblyAttributes(SyntaxNode node);
protected abstract bool IsEndOfLine(SyntaxTrivia trivia);
protected abstract bool IsEndOfFileToken(SyntaxToken token);
+ protected abstract bool IsSingleAttributeInAttributeList(SyntaxNode attribute);
+ protected abstract bool IsAnyPragmaDirectiveForId(SyntaxTrivia trivia, string id, out bool enableDirective, out bool hasMultipleIds);
+ protected abstract SyntaxTrivia TogglePragmaDirective(SyntaxTrivia trivia);
protected string GlobalSuppressionsFileHeaderComment
{
@@ -72,19 +73,19 @@ protected virtual SyntaxToken GetAdjustedTokenForPragmaRestore(SyntaxToken token
public Task> GetSuppressionsAsync(Document document, TextSpan span, IEnumerable diagnostics, CancellationToken cancellationToken)
{
- return GetSuppressionsAsync(document, span, diagnostics, skipSuppressMessage: false, cancellationToken: cancellationToken);
+ return GetSuppressionsAsync(document, span, diagnostics, skipSuppressMessage: false, skipUnsuppress: false, cancellationToken: cancellationToken);
}
internal async Task> GetPragmaSuppressionsAsync(Document document, TextSpan span, IEnumerable diagnostics, CancellationToken cancellationToken)
{
- var codeFixes = await GetSuppressionsAsync(document, span, diagnostics, skipSuppressMessage: true, cancellationToken: cancellationToken).ConfigureAwait(false);
+ var codeFixes = await GetSuppressionsAsync(document, span, diagnostics, skipSuppressMessage: true, skipUnsuppress: true, cancellationToken: cancellationToken).ConfigureAwait(false);
return codeFixes.SelectMany(fix => fix.Action.GetCodeActions()).OfType();
}
- private async Task> GetSuppressionsAsync(Document document, TextSpan span, IEnumerable diagnostics, bool skipSuppressMessage, CancellationToken cancellationToken)
+ private async Task> GetSuppressionsAsync(Document document, TextSpan span, IEnumerable diagnostics, bool skipSuppressMessage, bool skipUnsuppress, CancellationToken cancellationToken)
{
- // We only care about diagnostics that can be suppressed.
- diagnostics = diagnostics.Where(CanBeSuppressed);
+ // We only care about diagnostics that can be suppressed/unsuppressed.
+ diagnostics = diagnostics.Where(CanBeSuppressedOrUnsuppressed);
if (diagnostics.IsEmpty())
{
return SpecializedCollections.EmptyEnumerable();
@@ -106,25 +107,33 @@ private async Task> GetSuppressionsAsync(Document document,
var result = new List();
foreach (var diagnostic in diagnostics)
{
- var nestedActions = new List();
+ if (!diagnostic.IsSuppressed)
+ {
+ var nestedActions = new List();
+
+ // pragma warning disable.
+ nestedActions.Add(new PragmaWarningCodeAction(suppressionTargetInfo, document, diagnostic, this));
- // pragma warning disable.
- nestedActions.Add(new PragmaWarningCodeAction(this, suppressionTargetInfo.StartToken, suppressionTargetInfo.EndToken, suppressionTargetInfo.NodeWithTokens, document, diagnostic));
+ // SuppressMessageAttribute suppression is not supported for compiler diagnostics.
+ if (!skipSuppressMessage && !SuppressionHelpers.IsCompilerDiagnostic(diagnostic))
+ {
+ // global assembly-level suppress message attribute.
+ nestedActions.Add(new GlobalSuppressMessageCodeAction(suppressionTargetInfo.TargetSymbol, document.Project, diagnostic, this));
+ }
- // SuppressMessageAttribute suppression is not supported for compiler diagnostics.
- if (!skipSuppressMessage && !SuppressionHelpers.IsCompilerDiagnostic(diagnostic))
+ result.Add(new CodeFix(new SuppressionCodeAction(diagnostic, nestedActions), diagnostic));
+ }
+ else if (!skipUnsuppress)
{
- // global assembly-level suppress message attribute.
- nestedActions.Add(new GlobalSuppressMessageCodeAction(this, suppressionTargetInfo.TargetSymbol, document.Project, diagnostic));
+ var codeAcion = await RemoveSuppressionCodeAction.CreateAsync(suppressionTargetInfo, document, diagnostic, this, cancellationToken).ConfigureAwait(false);
+ result.Add(new CodeFix(codeAcion, diagnostic));
}
-
- result.Add(new CodeFix(new SuppressionCodeAction(diagnostic, nestedActions), diagnostic));
}
return result;
}
- private class SuppressionTargetInfo
+ internal class SuppressionTargetInfo
{
public ISymbol TargetSymbol { get; set; }
public SyntaxToken StartToken { get; set; }
@@ -165,15 +174,7 @@ private async Task GetSuppressionTargetInfoAsync(Document
var endToken = root.FindToken(lineAtPos.End);
endToken = GetAdjustedTokenForPragmaRestore(endToken, root, lines, indexOfLine);
- SyntaxNode nodeWithTokens = null;
- if (IsEndOfFileToken(endToken))
- {
- nodeWithTokens = root;
- }
- else
- {
- nodeWithTokens = startToken.GetCommonRoot(endToken);
- }
+ var nodeWithTokens = GetNodeWithTokens(startToken, endToken, root);
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var syntaxFacts = document.GetLanguageService();
@@ -229,6 +230,18 @@ private async Task GetSuppressionTargetInfoAsync(Document
return new SuppressionTargetInfo() { TargetSymbol = targetSymbol, NodeWithTokens = nodeWithTokens, StartToken = startToken, EndToken = endToken };
}
+ internal SyntaxNode GetNodeWithTokens(SyntaxToken startToken, SyntaxToken endToken, SyntaxNode root)
+ {
+ if (IsEndOfFileToken(endToken))
+ {
+ return root;
+ }
+ else
+ {
+ return startToken.GetCommonRoot(endToken);
+ }
+ }
+
protected string GetScopeString(SymbolKind targetSymbolKind)
{
switch (targetSymbolKind)
diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/ISuppressionFixProvider.cs b/src/Features/Core/Portable/CodeFixes/Suppression/ISuppressionFixProvider.cs
index 16431c72f127831baaa950ad96d8732ba7e5c268..4c76ed21e1ab3da6f3720ca75cdbd8380b74f9eb 100644
--- a/src/Features/Core/Portable/CodeFixes/Suppression/ISuppressionFixProvider.cs
+++ b/src/Features/Core/Portable/CodeFixes/Suppression/ISuppressionFixProvider.cs
@@ -11,12 +11,12 @@ namespace Microsoft.CodeAnalysis.CodeFixes.Suppression
internal interface ISuppressionFixProvider
{
///
- /// Returns true if the given diagnostic can be suppressed or triaged.
+ /// Returns true if the given diagnostic can be suppressed or unsuppressed.
///
- bool CanBeSuppressed(Diagnostic diagnostic);
+ bool CanBeSuppressedOrUnsuppressed(Diagnostic diagnostic);
///
- /// Gets one or more suppression or triage fixes for the specified diagnostics represented as a list of 's.
+ /// Gets one or more add suppression or remove suppression fixes for the specified diagnostics represented as a list of 's.
///
/// A list of zero or more potential 'es. It is also safe to return null if there are none.
Task> GetSuppressionsAsync(Document document, TextSpan span, IEnumerable diagnostics, CancellationToken cancellationToken);
diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/NestedSuppressionCodeAction.cs b/src/Features/Core/Portable/CodeFixes/Suppression/NestedSuppressionCodeAction.cs
index b41fec7a76ee1a7116e1f9d39b91216a64e541e1..d7b32921e070a9679859aed9231e39f28a2c5d5d 100644
--- a/src/Features/Core/Portable/CodeFixes/Suppression/NestedSuppressionCodeAction.cs
+++ b/src/Features/Core/Portable/CodeFixes/Suppression/NestedSuppressionCodeAction.cs
@@ -16,8 +16,13 @@ protected NestedSuppressionCodeAction(string title)
public sealed override string Title => _title;
protected abstract string DiagnosticIdForEquivalenceKey { get; }
- public sealed override string EquivalenceKey => Title + DiagnosticIdForEquivalenceKey;
+ public override string EquivalenceKey => Title + DiagnosticIdForEquivalenceKey;
+
public static bool IsEquivalenceKeyForGlobalSuppression(string equivalenceKey) =>
equivalenceKey.StartsWith(FeaturesResources.SuppressWithGlobalSuppressMessage);
+ public static bool IsEquivalenceKeyForPragmaWarning(string equivalenceKey) =>
+ equivalenceKey.StartsWith(FeaturesResources.SuppressWithPragma);
+ public static bool IsEquivalenceKeyForRemoveSuppression(string equivalenceKey) =>
+ equivalenceKey.StartsWith(FeaturesResources.RemoveSuppressionEquivalenceKeyPrefix);
}
}
diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/SuppressionHelpers.cs b/src/Features/Core/Portable/CodeFixes/Suppression/SuppressionHelpers.cs
index d4ffbd7e4558f8be16286f8b7a93c7df65e3e9aa..4b0dacd93a910a9d94c8f8912362f0b07ea97cf2 100644
--- a/src/Features/Core/Portable/CodeFixes/Suppression/SuppressionHelpers.cs
+++ b/src/Features/Core/Portable/CodeFixes/Suppression/SuppressionHelpers.cs
@@ -1,16 +1,9 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
-using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.Diagnostics;
-using Microsoft.CodeAnalysis.LanguageServices;
-using Microsoft.CodeAnalysis.Shared.Extensions;
-using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CodeFixes.Suppression
@@ -19,7 +12,19 @@ internal static class SuppressionHelpers
{
public static bool CanBeSuppressed(Diagnostic diagnostic)
{
- if (diagnostic.Location.Kind != LocationKind.SourceFile || diagnostic.IsSuppressed || IsNotConfigurableDiagnostic(diagnostic))
+ return CanBeSuppressedOrUnsuppressed(diagnostic, checkCanBeSuppressed: true);
+ }
+
+ public static bool CanBeUnsuppressed(Diagnostic diagnostic)
+ {
+ return CanBeSuppressedOrUnsuppressed(diagnostic, checkCanBeSuppressed: false);
+ }
+
+ private static bool CanBeSuppressedOrUnsuppressed(Diagnostic diagnostic, bool checkCanBeSuppressed)
+ {
+ if (diagnostic.Location.Kind != LocationKind.SourceFile ||
+ (diagnostic.IsSuppressed == checkCanBeSuppressed) ||
+ IsNotConfigurableDiagnostic(diagnostic))
{
// Don't offer suppression fixes for:
// 1. Diagnostics without a source location.
@@ -31,6 +36,7 @@ public static bool CanBeSuppressed(Diagnostic diagnostic)
switch (diagnostic.Severity)
{
case DiagnosticSeverity.Hidden:
+ // Hidden diagnostics should never show up.
return false;
case DiagnosticSeverity.Error:
diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/WrapperCodeFixProvider.cs b/src/Features/Core/Portable/CodeFixes/Suppression/WrapperCodeFixProvider.cs
index 675538127d1c9123eab4bf2a83c93e7b870518e1..729b9cb964f42be7a48864babf36d532da02fb21 100644
--- a/src/Features/Core/Portable/CodeFixes/Suppression/WrapperCodeFixProvider.cs
+++ b/src/Features/Core/Portable/CodeFixes/Suppression/WrapperCodeFixProvider.cs
@@ -22,7 +22,7 @@ public WrapperCodeFixProvider(ISuppressionFixProvider suppressionFixProvider, Im
public async override Task RegisterCodeFixesAsync(CodeFixContext context)
{
- var diagnostics = context.Diagnostics.WhereAsArray(_suppressionFixProvider.CanBeSuppressed);
+ var diagnostics = context.Diagnostics.WhereAsArray(_suppressionFixProvider.CanBeSuppressedOrUnsuppressed);
var suppressionFixes = await _suppressionFixProvider.GetSuppressionsAsync(context.Document, context.Span, diagnostics, context.CancellationToken).ConfigureAwait(false);
if (suppressionFixes != null)
{
diff --git a/src/Features/Core/Portable/Features.csproj b/src/Features/Core/Portable/Features.csproj
index 8b286fd5e208ec51013cd85afbfaae057594a6f2..0b11268a455aec25e43ab1eecfc0f05f5726be61 100644
--- a/src/Features/Core/Portable/Features.csproj
+++ b/src/Features/Core/Portable/Features.csproj
@@ -107,6 +107,13 @@
+
+
+
+
+
+
+
diff --git a/src/Features/Core/Portable/FeaturesResources.Designer.cs b/src/Features/Core/Portable/FeaturesResources.Designer.cs
index 2365727012f35b287746b12b7538dda525b4f100..9e73f938ff5f81e39cdf380b332752dd8c4cc5da 100644
--- a/src/Features/Core/Portable/FeaturesResources.Designer.cs
+++ b/src/Features/Core/Portable/FeaturesResources.Designer.cs
@@ -1663,6 +1663,24 @@ internal class FeaturesResources {
}
}
+ ///
+ /// Looks up a localized string similar to Remove Suppression.
+ ///
+ internal static string RemoveSuppressionEquivalenceKeyPrefix {
+ get {
+ return ResourceManager.GetString("RemoveSuppressionEquivalenceKeyPrefix", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Remove Suppression {0}.
+ ///
+ internal static string RemoveSuppressionForId {
+ get {
+ return ResourceManager.GetString("RemoveSuppressionForId", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Remove Unnecessary Cast.
///
diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx
index 8c94f0dbd8b8ed425eb29c425547378af0c682bc..a36b54a06bf21ad8fa2b13e467a4b8b8de37f24c 100644
--- a/src/Features/Core/Portable/FeaturesResources.resx
+++ b/src/Features/Core/Portable/FeaturesResources.resx
@@ -700,6 +700,12 @@ Do you want to continue?
in Suppression File
+
+ Remove Suppression {0}
+
+
+ Remove Suppression
+
<Pending>
diff --git a/src/Features/VisualBasic/Portable/CodeFixes/Suppression/VisualBasicSuppressionCodeFixProvider.vb b/src/Features/VisualBasic/Portable/CodeFixes/Suppression/VisualBasicSuppressionCodeFixProvider.vb
index d13d3fc283aa96189aa403c677d7be3808322167..855f276d4037355b134ff3570084b16a731190e1 100644
--- a/src/Features/VisualBasic/Portable/CodeFixes/Suppression/VisualBasicSuppressionCodeFixProvider.vb
+++ b/src/Features/VisualBasic/Portable/CodeFixes/Suppression/VisualBasicSuppressionCodeFixProvider.vb
@@ -14,16 +14,16 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.Suppression
Friend Class VisualBasicSuppressionCodeFixProvider
Inherits AbstractSuppressionCodeFixProvider
- Protected Overrides Function CreatePragmaRestoreDirectiveTrivia(diagnostic As Diagnostic, needsTrailingEndOfLine As Boolean) As SyntaxTriviaList
+ Protected Overrides Function CreatePragmaRestoreDirectiveTrivia(diagnostic As Diagnostic, formatNode As Func(Of SyntaxNode, SyntaxNode), needsLeadingEndOfLine As Boolean, needsTrailingEndOfLine As Boolean) As SyntaxTriviaList
Dim errorCodes = GetErrorCodes(diagnostic)
Dim pragmaDirective = SyntaxFactory.EnableWarningDirectiveTrivia(errorCodes)
- Return CreatePragmaDirectiveTrivia(pragmaDirective, diagnostic, True, needsTrailingEndOfLine)
+ Return CreatePragmaDirectiveTrivia(pragmaDirective, diagnostic, formatNode, needsLeadingEndOfLine, needsTrailingEndOfLine)
End Function
- Protected Overrides Function CreatePragmaDisableDirectiveTrivia(diagnostic As Diagnostic, needsLeadingEndOfLine As Boolean) As SyntaxTriviaList
+ Protected Overrides Function CreatePragmaDisableDirectiveTrivia(diagnostic As Diagnostic, formatNode As Func(Of SyntaxNode, SyntaxNode), needsLeadingEndOfLine As Boolean, needsTrailingEndOfLine As Boolean) As SyntaxTriviaList
Dim errorCodes = GetErrorCodes(diagnostic)
Dim pragmaDirective = SyntaxFactory.DisableWarningDirectiveTrivia(errorCodes)
- Return CreatePragmaDirectiveTrivia(pragmaDirective, diagnostic, needsLeadingEndOfLine, True)
+ Return CreatePragmaDirectiveTrivia(pragmaDirective, diagnostic, formatNode, needsLeadingEndOfLine, needsTrailingEndOfLine)
End Function
Private Shared Function GetErrorCodes(diagnostic As Diagnostic) As SeparatedSyntaxList(Of IdentifierNameSyntax)
@@ -34,8 +34,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.Suppression
Return New SeparatedSyntaxList(Of IdentifierNameSyntax)().Add(SyntaxFactory.IdentifierName(text))
End Function
- Private Function CreatePragmaDirectiveTrivia(enableOrDisablePragmaDirective As StructuredTriviaSyntax, diagnostic As Diagnostic, needsLeadingEndOfLine As Boolean, needsTrailingEndOfLine As Boolean) As SyntaxTriviaList
- Dim pragmaDirectiveTrivia = SyntaxFactory.Trivia(enableOrDisablePragmaDirective.WithAdditionalAnnotations(Formatter.Annotation))
+ Private Function CreatePragmaDirectiveTrivia(enableOrDisablePragmaDirective As StructuredTriviaSyntax, diagnostic As Diagnostic, formatNode As Func(Of SyntaxNode, SyntaxNode), needsLeadingEndOfLine As Boolean, needsTrailingEndOfLine As Boolean) As SyntaxTriviaList
+ enableOrDisablePragmaDirective = CType(formatNode(enableOrDisablePragmaDirective), StructuredTriviaSyntax)
+ Dim pragmaDirectiveTrivia = SyntaxFactory.Trivia(enableOrDisablePragmaDirective)
Dim endOfLineTrivia = SyntaxFactory.ElasticCarriageReturnLineFeed
Dim triviaList = SyntaxFactory.TriviaList(pragmaDirectiveTrivia)
@@ -178,5 +179,64 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.Suppression
Return attributeArgumentList
End Function
+
+ Protected Overrides Function IsSingleAttributeInAttributeList(attribute As SyntaxNode) As Boolean
+ Dim attributeSyntax = TryCast(attribute, AttributeSyntax)
+ If attributeSyntax IsNot Nothing Then
+ Dim attributeList = TryCast(attributeSyntax.Parent, AttributeListSyntax)
+ Return attributeList IsNot Nothing AndAlso attributeList.Attributes.Count = 1
+ End If
+
+ Return False
+ End Function
+
+ Protected Overrides Function IsAnyPragmaDirectiveForId(trivia As SyntaxTrivia, id As String, ByRef enableDirective As Boolean, ByRef hasMultipleIds As Boolean) As Boolean
+ Dim errorCodes As SeparatedSyntaxList(Of IdentifierNameSyntax)
+
+ Select Case trivia.Kind()
+ Case SyntaxKind.DisableWarningDirectiveTrivia
+ Dim pragmaWarning = DirectCast(trivia.GetStructure(), DisableWarningDirectiveTriviaSyntax)
+ errorCodes = pragmaWarning.ErrorCodes
+ enableDirective = False
+
+ Case SyntaxKind.EnableWarningDirectiveTrivia
+ Dim pragmaWarning = DirectCast(trivia.GetStructure(), EnableWarningDirectiveTriviaSyntax)
+ errorCodes = pragmaWarning.ErrorCodes
+ enableDirective = True
+
+ Case Else
+ enableDirective = False
+ hasMultipleIds = False
+ Return False
+ End Select
+
+ hasMultipleIds = errorCodes.Count > 1
+ Return errorCodes.Any(Function(node) node.ToString = id)
+ End Function
+
+ Protected Overrides Function TogglePragmaDirective(trivia As SyntaxTrivia) As SyntaxTrivia
+ Select Case trivia.Kind()
+ Case SyntaxKind.DisableWarningDirectiveTrivia
+ Dim pragmaWarning = DirectCast(trivia.GetStructure(), DisableWarningDirectiveTriviaSyntax)
+ Dim disabledKeyword = pragmaWarning.DisableKeyword
+ Dim enabledKeyword = SyntaxFactory.Token(disabledKeyword.LeadingTrivia, SyntaxKind.EnableKeyword, disabledKeyword.TrailingTrivia)
+ Dim newPragmaWarning = SyntaxFactory.EnableWarningDirectiveTrivia(pragmaWarning.HashToken, enabledKeyword, pragmaWarning.WarningKeyword, pragmaWarning.ErrorCodes) _
+ .WithLeadingTrivia(pragmaWarning.GetLeadingTrivia) _
+ .WithTrailingTrivia(pragmaWarning.GetTrailingTrivia)
+ Return SyntaxFactory.Trivia(newPragmaWarning)
+
+ Case SyntaxKind.EnableWarningDirectiveTrivia
+ Dim pragmaWarning = DirectCast(trivia.GetStructure(), EnableWarningDirectiveTriviaSyntax)
+ Dim enabledKeyword = pragmaWarning.EnableKeyword
+ Dim disabledKeyword = SyntaxFactory.Token(enabledKeyword.LeadingTrivia, SyntaxKind.DisableKeyword, enabledKeyword.TrailingTrivia)
+ Dim newPragmaWarning = SyntaxFactory.DisableWarningDirectiveTrivia(pragmaWarning.HashToken, disabledKeyword, pragmaWarning.WarningKeyword, pragmaWarning.ErrorCodes) _
+ .WithLeadingTrivia(pragmaWarning.GetLeadingTrivia) _
+ .WithTrailingTrivia(pragmaWarning.GetTrailingTrivia)
+ Return SyntaxFactory.Trivia(newPragmaWarning)
+
+ Case Else
+ Contract.Fail()
+ End Select
+ End Function
End Class
End Namespace
diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/IVisualStudioSuppressionFixService.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/IVisualStudioSuppressionFixService.cs
index c688900d56b785f6d9a360b93cada028245b8eeb..9fa43251cc65d474b3291089a5a03aa759ca0b02 100644
--- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/IVisualStudioSuppressionFixService.cs
+++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/IVisualStudioSuppressionFixService.cs
@@ -14,7 +14,7 @@ internal interface IVisualStudioSuppressionFixService
/// Adds source suppressions for all the diagnostics in the error list, i.e. baseline all active issues.
///
/// An optional project hierarchy object in the solution explorer. If non-null, then only the diagnostics from the project will be suppressed.
- void AddSuppressions(IVsHierarchy projectHierarchyOpt);
+ bool AddSuppressions(IVsHierarchy projectHierarchyOpt);
///
/// Adds source suppressions for diagnostics.
@@ -22,13 +22,13 @@ internal interface IVisualStudioSuppressionFixService
/// If true, then only the currently selected entries in the error list will be suppressed. Otherwise, all suppressable entries in the error list will be suppressed.
/// If true, then suppressions will be generated inline in the source file. Otherwise, they will be generated in a separate global suppressions file.
/// An optional project hierarchy object in the solution explorer. If non-null, then only the diagnostics from the project will be suppressed.
- void AddSuppressions(bool selectedErrorListEntriesOnly, bool suppressInSource, IVsHierarchy projectHierarchyOpt);
+ bool AddSuppressions(bool selectedErrorListEntriesOnly, bool suppressInSource, IVsHierarchy projectHierarchyOpt);
///
/// Removes source suppressions for suppressed diagnostics.
///
/// If true, then only the currently selected entries in the error list will be unsuppressed. Otherwise, all unsuppressable entries in the error list will be unsuppressed.
/// An optional project hierarchy object in the solution explorer. If non-null, then only the diagnostics from the project will be unsuppressed.
- void RemoveSuppressions(bool selectedErrorListEntriesOnly, IVsHierarchy projectHierarchyOpt);
+ bool RemoveSuppressions(bool selectedErrorListEntriesOnly, IVsHierarchy projectHierarchyOpt);
}
}
diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/VisualStudioDiagnosticListSuppressionStateService.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/VisualStudioDiagnosticListSuppressionStateService.cs
index 85c8721598cfc07891dad24deca3003844201b97..9b5ad5bdaba7fb0be31288d1b2eac4d25da962d5 100644
--- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/VisualStudioDiagnosticListSuppressionStateService.cs
+++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/VisualStudioDiagnosticListSuppressionStateService.cs
@@ -5,13 +5,16 @@
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading;
+using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes.Suppression;
using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Shell.TableControl;
using Microsoft.VisualStudio.Shell.TableManager;
+using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.TableDataSource
{
@@ -31,6 +34,9 @@ internal class VisualStudioDiagnosticListSuppressionStateService : IVisualStudio
private int _selectedCompilerDiagnosticItems;
private int _selectedNonSuppressionStateItems;
+ private const string SynthesizedFxCopDiagnostic = "SynthesizedFxCopDiagnostic";
+ private readonly string[] SynthesizedFxCopDiagnosticCustomTags = new string[] { SynthesizedFxCopDiagnostic };
+
[ImportingConstructor]
public VisualStudioDiagnosticListSuppressionStateService(
SVsServiceProvider serviceProvider,
@@ -135,9 +141,15 @@ private static bool EntrySupportsSuppressionState(ITableEntryHandle entryHandle,
{
int index;
var roslynSnapshot = GetEntriesSnapshot(entryHandle, out index);
+ if (roslynSnapshot == null)
+ {
+ isRoslynEntry = false;
+ isCompilerDiagnosticEntry = false;
+ return IsNonRoslynEntrySupportingSuppressionState(entryHandle, out isSuppressedEntry);
+ }
var diagnosticData = roslynSnapshot?.GetItem(index)?.Primary;
- if (diagnosticData == null || !diagnosticData.HasTextSpan || SuppressionHelpers.IsNotConfigurableDiagnostic(diagnosticData))
+ if (!IsEntryWithConfigurableSuppressionState(diagnosticData))
{
isRoslynEntry = false;
isSuppressedEntry = false;
@@ -151,6 +163,30 @@ private static bool EntrySupportsSuppressionState(ITableEntryHandle entryHandle,
return true;
}
+ private static bool IsNonRoslynEntrySupportingSuppressionState(ITableEntryHandle entryHandle, out bool isSuppressedEntry)
+ {
+ string suppressionStateValue;
+ if (entryHandle.TryGetValue(SuppressionStateColumnDefinition.ColumnName, out suppressionStateValue))
+ {
+ isSuppressedEntry = suppressionStateValue == ServicesVSResources.SuppressionStateSuppressed;
+ return true;
+ }
+
+ isSuppressedEntry = false;
+ return false;
+ }
+
+ ///
+ /// Returns true if an entry's suppression state can be modified.
+ ///
+ ///
+ private static bool IsEntryWithConfigurableSuppressionState(DiagnosticData entry)
+ {
+ return entry != null &&
+ entry.HasTextSpan &&
+ !SuppressionHelpers.IsNotConfigurableDiagnostic(entry);
+ }
+
private static AbstractTableEntriesSnapshot GetEntriesSnapshot(ITableEntryHandle entryHandle)
{
int index;
@@ -168,12 +204,21 @@ private static AbstractTableEntriesSnapshot GetEntriesSnapshot(I
return snapshot as AbstractTableEntriesSnapshot;
}
+ public bool IsSynthesizedNonRoslynDiagnostic(DiagnosticData diagnostic)
+ {
+ var tags = diagnostic.CustomTags;
+ return tags != null && tags.Contains(SynthesizedFxCopDiagnostic);
+ }
+
///
/// Gets objects for error list entries, filtered based on the given parameters.
///
- public ImmutableArray GetItems(bool selectedEntriesOnly, bool isAddSuppression, bool isSuppressionInSource, bool onlyCompilerDiagnostics, CancellationToken cancellationToken)
+ public async Task> GetItemsAsync(bool selectedEntriesOnly, bool isAddSuppression, bool isSuppressionInSource, bool onlyCompilerDiagnostics, CancellationToken cancellationToken)
{
var builder = ImmutableArray.CreateBuilder();
+ Dictionary projectNameToProjectMapOpt = null;
+ Dictionary> filePathToDocumentMapOpt = null;
+
var entries = selectedEntriesOnly ? _tableControl.SelectedEntries : _tableControl.Entries;
foreach (var entryHandle in entries)
{
@@ -185,28 +230,139 @@ public ImmutableArray GetItems(bool selectedEntriesOnly, bool is
if (roslynSnapshot != null)
{
diagnosticData = roslynSnapshot.GetItem(index)?.Primary;
- if (diagnosticData != null && diagnosticData.HasTextSpan)
+ }
+ else if (!isAddSuppression)
+ {
+ // For suppression removal, we also need to handle FxCop entries.
+ bool isSuppressedEntry;
+ if (!IsNonRoslynEntrySupportingSuppressionState(entryHandle, out isSuppressedEntry) ||
+ !isSuppressedEntry)
{
- var isCompilerDiagnostic = SuppressionHelpers.IsCompilerDiagnostic(diagnosticData);
- if (onlyCompilerDiagnostics && !isCompilerDiagnostic)
+ continue;
+ }
+
+ string errorCode = null, category = null, message = null, filePath = null, projectName = null;
+ int line = -1; // FxCop only supports line, not column.
+ var location = Location.None;
+
+ if (entryHandle.TryGetValue(StandardTableColumnDefinitions.ErrorCode, out errorCode) && !string.IsNullOrEmpty(errorCode) &&
+ entryHandle.TryGetValue(StandardTableColumnDefinitions.ErrorCategory, out category) && !string.IsNullOrEmpty(category) &&
+ entryHandle.TryGetValue(StandardTableColumnDefinitions.Text, out message) && !string.IsNullOrEmpty(message) &&
+ entryHandle.TryGetValue(StandardTableColumnDefinitions.ProjectName, out projectName) && !string.IsNullOrEmpty(projectName))
+ {
+ if (projectNameToProjectMapOpt == null)
{
+ projectNameToProjectMapOpt = new Dictionary();
+ foreach (var p in _workspace.CurrentSolution.Projects)
+ {
+ projectNameToProjectMapOpt[p.Name] = p;
+ }
+ }
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ Project project;
+ if (!projectNameToProjectMapOpt.TryGetValue(projectName, out project))
+ {
+ // bail out
continue;
}
- if (isAddSuppression)
+ Document document = null;
+ var hasLocation = (entryHandle.TryGetValue(StandardTableColumnDefinitions.DocumentName, out filePath) && !string.IsNullOrEmpty(filePath)) &&
+ (entryHandle.TryGetValue(StandardTableColumnDefinitions.Line, out line) && line >= 0);
+ if (hasLocation)
{
- // Compiler diagnostics can only be suppressed in source.
- if (!diagnosticData.IsSuppressed &&
- (isSuppressionInSource || !isCompilerDiagnostic))
+ if (string.IsNullOrEmpty(filePath) || line < 0)
+ {
+ // bail out
+ continue;
+ }
+
+ ImmutableDictionary filePathMap;
+ filePathToDocumentMapOpt = filePathToDocumentMapOpt ?? new Dictionary>();
+ if (!filePathToDocumentMapOpt.TryGetValue(project, out filePathMap))
{
- builder.Add(diagnosticData);
+ filePathMap = await GetFilePathToDocumentMapAsync(project, cancellationToken).ConfigureAwait(false);
+ filePathToDocumentMapOpt[project] = filePathMap;
}
+
+ if (!filePathMap.TryGetValue(filePath, out document))
+ {
+ // bail out
+ continue;
+ }
+
+ var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
+ var linePosition = new LinePosition(line, 0);
+ var linePositionSpan = new LinePositionSpan(start: linePosition, end: linePosition);
+ var textSpan = (await tree.GetTextAsync(cancellationToken).ConfigureAwait(false)).Lines.GetTextSpan(linePositionSpan);
+ location = tree.GetLocation(textSpan);
}
- else if (diagnosticData.IsSuppressed)
+
+ Contract.ThrowIfNull(project);
+ Contract.ThrowIfFalse((document != null) == location.IsInSource);
+
+ // Create a diagnostic with correct values for fields we care about: id, category, message, isSuppressed, location
+ // and default values for the rest of the fields (not used by suppression fixer).
+ var diagnostic = Diagnostic.Create(
+ id: errorCode,
+ category: category,
+ message: message,
+ severity: DiagnosticSeverity.Warning,
+ defaultSeverity: DiagnosticSeverity.Warning,
+ isEnabledByDefault: true,
+ warningLevel: 1,
+ isSuppressed: isSuppressedEntry,
+ title: message,
+ location: location,
+ customTags: SynthesizedFxCopDiagnosticCustomTags);
+
+ diagnosticData = document != null ?
+ DiagnosticData.Create(document, diagnostic) :
+ DiagnosticData.Create(project, diagnostic);
+ }
+ }
+
+ if (IsEntryWithConfigurableSuppressionState(diagnosticData))
+ {
+ var isCompilerDiagnostic = SuppressionHelpers.IsCompilerDiagnostic(diagnosticData);
+ if (onlyCompilerDiagnostics && !isCompilerDiagnostic)
+ {
+ continue;
+ }
+
+ if (isAddSuppression)
+ {
+ // Compiler diagnostics can only be suppressed in source.
+ if (!diagnosticData.IsSuppressed &&
+ (isSuppressionInSource || !isCompilerDiagnostic))
{
builder.Add(diagnosticData);
}
}
+ else if (diagnosticData.IsSuppressed)
+ {
+ builder.Add(diagnosticData);
+ }
+ }
+ }
+
+ return builder.ToImmutable();
+ }
+
+ private static async Task> GetFilePathToDocumentMapAsync(Project project, CancellationToken cancellationToken)
+ {
+ var builder = ImmutableDictionary.CreateBuilder();
+ foreach (var document in project.Documents)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
+ var filePath = tree.FilePath;
+ if (filePath != null)
+ {
+ builder.Add(filePath, document);
}
}
diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/VisualStudioDiagnosticListTableCommandHandler.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/VisualStudioDiagnosticListTableCommandHandler.cs
index d6c8af97e6a1c58f8767625dc1afc29070d8d909..fc62bad9f655dc3ec2fa6f21ccc2c54c365fcaa2 100644
--- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/VisualStudioDiagnosticListTableCommandHandler.cs
+++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/VisualStudioDiagnosticListTableCommandHandler.cs
@@ -29,7 +29,12 @@ public void Initialize(IServiceProvider serviceProvider)
var menuCommandService = (IMenuCommandService)serviceProvider.GetService(typeof(IMenuCommandService));
if (menuCommandService != null)
{
- AddSuppressionsCommandHandlers(menuCommandService);
+ // The Add/Remove suppression(s) have been moved to the VS code analysis layer, so we don't add the commands here.
+
+ // TODO: Figure out how to access menu commands registered by CodeAnalysisPackage and
+ // add the commands here if we cannot find the new command(s) in the code analysis layer.
+
+ // AddSuppressionsCommandHandlers(menuCommandService);
}
}
@@ -38,9 +43,7 @@ private void AddSuppressionsCommandHandlers(IMenuCommandService menuCommandServi
AddCommand(menuCommandService, ID.RoslynCommands.AddSuppressions, delegate { }, OnAddSuppressionsStatus);
AddCommand(menuCommandService, ID.RoslynCommands.AddSuppressionsInSource, OnAddSuppressionsInSource, OnAddSuppressionsInSourceStatus);
AddCommand(menuCommandService, ID.RoslynCommands.AddSuppressionsInSuppressionFile, OnAddSuppressionsInSuppressionFile, OnAddSuppressionsInSuppressionFileStatus);
-
- // TODO: RemoveSupressions NYI
- //AddCommand(menuCommandService, ID.RoslynCommands.RemoveSuppressions, OnRemoveSuppressions, OnRemoveSuppressionsStatus);
+ AddCommand(menuCommandService, ID.RoslynCommands.RemoveSuppressions, OnRemoveSuppressions, OnRemoveSuppressionsStatus);
}
///
diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs
index 63a05cf238288075043cee74476052ab932eea19..88dd7d9afa6ebecd43b7607de0a2ff54773a250f 100644
--- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs
+++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs
@@ -54,25 +54,43 @@ internal sealed class VisualStudioSuppressionFixService : IVisualStudioSuppressi
_tableControl = errorList?.TableControl;
}
- public void AddSuppressions(IVsHierarchy projectHierarchyOpt)
+ public bool AddSuppressions(IVsHierarchy projectHierarchyOpt)
{
+ if (_tableControl == null)
+ {
+ return false;
+ }
+
Func shouldFixInProject = GetShouldFixInProjectDelegate(_workspace, projectHierarchyOpt);
// Apply suppressions fix in global suppressions file for non-compiler diagnostics and
// in source only for compiler diagnostics.
ApplySuppressionFix(shouldFixInProject, selectedEntriesOnly: false, isAddSuppression: true, isSuppressionInSource: false, onlyCompilerDiagnostics: false, showPreviewChangesDialog: false);
ApplySuppressionFix(shouldFixInProject, selectedEntriesOnly: false, isAddSuppression: true, isSuppressionInSource: true, onlyCompilerDiagnostics: true, showPreviewChangesDialog: false);
+
+ return true;
+ }
+
+ public bool AddSuppressions(bool selectedErrorListEntriesOnly, bool suppressInSource, IVsHierarchy projectHierarchyOpt)
+ {
+ if (_tableControl == null)
+ {
+ return false;
+ }
+
+ Func shouldFixInProject = GetShouldFixInProjectDelegate(_workspace, projectHierarchyOpt);
+ return ApplySuppressionFix(shouldFixInProject, selectedErrorListEntriesOnly, isAddSuppression: true, isSuppressionInSource: suppressInSource, onlyCompilerDiagnostics: false, showPreviewChangesDialog: true);
}
- public void AddSuppressions(bool selectedErrorListEntriesOnly, bool suppressInSource, IVsHierarchy projectHierarchyOpt)
+ public bool RemoveSuppressions(bool selectedErrorListEntriesOnly, IVsHierarchy projectHierarchyOpt)
{
if (_tableControl == null)
{
- return;
+ return false;
}
Func shouldFixInProject = GetShouldFixInProjectDelegate(_workspace, projectHierarchyOpt);
- ApplySuppressionFix(shouldFixInProject, selectedErrorListEntriesOnly, isAddSuppression: true, isSuppressionInSource: suppressInSource, onlyCompilerDiagnostics: false, showPreviewChangesDialog: true);
+ return ApplySuppressionFix(shouldFixInProject, selectedErrorListEntriesOnly, isAddSuppression: false, isSuppressionInSource: false, onlyCompilerDiagnostics: false, showPreviewChangesDialog: true);
}
private static Func GetShouldFixInProjectDelegate(VisualStudioWorkspaceImpl workspace, IVsHierarchy projectHierarchyOpt)
@@ -92,35 +110,29 @@ public void AddSuppressions(bool selectedErrorListEntriesOnly, bool suppressInSo
}
}
- public void RemoveSuppressions(bool selectedErrorListEntriesOnly, IVsHierarchy projectHierarchyOpt)
- {
- if (_tableControl == null)
- {
- return;
- }
-
- // TODO
- }
-
- private void ApplySuppressionFix(Func shouldFixInProject, bool selectedEntriesOnly, bool isAddSuppression, bool isSuppressionInSource, bool onlyCompilerDiagnostics, bool showPreviewChangesDialog)
+ private bool ApplySuppressionFix(Func shouldFixInProject, bool selectedEntriesOnly, bool isAddSuppression, bool isSuppressionInSource, bool onlyCompilerDiagnostics, bool showPreviewChangesDialog)
{
ImmutableDictionary> diagnosticsToFixMap = null;
+ var waitDialogAndPreviewChangesTitle = isAddSuppression ? ServicesVSResources.SuppressMultipleOccurrences : ServicesVSResources.RemoveSuppressMultipleOccurrences;
+ var waitDialogMessage = isAddSuppression ? ServicesVSResources.ComputingSuppressionFix : ServicesVSResources.ComputingRemoveSuppressionFix;
+
// Get the diagnostics to fix from the suppression state service.
var result = _waitIndicator.Wait(
- ServicesVSResources.SuppressMultipleOccurrences,
- ServicesVSResources.ComputingSuppressionFix,
+ waitDialogAndPreviewChangesTitle,
+ waitDialogMessage,
allowCancel: true,
action: waitContext =>
{
try
{
- var diagnosticsToFix = _suppressionStateService.GetItems(
+ var diagnosticsToFix = _suppressionStateService.GetItemsAsync(
selectedEntriesOnly,
isAddSuppression,
isSuppressionInSource,
onlyCompilerDiagnostics,
- waitContext.CancellationToken);
+ waitContext.CancellationToken)
+ .WaitAndGetResult(waitContext.CancellationToken);
if (diagnosticsToFix.IsEmpty)
{
@@ -132,17 +144,28 @@ private void ApplySuppressionFix(Func shouldFixInProject, bool se
}
catch (OperationCanceledException)
{
+ diagnosticsToFixMap = null;
}
});
// Bail out if the user cancelled.
if (result == WaitIndicatorResult.Canceled ||
- diagnosticsToFixMap == null || diagnosticsToFixMap.IsEmpty)
+ diagnosticsToFixMap == null)
+ {
+ return false;
+ }
+
+ if (diagnosticsToFixMap.IsEmpty)
{
- return;
+ // Nothing to fix.
+ return true;
}
- var equivalenceKey = isSuppressionInSource ? FeaturesResources.SuppressWithPragma : FeaturesResources.SuppressWithGlobalSuppressMessage;
+ // Equivalence key determines what fix will be applied.
+ // Make sure we don't include any specific diagnostic ID, as we want all of the given diagnostics (which can have varied ID) to be fixed.
+ var equivalenceKey = isAddSuppression ?
+ (isSuppressionInSource ? FeaturesResources.SuppressWithPragma : FeaturesResources.SuppressWithGlobalSuppressMessage) :
+ FeaturesResources.RemoveSuppressionEquivalenceKeyPrefix;
// We have different suppression fixers for every language.
// So we need to group diagnostics by the containing project language and apply fixes separately.
@@ -155,8 +178,20 @@ private void ApplySuppressionFix(Func shouldFixInProject, bool se
foreach (var group in groups)
{
var language = group.Key;
- var waitDialogAndPreviewChangesTitle = hasMultipleLangauges ? string.Format(ServicesVSResources.SuppressMultipleOccurrencesForLanguage, language) : ServicesVSResources.SuppressMultipleOccurrences;
- var waitDialogMessage = hasMultipleLangauges ? string.Format(ServicesVSResources.ComputingSuppressionFixForLanguage, language) : ServicesVSResources.ComputingSuppressionFix;
+ if (hasMultipleLangauges)
+ {
+ // Change the dialog title and wait message appropriately.
+ if (isAddSuppression)
+ {
+ waitDialogAndPreviewChangesTitle = string.Format(ServicesVSResources.SuppressMultipleOccurrencesForLanguage, language);
+ waitDialogMessage = string.Format(ServicesVSResources.ComputingSuppressionFixForLanguage, language);
+ }
+ else
+ {
+ waitDialogAndPreviewChangesTitle = string.Format(ServicesVSResources.RemoveSuppressMultipleOccurrencesForLanguage, language);
+ waitDialogMessage = string.Format(ServicesVSResources.ComputingRemoveSuppressionFixForLanguage, language);
+ }
+ }
ImmutableDictionary> documentDiagnosticsPerLanguage = null;
CodeFixProvider suppressionFixer = null;
@@ -212,11 +247,13 @@ private void ApplySuppressionFix(Func shouldFixInProject, bool se
if (currentSolution == newSolution)
{
// User cancelled, so we just bail out.
- break;
+ return false;
}
needsMappingToNewSolution = true;
}
+
+ return true;
}
private async Task>> GetDiagnosticsToFixMapAsync(IEnumerable diagnosticsToFix, Func shouldFixInProject, CancellationToken cancellationToken)
@@ -273,11 +310,11 @@ private void ApplySuppressionFix(Func shouldFixInProject, bool se
if (!latestDocumentDiagnosticsMap.TryGetValue(document.Id, out latestDocumentDiagnostics))
{
// Ignore stale diagnostics in error list.
- continue;
+ latestDocumentDiagnostics = ImmutableHashSet.Empty;
}
// Filter out stale diagnostics in error list.
- var documentDiagnosticsToFix = documentDiagnostics.Value.Where(d => latestDocumentDiagnostics.Contains(d));
+ var documentDiagnosticsToFix = documentDiagnostics.Value.Where(d => latestDocumentDiagnostics.Contains(d) || _suppressionStateService.IsSynthesizedNonRoslynDiagnostic(d));
if (documentDiagnosticsToFix.IsEmpty())
{
diff --git a/src/VisualStudio/Core/Def/ServicesVSResources.Designer.cs b/src/VisualStudio/Core/Def/ServicesVSResources.Designer.cs
index 4f2ddcac918adc472cc5f1412e30917f993bc3dd..3da4e1f94fbee3490cf0b92dfb4fd9fae886e345 100644
--- a/src/VisualStudio/Core/Def/ServicesVSResources.Designer.cs
+++ b/src/VisualStudio/Core/Def/ServicesVSResources.Designer.cs
@@ -132,6 +132,24 @@ internal class ServicesVSResources {
}
}
+ ///
+ /// Looks up a localized string similar to Computing remove suppressions fix....
+ ///
+ internal static string ComputingRemoveSuppressionFix {
+ get {
+ return ResourceManager.GetString("ComputingRemoveSuppressionFix", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Computing remove suppressions fix ('{0}')....
+ ///
+ internal static string ComputingRemoveSuppressionFixForLanguage {
+ get {
+ return ResourceManager.GetString("ComputingRemoveSuppressionFixForLanguage", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Computing suppressions fix....
///
@@ -966,6 +984,24 @@ internal class ServicesVSResources {
}
}
+ ///
+ /// Looks up a localized string similar to Remove suppressions.
+ ///
+ internal static string RemoveSuppressMultipleOccurrences {
+ get {
+ return ResourceManager.GetString("RemoveSuppressMultipleOccurrences", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Remove suppressions ('{0}').
+ ///
+ internal static string RemoveSuppressMultipleOccurrencesForLanguage {
+ get {
+ return ResourceManager.GetString("RemoveSuppressMultipleOccurrencesForLanguage", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Resetting Interactive.
///
diff --git a/src/VisualStudio/Core/Def/ServicesVSResources.resx b/src/VisualStudio/Core/Def/ServicesVSResources.resx
index c33221812094639a32093390983f77ed62be9255..deb2c53b277b1243ae33a12f9954bf4df4b1f8e5 100644
--- a/src/VisualStudio/Core/Def/ServicesVSResources.resx
+++ b/src/VisualStudio/Core/Def/ServicesVSResources.resx
@@ -498,4 +498,16 @@ Use the dropdown to view and switch to other projects this file may belong to.
Computing suppressions fix ('{0}')...
+
+ Remove suppressions
+
+
+ Remove suppressions ('{0}')
+
+
+ Computing remove suppressions fix...
+
+
+ Computing remove suppressions fix ('{0}')...
+
\ No newline at end of file