提交 f07d04c7 编写于 作者: H Heejae Chang

Merge branch 'master' of https://github.com/dotnet/roslyn into missingerror

......@@ -15,7 +15,7 @@
<PropertyGroup>
<ToolsetCompilerPackageName>Microsoft.Net.Compilers</ToolsetCompilerPackageName>
<ToolsetCompilerPropsFilePath>$(NuGetPackageRoot)\Microsoft.Net.Compilers\1.1.0-beta1-20150727-01\build\Microsoft.Net.Compilers.props</ToolsetCompilerPropsFilePath>
<RoslynDiagnosticsPropsFilePath>$(NuGetPackageRoot)\Microsoft.Net.RoslynDiagnostics\1.1.1-beta1-20150814-01\build\Microsoft.Net.RoslynDiagnostics.props</RoslynDiagnosticsPropsFilePath>
<RoslynDiagnosticsPropsFilePath>$(NuGetPackageRoot)\Microsoft.Net.RoslynDiagnostics\1.1.1-beta1-20150818-01\build\Microsoft.Net.RoslynDiagnostics.props</RoslynDiagnosticsPropsFilePath>
<AdditionalFileItemNames>$(AdditionalFileItemNames);PublicAPI</AdditionalFileItemNames>
</PropertyGroup>
......
......@@ -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",
......
......@@ -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"
......
......@@ -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
......
......@@ -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.
......@@ -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);
}
......
......@@ -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<Func<int, int>> 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<Func<int, int>> 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<Func<int, int>> x = y => y = y;
Diagnostic(ErrorCode.ERR_ExpressionTreeContainsAssignment, "y = y").WithLocation(9, 45));
}
}
}
......@@ -52,6 +52,7 @@
<Compile Include="CaseInsensitiveComparison.cs" />
<Compile Include="DiagnosticAnalyzer\AnalyzerDriver.CompilationData.cs" />
<Compile Include="DiagnosticAnalyzer\SuppressMessageInfo.cs" />
<Compile Include="Diagnostic\SuppressionInfo.cs" />
<Compile Include="InternalUtilities\StreamExtensions.cs" />
<Compile Include="UnicodeCharacterUtilities.cs" />
<Compile Include="CodeAnalysisResources.Designer.cs">
......
......@@ -283,6 +283,26 @@ internal static Diagnostic Create(DiagnosticInfo info)
/// </summary>
public abstract bool IsSuppressed { get; }
/// <summary>
/// Gets the <see cref="SuppressionInfo"/> for suppressed diagnostics, i.e. <see cref="IsSuppressed"/> = true.
/// Otherwise, returns null.
/// </summary>
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);
}
/// <summary>
/// Returns true if this diagnostic is enabled by default by the author of the diagnostic.
/// </summary>
......
// 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
{
/// <summary>
/// Contains information about the source of diagnostic suppression.
/// </summary>
public class SuppressionInfo
{
/// <summary>
/// <see cref="Diagnostic.Id"/> of the suppressed diagnostic.
/// </summary>
public string Id { get; }
/// <summary>
/// If the diagnostic was suppressed by an attribute, then returns that attribute.
/// Otherwise, returns null.
/// </summary>
public AttributeData Attribute { get; }
internal SuppressionInfo(string id, AttributeData attribute)
{
Id = id;
Attribute = attribute;
}
}
}
......@@ -46,7 +46,7 @@ public void AddGlobalSymbolSuppression(ISymbol symbol, SuppressMessageInfo info)
}
else
{
suppressions = new Dictionary<string, SuppressMessageInfo>() {{ info.Id, info }};
suppressions = new Dictionary<string, SuppressMessageInfo>() { { 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<string>("Scope", SpecialType.System_String);
info.Target = attribute.DecodeNamedArgument<string>("Target", SpecialType.System_String);
info.MessageId = attribute.DecodeNamedArgument<string>("MessageId", SpecialType.System_String);
info.Attribute = attribute;
return true;
}
......
......@@ -8,5 +8,6 @@ internal struct SuppressMessageInfo
public string Scope;
public string Target;
public string MessageId;
public AttributeData Attribute;
}
}
......@@ -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<Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer>
......@@ -29,6 +30,9 @@ Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzersOptions.ConcurrentAna
Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzersOptions.LogAnalyzerExecutionTime.get -> bool
Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzersOptions.OnAnalyzerException.get -> System.Action<System.Exception, Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer, Microsoft.CodeAnalysis.Diagnostic>
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
......
......@@ -182,6 +182,7 @@
<Compile Include="CodeActions\Preview\PreviewExceptionTests.cs" />
<Compile Include="CodeActions\Preview\PreviewTests.cs" />
<Compile Include="CodeActions\ReplaceMethodWithProperty\ReplaceMethodWithPropertyTests.cs" />
<Compile Include="Diagnostics\Suppression\RemoveSuppressionTests.cs" />
<Compile Include="Diagnostics\UseAutoProperty\UseAutoPropertyTests.cs" />
<Compile Include="CommentSelection\CSharpCommentSelectionTests.cs" />
<Compile Include="Completion\CompletionProviders\AbstractCSharpCompletionProviderTests.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}");
......
......@@ -316,8 +316,17 @@ private void GroupFixes(Workspace workspace, IEnumerable<CodeFixCollection> 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);
}
......
......@@ -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;
......
......@@ -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;
}
......
......@@ -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<Diagnostic> 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);
}
......
......@@ -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
......
......@@ -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 =
......
// 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<SyntaxNode, SyntaxNode> 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<SyntaxNode, SyntaxNode> 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<SyntaxNode, SyntaxNode> formatNode, bool needsLeadingEndOfLine, bool needsTrailingEndOfLine)
{
var id = SyntaxFactory.IdentifierName(diagnostic.Id);
var ids = new SeparatedSyntaxList<ExpressionSyntax>().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);
}
}
}
......@@ -243,7 +243,7 @@ public async Task<IEnumerable<CodeFixCollection>> GetFixesAsync(Document documen
var diagnostics = await DiagnosticData.ToDiagnosticsAsync(document.Project, diagnosticDataCollection, cancellationToken).ConfigureAwait(false);
Func<Diagnostic, bool> hasFix = (d) => lazySuppressionProvider.Value.CanBeSuppressed(d);
Func<Diagnostic, bool> hasFix = (d) => lazySuppressionProvider.Value.CanBeSuppressedOrUnsuppressed(d);
Func<ImmutableArray<Diagnostic>, Task<IEnumerable<CodeFix>>> 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<bool> 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;
}
......
......@@ -106,7 +106,7 @@ public override bool CanBeFixed(Diagnostic diagnostic)
private class SuppressionFixerFixAllProviderInfo : FixAllProviderInfo
{
private readonly Func<Diagnostic, bool> _canBeSuppressedOrTriaged;
private readonly Func<Diagnostic, bool> _canBeSuppressedOrUnsuppressed;
public SuppressionFixerFixAllProviderInfo(
FixAllProvider fixAllProvider,
......@@ -114,12 +114,12 @@ private class SuppressionFixerFixAllProviderInfo : FixAllProviderInfo
IEnumerable<FixAllScope> supportedScopes)
: base(fixAllProvider, supportedScopes)
{
this._canBeSuppressedOrTriaged = suppressionFixer.CanBeSuppressed;
this._canBeSuppressedOrUnsuppressed = suppressionFixer.CanBeSuppressedOrUnsuppressed;
}
public override bool CanBeFixed(Diagnostic diagnostic)
{
return _canBeSuppressedOrTriaged(diagnostic);
return _canBeSuppressedOrUnsuppressed(diagnostic);
}
}
}
......
......@@ -33,8 +33,12 @@ public async override Task<CodeAction> 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;
......
......@@ -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;
......
// 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
{
/// <summary>
/// Suppression code action based on pragma add/remove/edit.
/// </summary>
internal interface IPragmaBasedCodeAction
{
Task<Document> GetChangedDocumentAsync(bool includeStartTokenChange, bool includeEndTokenChange, CancellationToken cancellationToken);
}
}
}
// 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
{
/// <summary>
/// Helper methods for pragma suppression add/remove batch fixers.
/// </summary>
private static class PragmaBatchFixHelpers
{
public static CodeAction CreateBatchPragmaFix(
AbstractSuppressionCodeFixProvider suppressionFixProvider,
Document document,
ImmutableArray<IPragmaBasedCodeAction> pragmaActions,
ImmutableArray<Diagnostic> 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<Document> BatchPragmaFixesAsync(
AbstractSuppressionCodeFixProvider suppressionFixProvider,
Document document,
ImmutableArray<IPragmaBasedCodeAction> pragmaActions,
ImmutableArray<Diagnostic> 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<Diagnostic, TextSpan>();
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<IPragmaBasedCodeAction>().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<IEnumerable<TextChange>> GetChangedDocumentAsync(
IPragmaBasedCodeAction pragmaAction,
Document currentDocument,
ImmutableArray<Diagnostic> diagnostics,
Dictionary<Diagnostic, TextSpan> 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<Diagnostic> diagnostics, Dictionary<Diagnostic, TextSpan> 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<Diagnostic> diagnostics, Dictionary<Diagnostic, TextSpan> currentDiagnosticSpans, TextChange textChange)
{
var isAdd = textChange.Span.Length == 0;
Func<TextSpan, bool> isPriorSpan = span => span.End <= textChange.Span.Start;
Func<TextSpan, bool> isFollowingSpan = span => span.Start >= textChange.Span.End;
Func<TextSpan, bool> 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);
}
}
}
}
}
}
}
// 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
{
/// <summary>
/// Helper methods for pragma based suppression code actions.
/// </summary>
private static class PragmaHelpers
{
internal async static Task<Document> GetChangeDocumentWithPragmaAdjustedAsync(
Document document,
TextSpan diagnosticSpan,
SuppressionTargetInfo suppressionTargetInfo,
Func<SyntaxToken, TextSpan, SyntaxToken> getNewStartToken,
Func<SyntaxToken, TextSpan, SyntaxToken> 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<SyntaxTrivia> 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<int, int> getNextIndex = cur => isStartToken ? cur - 1 : cur + 1;
Func<SyntaxTrivia, bool> 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<SyntaxNode, SyntaxNode> 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<SyntaxNode, SyntaxNode> formatNode, bool isRemoveSuppression = false)
{
ImmutableArray<SyntaxTrivia> 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));
};
}
}
}
}
// 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
{
/// <summary>
/// Batch fixer for pragma suppress code action.
/// </summary>
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<Diagnostic> diagnostics, Action<CodeAction> addFix, FixAllContext fixAllContext)
{
foreach (var diagnosticsForSpan in diagnostics.Where(d => d.Location.IsInSource).GroupBy(d => d.Location.SourceSpan))
var pragmaActionsBuilder = ImmutableArray.CreateBuilder<IPragmaBasedCodeAction>();
var pragmaDiagnosticsBuilder = ImmutableArray.CreateBuilder<Diagnostic>();
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);
}
}
}
}
......
// 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<Document> 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<Document> 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<SyntaxTrivia> 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;
}
}
}
// 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);
}
/// <summary>
/// Batch fixer for pragma suppression removal code action.
/// </summary>
private sealed class BatchFixer : BatchFixAllProvider
{
private readonly AbstractSuppressionCodeFixProvider _suppressionFixProvider;
public BatchFixer(AbstractSuppressionCodeFixProvider suppressionFixProvider)
{
_suppressionFixProvider = suppressionFixProvider;
}
public override async Task AddDocumentFixesAsync(Document document, ImmutableArray<Diagnostic> diagnostics, Action<CodeAction> addFix, FixAllContext fixAllContext)
{
// Batch all the pragma remove suppression fixes by executing them sequentially for the document.
var pragmaActionsBuilder = ImmutableArray.CreateBuilder<IPragmaBasedCodeAction>();
var pragmaDiagnosticsBuilder = ImmutableArray.CreateBuilder<Diagnostic>();
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<CodeAction> TryGetMergedFixAsync(IEnumerable<CodeAction> 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<AttributeRemoveAction>();
var newBatchOfFixes = new List<CodeAction>();
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<AttributeRemoveAction>().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<ImmutableArray<SyntaxNode>> GetAttributeNodesToFixAsync(ImmutableArray<AttributeRemoveAction> attributeRemoveFixes, CancellationToken cancellationToken)
{
var builder = ImmutableArray.CreateBuilder<SyntaxNode>(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
// 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
{
/// <summary>
/// Base type for remove suppression code actions.
/// </summary>
internal abstract partial class RemoveSuppressionCodeAction : AbstractSuppressionCodeAction
{
private readonly Document _document;
private readonly Diagnostic _diagnostic;
private readonly bool _forFixMultipleContext;
public static async Task<RemoveSuppressionCodeAction> 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;
}
}
}
// 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
{
/// <summary>
/// Code action to remove suppress message attributes for remove suppression.
/// </summary>
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<SyntaxNode> GetAttributeToRemoveAsync(CancellationToken cancellationToken)
{
var attributeNode = await _attribute.ApplicationSyntaxReference.GetSyntaxAsync(cancellationToken).ConfigureAwait(false);
return Fixer.IsSingleAttributeInAttributeList(attributeNode) ?
attributeNode.Parent :
attributeNode;
}
protected override async Task<Document> 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
// 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
{
/// <summary>
/// Code action to edit/remove/add the pragma directives for removing diagnostic suppression.
/// </summary>
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<Document> GetChangedDocumentAsync(CancellationToken cancellationToken)
{
return await GetChangedDocumentAsync(includeStartTokenChange: true, includeEndTokenChange: true, cancellationToken: cancellationToken).ConfigureAwait(false);
}
public async Task<Document> 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<SyntaxToken, TextSpan, SyntaxToken> getNewStartToken = (startToken, currentDiagnosticSpan) => includeStartTokenChange ?
GetNewTokenWithModifiedPragma(startToken, currentDiagnosticSpan, add, toggle, indexOfLeadingPragmaDisableToRemove, isStartToken: true) :
startToken;
Func<SyntaxToken, TextSpan, SyntaxToken> 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<SyntaxTrivia, bool> 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<bool> 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
// 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<SyntaxNode, SyntaxNode> formatNode, bool needsLeadingEndOfLine, bool needsTrailingEndOfLine);
protected abstract SyntaxTriviaList CreatePragmaRestoreDirectiveTrivia(Diagnostic diagnostic, Func<SyntaxNode, SyntaxNode> 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<IEnumerable<CodeFix>> GetSuppressionsAsync(Document document, TextSpan span, IEnumerable<Diagnostic> 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<IEnumerable<PragmaWarningCodeAction>> GetPragmaSuppressionsAsync(Document document, TextSpan span, IEnumerable<Diagnostic> 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<PragmaWarningCodeAction>();
}
private async Task<IEnumerable<CodeFix>> GetSuppressionsAsync(Document document, TextSpan span, IEnumerable<Diagnostic> diagnostics, bool skipSuppressMessage, CancellationToken cancellationToken)
private async Task<IEnumerable<CodeFix>> GetSuppressionsAsync(Document document, TextSpan span, IEnumerable<Diagnostic> 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<CodeFix>();
......@@ -106,25 +107,33 @@ private async Task<IEnumerable<CodeFix>> GetSuppressionsAsync(Document document,
var result = new List<CodeFix>();
foreach (var diagnostic in diagnostics)
{
var nestedActions = new List<NestedSuppressionCodeAction>();
if (!diagnostic.IsSuppressed)
{
var nestedActions = new List<NestedSuppressionCodeAction>();
// 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<SuppressionTargetInfo> 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<ISyntaxFactsService>();
......@@ -229,6 +230,18 @@ private async Task<SuppressionTargetInfo> 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)
......
......@@ -11,12 +11,12 @@ namespace Microsoft.CodeAnalysis.CodeFixes.Suppression
internal interface ISuppressionFixProvider
{
/// <summary>
/// Returns true if the given diagnostic can be suppressed or triaged.
/// Returns true if the given diagnostic can be suppressed or unsuppressed.
/// </summary>
bool CanBeSuppressed(Diagnostic diagnostic);
bool CanBeSuppressedOrUnsuppressed(Diagnostic diagnostic);
/// <summary>
/// Gets one or more suppression or triage fixes for the specified diagnostics represented as a list of <see cref="CodeAction"/>'s.
/// Gets one or more add suppression or remove suppression fixes for the specified diagnostics represented as a list of <see cref="CodeAction"/>'s.
/// </summary>
/// <returns>A list of zero or more potential <see cref="CodeFix"/>'es. It is also safe to return null if there are none.</returns>
Task<IEnumerable<CodeFix>> GetSuppressionsAsync(Document document, TextSpan span, IEnumerable<Diagnostic> diagnostics, CancellationToken cancellationToken);
......
......@@ -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);
}
}
// 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:
......
......@@ -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)
{
......
......@@ -107,6 +107,13 @@
<Compile Include="CodeFixes\FixAllOccurrences\IFixAllGetFixesService.cs" />
<Compile Include="CodeFixes\Iterator\AbstractIteratorCodeFixProvider.cs" />
<Compile Include="CodeFixes\PredefinedCodeFixProviderNames.cs" />
<Compile Include="CodeFixes\Suppression\AbstractSuppressionCodeFixProvider.IPragmaBasedCodeAction.cs" />
<Compile Include="CodeFixes\Suppression\AbstractSuppressionCodeFixProvider.PragmaBatchFixHelpers.cs" />
<Compile Include="CodeFixes\Suppression\AbstractSuppressionCodeFixProvider.PragmaHelpers.cs" />
<Compile Include="CodeFixes\Suppression\AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction.BatchFixer.cs" />
<Compile Include="CodeFixes\Suppression\AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction_Attribute.cs" />
<Compile Include="CodeFixes\Suppression\AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction_Pragma.cs" />
<Compile Include="CodeFixes\Suppression\AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction.cs" />
<Compile Include="CodeFixes\Suppression\SuppressionHelpers.cs" />
<Compile Include="CodeFixes\Suppression\AbstractSuppressionCodeFixProvider.AbstractSuppressionCodeAction.cs" />
<Compile Include="CodeFixes\Suppression\AbstractSuppressionCodeFixProvider.FixAllProvider.cs" />
......
......@@ -1663,6 +1663,24 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Remove Suppression.
/// </summary>
internal static string RemoveSuppressionEquivalenceKeyPrefix {
get {
return ResourceManager.GetString("RemoveSuppressionEquivalenceKeyPrefix", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Remove Suppression {0}.
/// </summary>
internal static string RemoveSuppressionForId {
get {
return ResourceManager.GetString("RemoveSuppressionForId", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Remove Unnecessary Cast.
/// </summary>
......
......@@ -700,6 +700,12 @@ Do you want to continue?</value>
<data name="SuppressWithGlobalSuppressMessage" xml:space="preserve">
<value>in Suppression File</value>
</data>
<data name="RemoveSuppressionForId" xml:space="preserve">
<value>Remove Suppression {0}</value>
</data>
<data name="RemoveSuppressionEquivalenceKeyPrefix" xml:space="preserve">
<value>Remove Suppression</value>
</data>
<data name="SuppressionPendingJustification" xml:space="preserve">
<value>&lt;Pending&gt;</value>
</data>
......
......@@ -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
......@@ -14,7 +14,7 @@ internal interface IVisualStudioSuppressionFixService
/// Adds source suppressions for all the diagnostics in the error list, i.e. baseline all active issues.
/// </summary>
/// <param name="projectHierarchyOpt">An optional project hierarchy object in the solution explorer. If non-null, then only the diagnostics from the project will be suppressed.</param>
void AddSuppressions(IVsHierarchy projectHierarchyOpt);
bool AddSuppressions(IVsHierarchy projectHierarchyOpt);
/// <summary>
/// Adds source suppressions for diagnostics.
......@@ -22,13 +22,13 @@ internal interface IVisualStudioSuppressionFixService
/// <param name="selectedErrorListEntriesOnly">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.</param>
/// <param name="suppressInSource">If true, then suppressions will be generated inline in the source file. Otherwise, they will be generated in a separate global suppressions file.</param>
/// <param name="projectHierarchyOpt">An optional project hierarchy object in the solution explorer. If non-null, then only the diagnostics from the project will be suppressed.</param>
void AddSuppressions(bool selectedErrorListEntriesOnly, bool suppressInSource, IVsHierarchy projectHierarchyOpt);
bool AddSuppressions(bool selectedErrorListEntriesOnly, bool suppressInSource, IVsHierarchy projectHierarchyOpt);
/// <summary>
/// Removes source suppressions for suppressed diagnostics.
/// </summary>
/// <param name="selectedErrorListEntriesOnly">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.</param>
/// <param name="projectHierarchyOpt">An optional project hierarchy object in the solution explorer. If non-null, then only the diagnostics from the project will be unsuppressed.</param>
void RemoveSuppressions(bool selectedErrorListEntriesOnly, IVsHierarchy projectHierarchyOpt);
bool RemoveSuppressions(bool selectedErrorListEntriesOnly, IVsHierarchy projectHierarchyOpt);
}
}
......@@ -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;
}
/// <summary>
/// Returns true if an entry's suppression state can be modified.
/// </summary>
/// <returns></returns>
private static bool IsEntryWithConfigurableSuppressionState(DiagnosticData entry)
{
return entry != null &&
entry.HasTextSpan &&
!SuppressionHelpers.IsNotConfigurableDiagnostic(entry);
}
private static AbstractTableEntriesSnapshot<DiagnosticData> GetEntriesSnapshot(ITableEntryHandle entryHandle)
{
int index;
......@@ -168,12 +204,21 @@ private static AbstractTableEntriesSnapshot<DiagnosticData> GetEntriesSnapshot(I
return snapshot as AbstractTableEntriesSnapshot<DiagnosticData>;
}
public bool IsSynthesizedNonRoslynDiagnostic(DiagnosticData diagnostic)
{
var tags = diagnostic.CustomTags;
return tags != null && tags.Contains(SynthesizedFxCopDiagnostic);
}
/// <summary>
/// Gets <see cref="DiagnosticData"/> objects for error list entries, filtered based on the given parameters.
/// </summary>
public ImmutableArray<DiagnosticData> GetItems(bool selectedEntriesOnly, bool isAddSuppression, bool isSuppressionInSource, bool onlyCompilerDiagnostics, CancellationToken cancellationToken)
public async Task<ImmutableArray<DiagnosticData>> GetItemsAsync(bool selectedEntriesOnly, bool isAddSuppression, bool isSuppressionInSource, bool onlyCompilerDiagnostics, CancellationToken cancellationToken)
{
var builder = ImmutableArray.CreateBuilder<DiagnosticData>();
Dictionary<string, Project> projectNameToProjectMapOpt = null;
Dictionary<Project, ImmutableDictionary<string, Document>> filePathToDocumentMapOpt = null;
var entries = selectedEntriesOnly ? _tableControl.SelectedEntries : _tableControl.Entries;
foreach (var entryHandle in entries)
{
......@@ -185,28 +230,139 @@ public ImmutableArray<DiagnosticData> 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<string, Project>();
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<string, Document> filePathMap;
filePathToDocumentMapOpt = filePathToDocumentMapOpt ?? new Dictionary<Project, ImmutableDictionary<string, Document>>();
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<ImmutableDictionary<string, Document>> GetFilePathToDocumentMapAsync(Project project, CancellationToken cancellationToken)
{
var builder = ImmutableDictionary.CreateBuilder<string, Document>();
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);
}
}
......
......@@ -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);
}
/// <summary>
......
......@@ -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<Project, bool> 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<Project, bool> 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<Project, bool> 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<Project, bool> 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<Project, bool> shouldFixInProject, bool selectedEntriesOnly, bool isAddSuppression, bool isSuppressionInSource, bool onlyCompilerDiagnostics, bool showPreviewChangesDialog)
private bool ApplySuppressionFix(Func<Project, bool> shouldFixInProject, bool selectedEntriesOnly, bool isAddSuppression, bool isSuppressionInSource, bool onlyCompilerDiagnostics, bool showPreviewChangesDialog)
{
ImmutableDictionary<Document, ImmutableArray<Diagnostic>> 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<Project, bool> 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<Project, bool> 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<Document, ImmutableArray<Diagnostic>> documentDiagnosticsPerLanguage = null;
CodeFixProvider suppressionFixer = null;
......@@ -212,11 +247,13 @@ private void ApplySuppressionFix(Func<Project, bool> shouldFixInProject, bool se
if (currentSolution == newSolution)
{
// User cancelled, so we just bail out.
break;
return false;
}
needsMappingToNewSolution = true;
}
return true;
}
private async Task<ImmutableDictionary<Document, ImmutableArray<Diagnostic>>> GetDiagnosticsToFixMapAsync(IEnumerable<DiagnosticData> diagnosticsToFix, Func<Project, bool> shouldFixInProject, CancellationToken cancellationToken)
......@@ -273,11 +310,11 @@ private void ApplySuppressionFix(Func<Project, bool> shouldFixInProject, bool se
if (!latestDocumentDiagnosticsMap.TryGetValue(document.Id, out latestDocumentDiagnostics))
{
// Ignore stale diagnostics in error list.
continue;
latestDocumentDiagnostics = ImmutableHashSet<DiagnosticData>.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())
{
......
......@@ -132,6 +132,24 @@ internal class ServicesVSResources {
}
}
/// <summary>
/// Looks up a localized string similar to Computing remove suppressions fix....
/// </summary>
internal static string ComputingRemoveSuppressionFix {
get {
return ResourceManager.GetString("ComputingRemoveSuppressionFix", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Computing remove suppressions fix (&apos;{0}&apos;)....
/// </summary>
internal static string ComputingRemoveSuppressionFixForLanguage {
get {
return ResourceManager.GetString("ComputingRemoveSuppressionFixForLanguage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Computing suppressions fix....
/// </summary>
......@@ -966,6 +984,24 @@ internal class ServicesVSResources {
}
}
/// <summary>
/// Looks up a localized string similar to Remove suppressions.
/// </summary>
internal static string RemoveSuppressMultipleOccurrences {
get {
return ResourceManager.GetString("RemoveSuppressMultipleOccurrences", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Remove suppressions (&apos;{0}&apos;).
/// </summary>
internal static string RemoveSuppressMultipleOccurrencesForLanguage {
get {
return ResourceManager.GetString("RemoveSuppressMultipleOccurrencesForLanguage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Resetting Interactive.
/// </summary>
......
......@@ -498,4 +498,16 @@ Use the dropdown to view and switch to other projects this file may belong to.</
<data name="ComputingSuppressionFixForLanguage" xml:space="preserve">
<value>Computing suppressions fix ('{0}')...</value>
</data>
<data name="RemoveSuppressMultipleOccurrences" xml:space="preserve">
<value>Remove suppressions</value>
</data>
<data name="RemoveSuppressMultipleOccurrencesForLanguage" xml:space="preserve">
<value>Remove suppressions ('{0}')</value>
</data>
<data name="ComputingRemoveSuppressionFix" xml:space="preserve">
<value>Computing remove suppressions fix...</value>
</data>
<data name="ComputingRemoveSuppressionFixForLanguage" xml:space="preserve">
<value>Computing remove suppressions fix ('{0}')...</value>
</data>
</root>
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册