提交 e7788159 编写于 作者: M Manish Vasani

Add support for adding/removing suppressions for diagnostics with no source...

Add support for adding/removing suppressions for diagnostics with no source location. Additionally,  when the diagnostics being suppressed or unsuppressed are across different languages, instead of showing a preview changes dialog per-language, we now show a single preview changes dialog for the entire changed solution.
上级 f5b65f90
......@@ -31,7 +31,34 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
return this;
}
public async Task<Solution> GetFixAllChangedSolutionAsync(FixAllProvider fixAllProvider, FixAllContext fixAllContext, string fixAllTitle, string waitDialogMessage)
{
// Compute fix all occurrences code fix for the given fix all context.
// Bring up a cancellable wait dialog.
var codeAction = GetFixAllCodeAction(fixAllProvider, fixAllContext, fixAllTitle, waitDialogMessage);
if (codeAction == null)
{
return null;
}
fixAllContext.CancellationToken.ThrowIfCancellationRequested();
return await codeAction.GetChangedSolutionInternalAsync(fixAllContext.CancellationToken).ConfigureAwait(false);
}
public async Task<IEnumerable<CodeActionOperation>> GetFixAllOperationsAsync(FixAllProvider fixAllProvider, FixAllContext fixAllContext, string fixAllTitle, string waitDialogMessage, bool showPreviewChangesDialog)
{
// Compute fix all occurrences code fix for the given fix all context.
// Bring up a cancellable wait dialog.
var codeAction = GetFixAllCodeAction(fixAllProvider, fixAllContext, fixAllTitle, waitDialogMessage);
if (codeAction == null)
{
return null;
}
return await GetFixAllOperationsAsync(codeAction, fixAllContext, fixAllTitle, showPreviewChangesDialog).ConfigureAwait(false);
}
private CodeAction GetFixAllCodeAction(FixAllProvider fixAllProvider, FixAllContext fixAllContext, string fixAllTitle, string waitDialogMessage)
{
// Compute fix all occurrences code fix for the given fix all context.
// Bring up a cancellable wait dialog.
......@@ -75,7 +102,7 @@ public async Task<IEnumerable<CodeActionOperation>> GetFixAllOperationsAsync(Fix
}
FixAllLogger.LogComputationResult(completed: true);
return await GetFixAllOperationsAsync(codeAction, fixAllContext, fixAllTitle, showPreviewChangesDialog).ConfigureAwait(false);
return codeAction;
}
private async Task<IEnumerable<CodeActionOperation>> GetFixAllOperationsAsync(CodeAction codeAction, FixAllContext fixAllContext, string fixAllPreviewChangesTitle, bool showPreviewChangesDialog)
......@@ -98,37 +125,60 @@ private async Task<IEnumerable<CodeActionOperation>> GetFixAllOperationsAsync(Co
if (showPreviewChangesDialog)
{
cancellationToken.ThrowIfCancellationRequested();
using (Logger.LogBlock(FunctionId.CodeFixes_FixAllOccurrencesPreviewChanges, cancellationToken))
newSolution = PreviewChanges(
fixAllContext.Project.Solution,
newSolution,
fixAllPreviewChangesTitle,
codeAction.Title,
fixAllContext.Project.Language,
workspace,
cancellationToken);
if (newSolution == null)
{
var previewService = workspace.Services.GetService<IPreviewDialogService>();
var glyph = fixAllContext.Project.Language == LanguageNames.CSharp ?
Glyph.CSharpProject :
Glyph.BasicProject;
return null;
}
}
// Get a code action, with apply changes operation replaced with the newSolution.
return GetNewFixAllOperations(operations, newSolution, cancellationToken);
}
internal static Solution PreviewChanges(
Solution currentSolution,
Solution newSolution,
string fixAllPreviewChangesTitle,
string fixAllTopLevelHeader,
string languageOpt,
Workspace workspace,
CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
using (Logger.LogBlock(FunctionId.CodeFixes_FixAllOccurrencesPreviewChanges, cancellationToken))
{
var previewService = workspace.Services.GetService<IPreviewDialogService>();
var glyph = languageOpt == null ?
Glyph.Assembly :
languageOpt == LanguageNames.CSharp ? Glyph.CSharpProject : Glyph.BasicProject;
var changedSolution = previewService.PreviewChanges(
var changedSolution = previewService.PreviewChanges(
string.Format(EditorFeaturesResources.PreviewChangesOf, fixAllPreviewChangesTitle),
"vs.codefix.fixall",
codeAction.Title,
fixAllTopLevelHeader,
fixAllPreviewChangesTitle,
glyph,
newSolution,
fixAllContext.Project.Solution);
if (changedSolution == null)
{
// User clicked cancel.
FixAllLogger.LogPreviewChangesResult(applied: false);
return null;
}
currentSolution);
FixAllLogger.LogPreviewChangesResult(applied: true, allChangesApplied: changedSolution == newSolution);
newSolution = changedSolution;
if (changedSolution == null)
{
// User clicked cancel.
FixAllLogger.LogPreviewChangesResult(applied: false);
return null;
}
}
// Get a code action, with apply changes operation replaced with the newSolution.
return GetNewFixAllOperations(operations, newSolution, cancellationToken);
FixAllLogger.LogPreviewChangesResult(applied: true, allChangesApplied: changedSolution == newSolution);
return changedSolution;
}
}
private IEnumerable<CodeActionOperation> GetNewFixAllOperations(IEnumerable<CodeActionOperation> operations, Solution newSolution, CancellationToken cancellationToken)
......
......@@ -35,13 +35,29 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
CodeFixProvider fixProvider,
FixAllProvider fixAllProvider,
string equivalenceKey,
string waitDialogAndPreviewChangesTitle,
string title,
string waitDialogMessage,
bool showPreviewChangesDialog,
CancellationToken cancellationToken)
{
var fixMultipleContext = FixMultipleContext.Create(diagnosticsToFix, fixProvider, equivalenceKey, cancellationToken);
ComputeAndApplyFix(fixMultipleContext, workspace, fixAllProvider, waitDialogAndPreviewChangesTitle, waitDialogMessage, showPreviewChangesDialog, cancellationToken);
var suggestedAction = GetSuggestedAction(fixMultipleContext, workspace, fixAllProvider, title, waitDialogMessage, showPreviewChangesDialog, cancellationToken);
suggestedAction.Invoke(cancellationToken);
}
public Solution GetFix(
ImmutableDictionary<Document, ImmutableArray<Diagnostic>> diagnosticsToFix,
Workspace workspace,
CodeFixProvider fixProvider,
FixAllProvider fixAllProvider,
string equivalenceKey,
string waitDialogTitle,
string waitDialogMessage,
CancellationToken cancellationToken)
{
var fixMultipleContext = FixMultipleContext.Create(diagnosticsToFix, fixProvider, equivalenceKey, cancellationToken);
var suggestedAction = GetSuggestedAction(fixMultipleContext, workspace, fixAllProvider, waitDialogTitle, waitDialogMessage, showPreviewChangesDialog: false, cancellationToken: cancellationToken);
return suggestedAction.GetChangedSolution(cancellationToken);
}
public void ComputeAndApplyFix(
......@@ -50,27 +66,42 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
CodeFixProvider fixProvider,
FixAllProvider fixAllProvider,
string equivalenceKey,
string waitDialogAndPreviewChangesTitle,
string title,
string waitDialogMessage,
bool showPreviewChangesDialog,
CancellationToken cancellationToken)
{
var fixMultipleContext = FixMultipleContext.Create(diagnosticsToFix, fixProvider, equivalenceKey, cancellationToken);
ComputeAndApplyFix(fixMultipleContext, workspace, fixAllProvider, waitDialogAndPreviewChangesTitle, waitDialogMessage, showPreviewChangesDialog, cancellationToken);
var suggestedAction = GetSuggestedAction(fixMultipleContext, workspace, fixAllProvider, title, waitDialogMessage, showPreviewChangesDialog, cancellationToken);
suggestedAction.Invoke(cancellationToken);
}
public Solution GetFix(
ImmutableDictionary<Project, ImmutableArray<Diagnostic>> diagnosticsToFix,
Workspace workspace,
CodeFixProvider fixProvider,
FixAllProvider fixAllProvider,
string equivalenceKey,
string waitDialogTitle,
string waitDialogMessage,
CancellationToken cancellationToken)
{
var fixMultipleContext = FixMultipleContext.Create(diagnosticsToFix, fixProvider, equivalenceKey, cancellationToken);
var suggestedAction = GetSuggestedAction(fixMultipleContext, workspace, fixAllProvider, waitDialogTitle, waitDialogMessage, showPreviewChangesDialog: false, cancellationToken: cancellationToken);
return suggestedAction.GetChangedSolution(cancellationToken);
}
private void ComputeAndApplyFix(
private FixMultipleSuggestedAction GetSuggestedAction(
FixMultipleContext fixMultipleContext,
Workspace workspace,
FixAllProvider fixAllProvider,
string waitDialogAndPreviewChangesTitle,
string title,
string waitDialogMessage,
bool showPreviewChangesDialog,
CancellationToken cancellationToken)
{
var fixMultipleCodeAction = new FixMultipleCodeAction(fixMultipleContext, fixAllProvider, title: waitDialogAndPreviewChangesTitle, previewChangesDialogTitle: waitDialogAndPreviewChangesTitle, computingFixWaitDialogMessage: waitDialogMessage, showPreviewChangesDialog: showPreviewChangesDialog);
var fixMultipleSuggestedAction = new FixMultipleSuggestedAction(workspace, _editHandler, fixMultipleCodeAction, fixAllProvider);
fixMultipleSuggestedAction.Invoke(cancellationToken);
var fixMultipleCodeAction = new FixMultipleCodeAction(fixMultipleContext, fixAllProvider, title, waitDialogMessage, showPreviewChangesDialog);
return new FixMultipleSuggestedAction(workspace, _editHandler, fixMultipleCodeAction, fixAllProvider);
}
}
}
......@@ -9,6 +9,7 @@
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.VisualStudio.Text;
using Roslyn.Utilities;
using Microsoft.CodeAnalysis.Extensions;
namespace Microsoft.CodeAnalysis.Editor.Implementation.Suggestions
{
......@@ -63,6 +64,18 @@ public override Task<object> GetPreviewAsync(CancellationToken cancellationToken
return SpecializedTasks.Default<object>();
}
public Solution GetChangedSolution(CancellationToken cancellationToken)
{
Solution newSolution = null;
var extensionManager = this.Workspace.Services.GetService<IExtensionManager>();
extensionManager.PerformAction(Provider, () =>
{
newSolution = CodeAction.GetChangedSolutionInternalAsync(cancellationToken).WaitAndGetResult(cancellationToken);
});
return newSolution;
}
public override void Invoke(CancellationToken cancellationToken)
{
using (Logger.LogBlock(FunctionId.CodeFixes_FixAllOccurrencesSession, cancellationToken))
......
......@@ -96,7 +96,7 @@ protected override SyntaxNode AddGlobalSuppressMessageAttribute(SyntaxNode newRo
{
var compilationRoot = (CompilationUnitSyntax)newRoot;
var isFirst = !compilationRoot.AttributeLists.Any();
var leadingTriviaForAttributeList = isFirst ?
var leadingTriviaForAttributeList = isFirst && !compilationRoot.HasLeadingTrivia ?
SyntaxFactory.TriviaList(SyntaxFactory.Comment(GlobalSuppressionsFileHeaderComment)) :
default(SyntaxTriviaList);
var attributeList = CreateAttributeList(targetSymbol, diagnostic, leadingTrivia: leadingTriviaForAttributeList, needsLeadingEndOfLine: !isFirst);
......
......@@ -46,7 +46,7 @@ public override string Title
}
public FixAllContext FixAllContext => _fixAllContext;
protected virtual string FixAllPreviewChangesTitle => FeaturesResources.FixAllOccurrences;
protected virtual string FixAllWaitDialogAndPreviewChangesTitle => FeaturesResources.FixAllOccurrences;
protected virtual string ComputingFixAllWaitDialogMessage => FeaturesResources.ComputingFixAllOccurrences;
protected override async Task<IEnumerable<CodeActionOperation>> ComputeOperationsAsync(CancellationToken cancellationToken)
......@@ -55,8 +55,20 @@ protected override async Task<IEnumerable<CodeActionOperation>> ComputeOperation
FixAllLogger.LogContext(_fixAllContext, IsInternalCodeFixProvider(_fixAllContext.CodeFixProvider));
var service = _fixAllContext.Project.Solution.Workspace.Services.GetService<IFixAllGetFixesService>();
// Use the new cancellation token instead of the stale one present inside _fixAllContext.
return await service.GetFixAllOperationsAsync(_fixAllProvider, _fixAllContext.WithCancellationToken(cancellationToken), FixAllPreviewChangesTitle, ComputingFixAllWaitDialogMessage, _showPreviewChangesDialog).ConfigureAwait(false);
return await service.GetFixAllOperationsAsync(_fixAllProvider, _fixAllContext.WithCancellationToken(cancellationToken), FixAllWaitDialogAndPreviewChangesTitle, ComputingFixAllWaitDialogMessage, _showPreviewChangesDialog).ConfigureAwait(false);
}
protected async override Task<Solution> GetChangedSolutionAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
FixAllLogger.LogContext(_fixAllContext, IsInternalCodeFixProvider(_fixAllContext.CodeFixProvider));
var service = _fixAllContext.Project.Solution.Workspace.Services.GetService<IFixAllGetFixesService>();
// Use the new cancellation token instead of the stale one present inside _fixAllContext.
return await service.GetFixAllChangedSolutionAsync(_fixAllProvider, _fixAllContext.WithCancellationToken(cancellationToken), FixAllWaitDialogAndPreviewChangesTitle, ComputingFixAllWaitDialogMessage).ConfigureAwait(false);
}
private static bool IsInternalCodeFixProvider(CodeFixProvider fixer)
......
......@@ -8,14 +8,12 @@ namespace Microsoft.CodeAnalysis.CodeFixes
internal partial class FixMultipleCodeAction : FixAllCodeAction
{
private readonly string _title;
private readonly string _previewChangesDialogTitle;
private readonly string _computingFixWaitDialogMessage;
internal FixMultipleCodeAction(FixMultipleContext fixMultipleContext, FixAllProvider fixAllProvider, string title, string previewChangesDialogTitle, string computingFixWaitDialogMessage, bool showPreviewChangesDialog)
internal FixMultipleCodeAction(FixMultipleContext fixMultipleContext, FixAllProvider fixAllProvider, string title, string computingFixWaitDialogMessage, bool showPreviewChangesDialog)
: base (fixMultipleContext, fixAllProvider, showPreviewChangesDialog)
{
_title = title;
_previewChangesDialogTitle = previewChangesDialogTitle;
_computingFixWaitDialogMessage = computingFixWaitDialogMessage;
}
......@@ -25,7 +23,7 @@ public Diagnostic GetTriggerDiagnostic()
}
public override string Title => _title;
protected override string FixAllPreviewChangesTitle => _previewChangesDialogTitle;
protected override string FixAllWaitDialogAndPreviewChangesTitle => _title;
protected override string ComputingFixAllWaitDialogMessage => _computingFixWaitDialogMessage;
}
}
......@@ -14,5 +14,10 @@ internal interface IFixAllGetFixesService : IWorkspaceService
/// returns the code action operations corresponding to the fix.
/// </summary>
Task<IEnumerable<CodeActionOperation>> GetFixAllOperationsAsync(FixAllProvider fixAllProvider, FixAllContext fixAllContext, string fixAllTitle, string waitDialogMessage, bool showPreviewChangesDialog);
/// <summary>
/// Computes the fix all occurrences code fix and returns the changed solution.
/// </summary>
Task<Solution> GetFixAllChangedSolutionAsync(FixAllProvider fixAllProvider, FixAllContext fixAllContext, string fixAllTitle, string waitDialogMessage);
}
}
......@@ -23,6 +23,20 @@ internal interface IFixMultipleOccurrencesService : IWorkspaceService
bool showPreviewChangesDialog,
CancellationToken cancellationToken);
/// <summary>
/// Get the fix muliple occurrences code fix for the given diagnostics with source locations.
/// NOTE: This method does not apply the fix to the workspace.
/// </summary>
Solution GetFix(
ImmutableDictionary<Document, ImmutableArray<Diagnostic>> diagnosticsToFix,
Workspace workspace,
CodeFixProvider fixProvider,
FixAllProvider fixAllProvider,
string equivalenceKey,
string waitDialogTitle,
string waitDialogMessage,
CancellationToken cancellationToken);
/// <summary>
/// Computes the fix muliple occurrences code fix for the given diagnostics without any source location, brings up the preview changes dialog for the fix and
/// apply the code action operations corresponding to the fix.
......@@ -37,5 +51,19 @@ internal interface IFixMultipleOccurrencesService : IWorkspaceService
string waitDialogMessage,
bool showPreviewChangesDialog,
CancellationToken cancellationToken);
/// <summary>
/// Get the fix muliple occurrences code fix for the given diagnostics with source locations.
/// NOTE: This method does not apply the fix to the workspace.
/// </summary>
Solution GetFix(
ImmutableDictionary<Project, ImmutableArray<Diagnostic>> diagnosticsToFix,
Workspace workspace,
CodeFixProvider fixProvider,
FixAllProvider fixAllProvider,
string equivalenceKey,
string waitDialogTitle,
string waitDialogMessage,
CancellationToken cancellationToken);
}
}
......@@ -76,6 +76,24 @@ public override async Task AddDocumentFixesAsync(Document document, ImmutableArr
}
}
public async override Task AddProjectFixesAsync(Project project, ImmutableArray<Diagnostic> diagnostics, Action<CodeAction> addFix, FixAllContext fixAllContext)
{
foreach (var diagnostic in diagnostics.Where(d => !d.Location.IsInSource && d.IsSuppressed))
{
var removeSuppressionFixes = await _suppressionFixProvider.GetSuppressionsAsync(project, SpecializedCollections.SingletonEnumerable(diagnostic), fixAllContext.CancellationToken).ConfigureAwait(false);
var removeSuppressionCodeAction = removeSuppressionFixes.SingleOrDefault()?.Action as RemoveSuppressionCodeAction;
if (removeSuppressionCodeAction != null)
{
if (fixAllContext is FixMultipleContext)
{
removeSuppressionCodeAction = removeSuppressionCodeAction.CloneForFixMultipleContext();
}
addFix(removeSuppressionCodeAction);
}
}
}
public override async Task<CodeAction> TryGetMergedFixAsync(IEnumerable<CodeAction> batchOfFixes, FixAllContext fixAllContext)
{
// Batch all the attribute removal fixes into a single fix.
......@@ -83,7 +101,7 @@ public override async Task<CodeAction> TryGetMergedFixAsync(IEnumerable<CodeActi
// This ensures no merge conflicts in merging all fixes by our base implementation.
var cancellationToken = fixAllContext.CancellationToken;
var oldSolution = fixAllContext.Document.Project.Solution;
var oldSolution = fixAllContext.Project.Solution;
var currentSolution = oldSolution;
var attributeRemoveFixes = new List<AttributeRemoveAction>();
......
......@@ -12,37 +12,39 @@ internal abstract partial class AbstractSuppressionCodeFixProvider : ISuppressio
/// </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,
Document documentOpt,
Project project,
Diagnostic diagnostic,
AbstractSuppressionCodeFixProvider fixer,
CancellationToken cancellationToken)
{
var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
var attribute = diagnostic.GetSuppressionInfo(compilation).Attribute;
if (attribute != null)
{
return AttributeRemoveAction.Create(attribute, document, diagnostic, fixer);
return AttributeRemoveAction.Create(attribute, project, diagnostic, fixer);
}
else if (documentOpt != null)
{
return PragmaRemoveAction.Create(suppressionTargetInfo, documentOpt, diagnostic, fixer);
}
else
{
return PragmaRemoveAction.Create(suppressionTargetInfo, document, diagnostic, fixer);
return null;
}
}
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;
}
......
......@@ -15,31 +15,33 @@ internal abstract partial class RemoveSuppressionCodeAction
/// </summary>
private sealed class AttributeRemoveAction : RemoveSuppressionCodeAction
{
private readonly Project _project;
private readonly AttributeData _attribute;
public static AttributeRemoveAction Create(
AttributeData attribute,
Document document,
Project project,
Diagnostic diagnostic,
AbstractSuppressionCodeFixProvider fixer)
{
return new AttributeRemoveAction(attribute, document, diagnostic, fixer);
return new AttributeRemoveAction(attribute, project, diagnostic, fixer);
}
private AttributeRemoveAction(
AttributeData attribute,
Document document,
Project project,
Diagnostic diagnostic,
AbstractSuppressionCodeFixProvider fixer,
bool forFixMultipleContext = false)
: base(document, diagnostic, fixer, forFixMultipleContext)
: base(diagnostic, fixer, forFixMultipleContext)
{
_project = project;
_attribute = attribute;
}
public override RemoveSuppressionCodeAction CloneForFixMultipleContext()
{
return new AttributeRemoveAction(_attribute, _document, _diagnostic, Fixer, forFixMultipleContext: true);
return new AttributeRemoveAction(_attribute, _project, _diagnostic, Fixer, forFixMultipleContext: true);
}
public override SyntaxTree SyntaxTreeToModify => _attribute.ApplicationSyntaxReference.SyntaxTree;
......@@ -52,30 +54,18 @@ public async Task<SyntaxNode> GetAttributeToRemoveAsync(CancellationToken cancel
attributeNode;
}
protected override async Task<Document> GetChangedDocumentAsync(CancellationToken cancellationToken)
protected async override Task<Solution> GetChangedSolutionAsync(CancellationToken cancellationToken)
{
var attributeNode = await GetAttributeToRemoveAsync(cancellationToken).ConfigureAwait(false);
var document = GetDocumentWithAttribute(attributeNode);
if (document == null)
var documentWithAttribute = _project.GetDocument(attributeNode.SyntaxTree);
if (documentWithAttribute == null)
{
return _document;
return _project.Solution;
}
var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
var editor = await DocumentEditor.CreateAsync(documentWithAttribute, 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);
return editor.GetChangedDocument().Project.Solution;
}
}
}
......
......@@ -21,6 +21,7 @@ internal abstract partial class RemoveSuppressionCodeAction
/// </summary>
private class PragmaRemoveAction : RemoveSuppressionCodeAction, IPragmaBasedCodeAction
{
private readonly Document _document;
private readonly SuppressionTargetInfo _suppressionTargetInfo;
public static PragmaRemoveAction Create(
......@@ -42,8 +43,9 @@ private class PragmaRemoveAction : RemoveSuppressionCodeAction, IPragmaBasedCode
Diagnostic diagnostic,
AbstractSuppressionCodeFixProvider fixer,
bool forFixMultipleContext = false)
: base(document, diagnostic, fixer, forFixMultipleContext)
: base(diagnostic, fixer, forFixMultipleContext)
{
_document = document;
_suppressionTargetInfo = suppressionTargetInfo;
}
......
......@@ -84,23 +84,42 @@ internal async Task<IEnumerable<PragmaWarningCodeAction>> GetPragmaSuppressionsA
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/unsuppressed.
diagnostics = diagnostics.Where(CanBeSuppressedOrUnsuppressed);
if (diagnostics.IsEmpty())
var suppressionTargetInfo = await GetSuppressionTargetInfoAsync(document, span, cancellationToken).ConfigureAwait(false);
if (suppressionTargetInfo == null)
{
return SpecializedCollections.EmptyEnumerable<CodeFix>();
}
var suppressionTargetInfo = await GetSuppressionTargetInfoAsync(document, span, cancellationToken).ConfigureAwait(false);
if (suppressionTargetInfo == null)
return await GetSuppressionsAsync(documentOpt: document, project: document.Project, diagnostics: diagnostics,
suppressionTargetInfo: suppressionTargetInfo, skipSuppressMessage: skipSuppressMessage, skipUnsuppress: skipUnsuppress, cancellationToken: cancellationToken).ConfigureAwait(false);
}
public async Task<IEnumerable<CodeFix>> GetSuppressionsAsync(Project project, IEnumerable<Diagnostic> diagnostics, CancellationToken cancellationToken)
{
if (!project.SupportsCompilation)
{
return SpecializedCollections.EmptyEnumerable<CodeFix>();
}
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
var suppressionTargetInfo = new SuppressionTargetInfo() { TargetSymbol = compilation.Assembly };
return await GetSuppressionsAsync(documentOpt: null, project: project, diagnostics: diagnostics,
suppressionTargetInfo: suppressionTargetInfo, skipSuppressMessage: false, skipUnsuppress: false, cancellationToken: cancellationToken).ConfigureAwait(false);
}
private async Task<IEnumerable<CodeFix>> GetSuppressionsAsync(Document documentOpt, Project project, IEnumerable<Diagnostic> diagnostics, SuppressionTargetInfo suppressionTargetInfo, bool skipSuppressMessage, bool skipUnsuppress, CancellationToken cancellationToken)
{
// We only care about diagnostics that can be suppressed/unsuppressed.
diagnostics = diagnostics.Where(CanBeSuppressedOrUnsuppressed);
if (diagnostics.IsEmpty())
{
return SpecializedCollections.EmptyEnumerable<CodeFix>();
}
if (!skipSuppressMessage)
{
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var suppressMessageAttribute = semanticModel.Compilation.SuppressMessageAttributeType();
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
var suppressMessageAttribute = compilation.SuppressMessageAttributeType();
skipSuppressMessage = suppressMessageAttribute == null || !suppressMessageAttribute.IsAttribute();
}
......@@ -111,22 +130,28 @@ private async Task<IEnumerable<CodeFix>> GetSuppressionsAsync(Document document,
{
var nestedActions = new List<NestedSuppressionCodeAction>();
// pragma warning disable.
nestedActions.Add(new PragmaWarningCodeAction(suppressionTargetInfo, document, diagnostic, this));
if (diagnostic.Location.IsInSource && documentOpt != null)
{
// pragma warning disable.
nestedActions.Add(new PragmaWarningCodeAction(suppressionTargetInfo, documentOpt, diagnostic, this));
}
// 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));
nestedActions.Add(new GlobalSuppressMessageCodeAction(suppressionTargetInfo.TargetSymbol, project, diagnostic, this));
}
result.Add(new CodeFix(new SuppressionCodeAction(diagnostic, nestedActions), diagnostic));
}
else if (!skipUnsuppress)
{
var codeAcion = await RemoveSuppressionCodeAction.CreateAsync(suppressionTargetInfo, document, diagnostic, this, cancellationToken).ConfigureAwait(false);
result.Add(new CodeFix(codeAcion, diagnostic));
var codeAction = await RemoveSuppressionCodeAction.CreateAsync(suppressionTargetInfo, documentOpt, project, diagnostic, this, cancellationToken).ConfigureAwait(false);
if (codeAction != null)
{
result.Add(new CodeFix(codeAction, diagnostic));
}
}
}
......
......@@ -21,6 +21,12 @@ internal interface ISuppressionFixProvider
/// <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);
/// <summary>
/// Gets one or more add suppression or remove suppression fixes for the specified no-location 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(Project project, IEnumerable<Diagnostic> diagnostics, CancellationToken cancellationToken);
/// <summary>
/// Gets an optional <see cref="FixAllProvider"/> that can fix all/multiple occurrences of diagnostics fixed by this fix provider.
/// Return null if the provider doesn't support fix all/multiple occurrences.
......
......@@ -22,14 +22,12 @@ public static bool CanBeUnsuppressed(Diagnostic diagnostic)
private static bool CanBeSuppressedOrUnsuppressed(Diagnostic diagnostic, bool checkCanBeSuppressed)
{
if (diagnostic.Location.Kind != LocationKind.SourceFile ||
(diagnostic.IsSuppressed == checkCanBeSuppressed) ||
if (diagnostic.IsSuppressed == checkCanBeSuppressed ||
IsNotConfigurableDiagnostic(diagnostic))
{
// Don't offer suppression fixes for:
// 1. Diagnostics without a source location.
// 2. Diagnostics with a source suppression.
// 3. Non-configurable diagnostics (includes compiler errors).
// 1. Diagnostics with a source suppression.
// 2. Non-configurable diagnostics (includes compiler errors).
return false;
}
......
// 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.Linq;
using System.Threading.Tasks;
......@@ -22,8 +23,25 @@ public WrapperCodeFixProvider(ISuppressionFixProvider suppressionFixProvider, Im
public async override Task RegisterCodeFixesAsync(CodeFixContext context)
{
var diagnostics = context.Diagnostics.WhereAsArray(_suppressionFixProvider.CanBeSuppressedOrUnsuppressed);
var suppressionFixes = await _suppressionFixProvider.GetSuppressionsAsync(context.Document, context.Span, diagnostics, context.CancellationToken).ConfigureAwait(false);
var diagnostics = context.Diagnostics.Where(_suppressionFixProvider.CanBeSuppressedOrUnsuppressed);
var documentDiagnostics = diagnostics.Where(d => d.Location.IsInSource).ToImmutableArray();
if (!documentDiagnostics.IsEmpty)
{
var suppressionFixes = await _suppressionFixProvider.GetSuppressionsAsync(context.Document, context.Span, diagnostics, context.CancellationToken).ConfigureAwait(false);
RegisterSuppressionFixes(context, suppressionFixes);
}
var projectDiagnostics = diagnostics.Where(d => !d.Location.IsInSource).ToImmutableArray();
if (!projectDiagnostics.IsEmpty)
{
var suppressionFixes = await _suppressionFixProvider.GetSuppressionsAsync(context.Project, diagnostics, context.CancellationToken).ConfigureAwait(false);
RegisterSuppressionFixes(context, suppressionFixes);
}
}
private void RegisterSuppressionFixes(CodeFixContext context, IEnumerable<CodeFix> suppressionFixes)
{
if (suppressionFixes != null)
{
foreach (var suppressionFix in suppressionFixes)
......
......@@ -132,7 +132,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.Suppression
attributeStatement = attributeStatement.WithAdditionalAnnotations(Formatter.Annotation)
Dim leadingTrivia = If(isFirst,
Dim leadingTrivia = If(isFirst AndAlso Not compilationRoot.HasLeadingTrivia,
SyntaxFactory.TriviaList(SyntaxFactory.CommentTrivia(GlobalSuppressionsFileHeaderComment)),
Nothing)
leadingTrivia = leadingTrivia.AddRange(compilationRoot.GetLeadingTrivia())
......
......@@ -32,6 +32,7 @@ internal class VisualStudioDiagnosticListSuppressionStateService : IVisualStudio
private int _selectedSuppressedItems;
private int _selectedRoslynItems;
private int _selectedCompilerDiagnosticItems;
private int _selectedNoLocationDiagnosticItems;
private int _selectedNonSuppressionStateItems;
private const string SynthesizedFxCopDiagnostic = "SynthesizedFxCopDiagnostic";
......@@ -62,7 +63,8 @@ internal class VisualStudioDiagnosticListSuppressionStateService : IVisualStudio
// If only Roslyn active items are selected, we enable suppress in source.
public bool CanSuppressSelectedEntriesInSource => _selectedActiveItems > 0 &&
_selectedSuppressedItems == 0 &&
_selectedRoslynItems == _selectedActiveItems;
_selectedRoslynItems == _selectedActiveItems &&
(_selectedRoslynItems - _selectedNoLocationDiagnosticItems) > 0;
// If only active items are selected, and there is at least one Roslyn item, we enable suppress in suppression file.
// Also, compiler diagnostics cannot be suppressed in suppression file, so there must be at least one non-compiler item.
......@@ -76,6 +78,7 @@ private void ClearState()
_selectedSuppressedItems = 0;
_selectedRoslynItems = 0;
_selectedCompilerDiagnosticItems = 0;
_selectedNoLocationDiagnosticItems = 0;
_selectedNonSuppressionStateItems = 0;
}
......@@ -119,14 +122,14 @@ public void ProcessSelectionChanged(TableSelectionChangedEventArgs e)
private bool ProcessEntries(IEnumerable<ITableEntryHandle> entryHandles, bool added)
{
bool isRoslynEntry, isSuppressedEntry, isCompilerDiagnosticEntry;
bool isRoslynEntry, isSuppressedEntry, isCompilerDiagnosticEntry, isNoLocationDiagnosticEntry;
var hasSuppressionStateEntry = false;
foreach (var entryHandle in entryHandles)
{
if (EntrySupportsSuppressionState(entryHandle, out isRoslynEntry, out isSuppressedEntry, out isCompilerDiagnosticEntry))
if (EntrySupportsSuppressionState(entryHandle, out isRoslynEntry, out isSuppressedEntry, out isCompilerDiagnosticEntry, out isNoLocationDiagnosticEntry))
{
hasSuppressionStateEntry = true;
HandleSuppressionStateEntry(isRoslynEntry, isSuppressedEntry, isCompilerDiagnosticEntry, added);
HandleSuppressionStateEntry(isRoslynEntry, isSuppressedEntry, isCompilerDiagnosticEntry, isNoLocationDiagnosticEntry, added);
}
else
{
......@@ -137,8 +140,12 @@ private bool ProcessEntries(IEnumerable<ITableEntryHandle> entryHandles, bool ad
return hasSuppressionStateEntry;
}
private static bool EntrySupportsSuppressionState(ITableEntryHandle entryHandle, out bool isRoslynEntry, out bool isSuppressedEntry, out bool isCompilerDiagnosticEntry)
private static bool EntrySupportsSuppressionState(ITableEntryHandle entryHandle, out bool isRoslynEntry, out bool isSuppressedEntry, out bool isCompilerDiagnosticEntry, out bool isNoLocationDiagnosticEntry)
{
string filePath;
isNoLocationDiagnosticEntry = !entryHandle.TryGetValue(StandardTableColumnDefinitions.DocumentName, out filePath) ||
string.IsNullOrEmpty(filePath);
int index;
var roslynSnapshot = GetEntriesSnapshot(entryHandle, out index);
if (roslynSnapshot == null)
......@@ -183,7 +190,6 @@ private static bool IsNonRoslynEntrySupportingSuppressionState(ITableEntryHandle
private static bool IsEntryWithConfigurableSuppressionState(DiagnosticData entry)
{
return entry != null &&
entry.HasTextSpan &&
!SuppressionHelpers.IsNotConfigurableDiagnostic(entry);
}
......@@ -381,7 +387,7 @@ private static void UpdateSelectedItems(bool added, ref int count)
}
}
private void HandleSuppressionStateEntry(bool isRoslynEntry, bool isSuppressedEntry, bool isCompilerDiagnosticEntry, bool added)
private void HandleSuppressionStateEntry(bool isRoslynEntry, bool isSuppressedEntry, bool isCompilerDiagnosticEntry, bool isNoLocationDiagnosticEntry, bool added)
{
if (isRoslynEntry)
{
......@@ -393,6 +399,11 @@ private void HandleSuppressionStateEntry(bool isRoslynEntry, bool isSuppressedEn
UpdateSelectedItems(added, ref _selectedCompilerDiagnosticItems);
}
if (isNoLocationDiagnosticEntry)
{
UpdateSelectedItems(added, ref _selectedNoLocationDiagnosticItems);
}
if (isSuppressedEntry)
{
UpdateSelectedItems(added, ref _selectedSuppressedItems);
......
......@@ -87,6 +87,24 @@ internal class ServicesVSResources {
}
}
/// <summary>
/// Looks up a localized string similar to Applying remove suppressions fix....
/// </summary>
internal static string ApplyingRemoveSuppressionFix {
get {
return ResourceManager.GetString("ApplyingRemoveSuppressionFix", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Applying suppressions fix....
/// </summary>
internal static string ApplyingSuppressionFix {
get {
return ResourceManager.GetString("ApplyingSuppressionFix", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Building Project.
/// </summary>
......@@ -141,15 +159,6 @@ internal class ServicesVSResources {
}
}
/// <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>
......@@ -159,15 +168,6 @@ internal class ServicesVSResources {
}
}
/// <summary>
/// Looks up a localized string similar to Computing suppressions fix (&apos;{0}&apos;)....
/// </summary>
internal static string ComputingSuppressionFixForLanguage {
get {
return ResourceManager.GetString("ComputingSuppressionFixForLanguage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Could not find location of folder on disk.
/// </summary>
......@@ -1002,15 +1002,6 @@ internal class ServicesVSResources {
}
}
/// <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>
......@@ -1092,15 +1083,6 @@ internal class ServicesVSResources {
}
}
/// <summary>
/// Looks up a localized string similar to Suppress diagnostics (&apos;{0}&apos;).
/// </summary>
internal static string SuppressMultipleOccurrencesForLanguage {
get {
return ResourceManager.GetString("SuppressMultipleOccurrencesForLanguage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to IDs are not supported for this symbol type..
/// </summary>
......
......@@ -501,25 +501,20 @@ Use the dropdown to view and switch to other projects this file may belong to.</
<data name="SuppressMultipleOccurrences" xml:space="preserve">
<value>Suppress diagnostics</value>
</data>
<data name="SuppressMultipleOccurrencesForLanguage" xml:space="preserve">
<value>Suppress diagnostics ('{0}')</value>
</data>
<data name="ComputingSuppressionFix" xml:space="preserve">
<value>Computing suppressions fix...</value>
</data>
<data name="ComputingSuppressionFixForLanguage" xml:space="preserve">
<value>Computing suppressions fix ('{0}')...</value>
<data name="ApplyingSuppressionFix" xml:space="preserve">
<value>Applying suppressions fix...</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 name="ApplyingRemoveSuppressionFix" xml:space="preserve">
<value>Applying remove suppressions fix...</value>
</data>
</root>
\ No newline at end of file
......@@ -17,6 +17,7 @@ namespace Microsoft.CodeAnalysis.CodeFixes
public struct CodeFixContext
{
private readonly Document _document;
private readonly Project _project;
private readonly TextSpan _span;
private readonly ImmutableArray<Diagnostic> _diagnostics;
private readonly CancellationToken _cancellationToken;
......@@ -27,6 +28,11 @@ public struct CodeFixContext
/// </summary>
public Document Document { get { return _document; } }
/// <summary>
/// Project corresponding to the diagnostics to fix.
/// </summary>
internal Project Project { get { return _project; } }
/// <summary>
/// Text span within the <see cref="CodeFixContext.Document"/> to fix.
/// </summary>
......@@ -97,6 +103,27 @@ public struct CodeFixContext
Action<CodeAction, ImmutableArray<Diagnostic>> registerCodeFix,
bool verifyArguments,
CancellationToken cancellationToken)
: this (document, document.Project, span, diagnostics, registerCodeFix, verifyArguments, cancellationToken)
{
}
internal CodeFixContext(
Project project,
ImmutableArray<Diagnostic> diagnostics,
Action<CodeAction, ImmutableArray<Diagnostic>> registerCodeFix,
CancellationToken cancellationToken)
: this(document: null, project: project, span: default(TextSpan), diagnostics: diagnostics, registerCodeFix: registerCodeFix, verifyArguments: false, cancellationToken: cancellationToken)
{
}
private CodeFixContext(
Document document,
Project project,
TextSpan span,
ImmutableArray<Diagnostic> diagnostics,
Action<CodeAction, ImmutableArray<Diagnostic>> registerCodeFix,
bool verifyArguments,
CancellationToken cancellationToken)
{
if (verifyArguments)
{
......@@ -114,6 +141,7 @@ public struct CodeFixContext
}
_document = document;
_project = project;
_span = span;
_diagnostics = diagnostics;
_registerCodeFix = registerCodeFix;
......
......@@ -83,7 +83,6 @@ public async virtual Task AddDocumentFixesAsync(Document document, ImmutableArra
{
Debug.Assert(!diagnostics.IsDefault);
var cancellationToken = fixAllContext.CancellationToken;
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var fixerTasks = new Task[diagnostics.Length];
for (var i = 0; i < diagnostics.Length; i++)
......@@ -159,9 +158,39 @@ public async virtual Task AddDocumentFixesAsync(Document document, ImmutableArra
return null;
}
public virtual Task AddProjectFixesAsync(Project project, IEnumerable<Diagnostic> diagnostics, Action<CodeAction> addFix, FixAllContext fixAllContext)
public virtual async Task AddProjectFixesAsync(Project project, ImmutableArray<Diagnostic> diagnostics, Action<CodeAction> addFix, FixAllContext fixAllContext)
{
throw new NotImplementedException();
Debug.Assert(!diagnostics.IsDefault);
var cancellationToken = fixAllContext.CancellationToken;
cancellationToken.ThrowIfCancellationRequested();
var fixes = new List<CodeAction>();
var context = new CodeFixContext(project, diagnostics,
// TODO: Can we share code between similar lambdas that we pass to this API in BatchFixAllProvider.cs, CodeFixService.cs and CodeRefactoringService.cs?
(a, d) =>
{
// Serialize access for thread safety - we don't know what thread the fix provider will call this delegate from.
lock (fixes)
{
fixes.Add(a);
}
},
cancellationToken);
// TODO: Wrap call to ComputeFixesAsync() below in IExtensionManager.PerformFunctionAsync() so that
// a buggy extension that throws can't bring down the host?
var task = fixAllContext.CodeFixProvider.RegisterCodeFixesAsync(context) ?? SpecializedTasks.EmptyTask;
await task.ConfigureAwait(false);
foreach (var fix in fixes)
{
cancellationToken.ThrowIfCancellationRequested();
if (fix != null && fix.EquivalenceKey == fixAllContext.CodeActionEquivalenceKey)
{
addFix(fix);
}
}
}
public virtual async Task<CodeAction> TryGetMergedFixAsync(IEnumerable<CodeAction> batchOfFixes, FixAllContext fixAllContext)
......
......@@ -40,7 +40,7 @@ internal partial class FixMultipleContext : FixAllContext
/// <summary>
/// Creates a new <see cref="FixMultipleContext"/>.
/// Use this overload when applying fix multiple diagnostics with a source location.
/// Use this overload when applying fix multiple diagnostics with no source location.
/// </summary>
/// <param name="diagnosticsToFix">Specific set of diagnostics to fix. Must be a non-empty set.</param>
/// <param name="codeFixProvider">Underlying <see cref="CodeFixes.CodeFixProvider"/> which triggered this fix all.</param>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册