提交 515c3f96 编写于 作者: M Manish Vasani

This change adds a new code action to Features layer Suppression fixer to...

This change adds a new code action to Features layer Suppression fixer to enable Removing suppressions on diagnostics which are suppressed in source by pragma/SuppressMessageAttribute. Additionally, this change also adds a batch fixer to enable removing suppression on multiple/all diagnostics. This functionality will be consumed by the "Remove suppression(s)" context menu for error list selection.
上级 9b7d716f
......@@ -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;
......
// 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
{
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
{
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 removal 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 removal 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
{
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));
};
}
internal static void ResolveFixAllMergeConflictForPragmaAdd(List<TextChange> cumulativeChanges, int indexOfCurrentCumulativeChange, TextChange conflictingChange, bool isAddPragmaWarningSuppression)
{
// If there are multiple diagnostics with different IDs on the same line, we want to retain all the added pragmas.
var cumulativeChange = cumulativeChanges[indexOfCurrentCumulativeChange];
var mergedChange = ResolveFixAllMergeConflictForPragmaAdd(cumulativeChange, conflictingChange, isAddPragmaWarningSuppression: false);
cumulativeChanges[indexOfCurrentCumulativeChange] = mergedChange;
}
private static TextChange ResolveFixAllMergeConflictForPragmaAdd(TextChange cumulativeChange, TextChange conflictingChange, bool isAddPragmaWarningSuppression)
{
// If one of the change is a removal, just return the other one.
if (string.IsNullOrEmpty(cumulativeChange.NewText))
{
return conflictingChange;
}
else if (string.IsNullOrEmpty(conflictingChange.NewText))
{
return cumulativeChange;
}
// We have 2 code actions trying to add a pragma directive at the same location.
// If these are different IDs, then the order doesn't really matter.
// However, if these are disable and enable directives with same ID, then order does matter.
// We won't to make sure that for add suppression case, the restore precedes the enable and for remove suppression case, it is vice versa.
// We get the right ordering by sorting the pragma directive text.
string newText = cumulativeChange.NewText + conflictingChange.NewText;
var conflictChangeLexicallySmaller = string.Compare(conflictingChange.NewText, cumulativeChange.NewText, StringComparison.OrdinalIgnoreCase) < 0;
if ((isAddPragmaWarningSuppression && !conflictChangeLexicallySmaller) ||
(!isAddPragmaWarningSuppression && conflictChangeLexicallySmaller))
{
newText = conflictingChange.NewText + cumulativeChange.NewText;
}
var newSpan = new TextSpan(cumulativeChange.Span.Start, cumulativeChange.Span.Length);
return new TextChange(newSpan, newText);
}
}
}
}
// 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
{
......@@ -22,22 +25,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);
}
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
{
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
{
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
{
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" />
......
......@@ -1654,6 +1654,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
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册