提交 04b35434 编写于 作者: M Manish Vasani

Merge pull request #6096 from mavasani/BulkSuppressPerfFixes

Address the performance issues for bulk suppression in IDE.

1. "Suppress active issues" command from solution explorer was doing couple of builds - one command line build to compute the FxCop diagnostics and another intellisense build to fetch Roslyn diagnostics. This change removes the second build and instead uses the diagnostics emitted from the build for suppression.

2. Bulk suppressing multiple diagnostics from error list/suppress active issues command was spending more than 50% of time in formatting the generated global suppressions file. This delay grows significantly larger with 1000+ issues being suppressed. This fix changes us to not format the entire suppressions file at once, but instead format each attribute separately and avoid formatting the batched file. This significantly brings down the formatting time.

3. Thread in the cancellation token during batch fix computation - otherwise cancelling the wait dialog while computing suppressions fix will make VS unresponsive.

Fixes #6066
......@@ -378,9 +378,8 @@ class Class2
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"", Scope = ""member"", Target = ""~M:Class1.Method~System.Int32"")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"", Scope = ""type"", Target = ""~T:Class1"")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"", Scope = ""type"", Target = ""~T:Class2"")]
".Replace("<", "&lt;").Replace(">", "&gt;");
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"", Scope = ""type"", Target = ""~T:Class2"")]"
.Replace("<", "&lt;").Replace(">", "&gt;");
var expected = @"
<Workspace>
......@@ -483,9 +482,8 @@ class Class2
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"", Scope = ""member"", Target = ""~M:Class1.Method~System.Int32"")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"", Scope = ""type"", Target = ""~T:Class1"")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"", Scope = ""type"", Target = ""~T:Class2"")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"", Scope = ""type"", Target = ""~T:Class3"")]
".Replace("<", "&lt;").Replace(">", "&gt;");
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"", Scope = ""type"", Target = ""~T:Class3"")]"
.Replace("<", "&lt;").Replace(">", "&gt;");
var expected = @"
<Workspace>
......@@ -710,9 +708,8 @@ class Class2
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""NoLocationDiagnostic"", ""NoLocationDiagnostic:NoLocationDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"")]
".Replace("<", "&lt;").Replace(">", "&gt;");
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""NoLocationDiagnostic"", ""NoLocationDiagnostic:NoLocationDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"")]"
.Replace("<", "&lt;").Replace(">", "&gt;");
var expected = @"
<Workspace>
......
......@@ -43,7 +43,7 @@ public async Task<Solution> GetFixAllChangedSolutionAsync(FixAllProvider fixAllP
}
fixAllContext.CancellationToken.ThrowIfCancellationRequested();
return await codeAction.GetChangedSolutionInternalAsync(fixAllContext.CancellationToken).ConfigureAwait(false);
return await codeAction.GetChangedSolutionInternalAsync(cancellationToken: fixAllContext.CancellationToken).ConfigureAwait(false);
}
public async Task<IEnumerable<CodeActionOperation>> GetFixAllOperationsAsync(FixAllProvider fixAllProvider, FixAllContext fixAllContext, string fixAllTitle, string waitDialogMessage, bool showPreviewChangesDialog)
......@@ -126,7 +126,7 @@ private async Task<IEnumerable<CodeActionOperation>> GetFixAllOperationsAsync(Co
}
cancellationToken.ThrowIfCancellationRequested();
var newSolution = await codeAction.GetChangedSolutionInternalAsync(cancellationToken).ConfigureAwait(false);
var newSolution = await codeAction.GetChangedSolutionInternalAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
if (showPreviewChangesDialog)
{
......
......@@ -70,7 +70,8 @@ public Solution GetChangedSolution(CancellationToken cancellationToken)
var extensionManager = this.Workspace.Services.GetService<IExtensionManager>();
extensionManager.PerformAction(Provider, () =>
{
newSolution = CodeAction.GetChangedSolutionInternalAsync(cancellationToken).WaitAndGetResult(cancellationToken);
// We don't need to post process changes here as the inner code action created for Fix multiple code fix already executes.
newSolution = CodeAction.GetChangedSolutionInternalAsync(postProcessChanges: false, cancellationToken: cancellationToken).WaitAndGetResult(cancellationToken);
});
return newSolution;
......
......@@ -1482,7 +1482,7 @@ End Class]]>
' a specific target and scoped to a namespace, type, member, etc.
<Assembly: Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification:=""<Pending>"", Scope:=""type"", Target:=""Class1"")>
<Assembly: Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification:=""{FeaturesResources.SuppressionPendingJustification}"", Scope:=""type"", Target:=""~T:Class2"")>
<Assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification:=""{FeaturesResources.SuppressionPendingJustification}"", Scope:=""type"", Target:=""~T:Class2"")>
"
Test(source.ToString(), expected)
......@@ -1518,7 +1518,7 @@ End Class
' Project-level suppressions either have no target or are given
' a specific target and scoped to a namespace, type, member, etc.
<Assembly: Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification:=""{FeaturesResources.SuppressionPendingJustification}"", Scope:=""type"", Target:=""~T:Class2"")>
<Assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification:=""{FeaturesResources.SuppressionPendingJustification}"", Scope:=""type"", Target:=""~T:Class2"")>
"
Test(source.ToString(), expected)
......@@ -1564,7 +1564,7 @@ End Class
' a specific target and scoped to a namespace, type, member, etc.
<Assembly: Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification:=""<Pending>"", Scope:=""type"", Target:=""Class1"")>
<Assembly: Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification:=""{FeaturesResources.SuppressionPendingJustification}"", Scope:=""type"", Target:=""~T:Class2"")>
<Assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification:=""{FeaturesResources.SuppressionPendingJustification}"", Scope:=""type"", Target:=""~T:Class2"")>
"
Test(source.ToString(), expected)
......
......@@ -4,12 +4,12 @@
using System.Composition;
using System.Globalization;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CodeFixes.Suppression;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Simplification;
namespace Microsoft.CodeAnalysis.CSharp.CodeFixes.Suppression
{
......@@ -92,7 +92,7 @@ protected override bool IsEndOfFileToken(SyntaxToken token)
return token.Kind() == SyntaxKind.EndOfFileToken;
}
protected override SyntaxNode AddGlobalSuppressMessageAttribute(SyntaxNode newRoot, ISymbol targetSymbol, Diagnostic diagnostic)
protected override SyntaxNode AddGlobalSuppressMessageAttribute(SyntaxNode newRoot, ISymbol targetSymbol, Diagnostic diagnostic, Workspace workspace, CancellationToken cancellationToken)
{
var compilationRoot = (CompilationUnitSyntax)newRoot;
var isFirst = !compilationRoot.AttributeLists.Any();
......@@ -100,6 +100,7 @@ protected override SyntaxNode AddGlobalSuppressMessageAttribute(SyntaxNode newRo
SyntaxFactory.TriviaList(SyntaxFactory.Comment(GlobalSuppressionsFileHeaderComment)) :
default(SyntaxTriviaList);
var attributeList = CreateAttributeList(targetSymbol, diagnostic, leadingTrivia: leadingTriviaForAttributeList, needsLeadingEndOfLine: !isFirst);
attributeList = (AttributeListSyntax)Formatter.Format(attributeList, workspace, cancellationToken: cancellationToken);
return compilationRoot.AddAttributeLists(attributeList);
}
......@@ -110,8 +111,7 @@ protected override SyntaxNode AddGlobalSuppressMessageAttribute(SyntaxNode newRo
bool needsLeadingEndOfLine)
{
var attributeArguments = CreateAttributeArguments(targetSymbol, diagnostic);
var attribute = SyntaxFactory.Attribute(SyntaxFactory.ParseName(SuppressMessageAttributeName), attributeArguments)
.WithAdditionalAnnotations(Simplifier.Annotation);
var attribute = SyntaxFactory.Attribute(SyntaxFactory.ParseName(SuppressMessageAttributeName), attributeArguments);
var attributes = new SeparatedSyntaxList<AttributeSyntax>().Add(attribute);
var targetSpecifier = SyntaxFactory.AttributeTargetSpecifier(SyntaxFactory.Token(SyntaxKind.AssemblyKeyword));
......@@ -124,9 +124,7 @@ protected override SyntaxNode AddGlobalSuppressMessageAttribute(SyntaxNode newRo
triviaList = triviaList.Add(endOfLineTrivia);
}
return attributeList
.WithLeadingTrivia(leadingTrivia.AddRange(triviaList))
.WithAdditionalAnnotations(Formatter.Annotation);
return attributeList.WithLeadingTrivia(leadingTrivia.AddRange(triviaList));
}
private AttributeArgumentListSyntax CreateAttributeArguments(ISymbol targetSymbol, Diagnostic 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.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
......@@ -50,7 +44,7 @@ public async override Task<CodeAction> GetFixAsync(FixAllContext fixAllContext)
return !isGlobalSuppression ?
await batchFixer.GetFixAsync(documentsAndDiagnosticsToFixMap, fixAllContext).ConfigureAwait(false) :
CreateGlobalSuppressionFixAllAction(title, suppressionFixer, fixAllContext.Document, documentsAndDiagnosticsToFixMap);
GlobalSuppressMessageFixAllCodeAction.Create(title, suppressionFixer, fixAllContext.Document, documentsAndDiagnosticsToFixMap);
}
else
{
......@@ -60,23 +54,9 @@ public async override Task<CodeAction> GetFixAsync(FixAllContext fixAllContext)
return !isGlobalSuppression ?
await batchFixer.GetFixAsync(projectsAndDiagnosticsToFixMap, fixAllContext).ConfigureAwait(false) :
CreateGlobalSuppressionFixAllAction(title, suppressionFixer, fixAllContext.Project, projectsAndDiagnosticsToFixMap);
GlobalSuppressMessageFixAllCodeAction.Create(title, suppressionFixer, fixAllContext.Project, projectsAndDiagnosticsToFixMap);
}
}
private static CodeAction CreateGlobalSuppressionFixAllAction(string title, AbstractSuppressionCodeFixProvider fixer, Document triggerDocument, ImmutableDictionary<Document, ImmutableArray<Diagnostic>> diagnosticsByDocument)
{
return new CodeAction.SolutionChangeAction(title,
ct => GlobalSuppressMessageFixAllCodeAction.CreateChangedSolutionAsync(fixer, triggerDocument, diagnosticsByDocument, ct),
equivalenceKey: title);
}
private static CodeAction CreateGlobalSuppressionFixAllAction(string title, AbstractSuppressionCodeFixProvider fixer, Project triggerProject, ImmutableDictionary<Project, ImmutableArray<Diagnostic>> diagnosticsByProject)
{
return new CodeAction.SolutionChangeAction(title,
ct => GlobalSuppressMessageFixAllCodeAction.CreateChangedSolutionAsync(fixer, triggerProject, diagnosticsByProject, ct),
equivalenceKey: title);
}
}
}
}
......@@ -23,9 +23,10 @@ public GlobalSuppressMessageCodeAction(ISymbol targetSymbol, Project project, Di
protected override async Task<Document> GetChangedSuppressionDocumentAsync(CancellationToken cancellationToken)
{
var suppressionsDoc = await GetOrCreateSuppressionsDocumentAsync(cancellationToken).ConfigureAwait(false);
var workspace = suppressionsDoc.Project.Solution.Workspace;
var suppressionsRoot = await suppressionsDoc.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var semanticModel = await suppressionsDoc.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
suppressionsRoot = Fixer.AddGlobalSuppressMessageAttribute(suppressionsRoot, _targetSymbol, _diagnostic);
suppressionsRoot = Fixer.AddGlobalSuppressMessageAttribute(suppressionsRoot, _targetSymbol, _diagnostic, workspace, cancellationToken);
return suppressionsDoc.WithSyntaxRoot(suppressionsRoot);
}
......
// 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
......@@ -14,14 +16,42 @@ internal abstract partial class AbstractSuppressionCodeFixProvider : ISuppressio
internal sealed class GlobalSuppressMessageFixAllCodeAction : AbstractGlobalSuppressMessageCodeAction
{
private readonly IEnumerable<KeyValuePair<ISymbol, ImmutableArray<Diagnostic>>> _diagnosticsBySymbol;
private GlobalSuppressMessageFixAllCodeAction(AbstractSuppressionCodeFixProvider fixer, IEnumerable<KeyValuePair<ISymbol, ImmutableArray<Diagnostic>>> diagnosticsBySymbol, Project project)
: base(fixer, project)
{
_diagnosticsBySymbol = diagnosticsBySymbol;
}
internal static async Task<Solution> CreateChangedSolutionAsync(AbstractSuppressionCodeFixProvider fixer, Document triggerDocument, ImmutableDictionary<Document, ImmutableArray<Diagnostic>> diagnosticsByDocument, CancellationToken cancellationToken)
internal static CodeAction Create(string title, AbstractSuppressionCodeFixProvider fixer, Document triggerDocument, ImmutableDictionary<Document, ImmutableArray<Diagnostic>> diagnosticsByDocument)
{
return new GlobalSuppressionSolutionChangeAction(title,
ct => CreateChangedSolutionAsync(fixer, triggerDocument, diagnosticsByDocument, ct),
equivalenceKey: title);
}
internal static CodeAction Create(string title, AbstractSuppressionCodeFixProvider fixer, Project triggerProject, ImmutableDictionary<Project, ImmutableArray<Diagnostic>> diagnosticsByProject)
{
return new GlobalSuppressionSolutionChangeAction(title,
ct => CreateChangedSolutionAsync(fixer, triggerProject, diagnosticsByProject, ct),
equivalenceKey: title);
}
private class GlobalSuppressionSolutionChangeAction : SolutionChangeAction
{
public GlobalSuppressionSolutionChangeAction(string title, Func<CancellationToken, Task<Solution>> createChangedSolution, string equivalenceKey)
: base(title, createChangedSolution, equivalenceKey)
{
}
protected override Task<Document> PostProcessChangesAsync(Document document, CancellationToken cancellationToken)
{
// PERF: We don't to formatting on the entire global suppressions document, but instead do it for each attribute individual in the fixer.
return Task.FromResult(document);
}
}
private static async Task<Solution> CreateChangedSolutionAsync(AbstractSuppressionCodeFixProvider fixer, Document triggerDocument, ImmutableDictionary<Document, ImmutableArray<Diagnostic>> diagnosticsByDocument, CancellationToken cancellationToken)
{
var currentSolution = triggerDocument.Project.Solution;
foreach (var grouping in diagnosticsByDocument.GroupBy(d => d.Key.Project))
......@@ -40,7 +70,7 @@ internal static async Task<Solution> CreateChangedSolutionAsync(AbstractSuppress
return currentSolution;
}
internal static async Task<Solution> CreateChangedSolutionAsync(AbstractSuppressionCodeFixProvider fixer, Project triggerProject, ImmutableDictionary<Project, ImmutableArray<Diagnostic>> diagnosticsByProject, CancellationToken cancellationToken)
private static async Task<Solution> CreateChangedSolutionAsync(AbstractSuppressionCodeFixProvider fixer, Project triggerProject, ImmutableDictionary<Project, ImmutableArray<Diagnostic>> diagnosticsByProject, CancellationToken cancellationToken)
{
var currentSolution = triggerProject.Solution;
foreach (var kvp in diagnosticsByProject)
......@@ -65,8 +95,9 @@ internal static async Task<Solution> CreateChangedSolutionAsync(AbstractSuppress
protected override async Task<Document> GetChangedSuppressionDocumentAsync(CancellationToken cancellationToken)
{
var suppressionsDoc = await GetOrCreateSuppressionsDocumentAsync(cancellationToken).ConfigureAwait(false);
var workspace = suppressionsDoc.Project.Solution.Workspace;
var suppressionsRoot = await suppressionsDoc.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
foreach (var kvp in _diagnosticsBySymbol)
{
var targetSymbol = kvp.Key;
......@@ -75,7 +106,7 @@ protected override async Task<Document> GetChangedSuppressionDocumentAsync(Cance
foreach (var diagnostic in diagnostics)
{
Contract.ThrowIfFalse(!diagnostic.IsSuppressed);
suppressionsRoot = Fixer.AddGlobalSuppressMessageAttribute(suppressionsRoot, targetSymbol, diagnostic);
suppressionsRoot = Fixer.AddGlobalSuppressMessageAttribute(suppressionsRoot, targetSymbol, diagnostic, workspace, cancellationToken);
}
}
......
......@@ -15,7 +15,7 @@ namespace Microsoft.CodeAnalysis.CodeFixes.Suppression
{
internal abstract partial class AbstractSuppressionCodeFixProvider : ISuppressionFixProvider
{
public const string SuppressMessageAttributeName = "System.Diagnostics.CodeAnalysis.SuppressMessageAttribute";
public const string SuppressMessageAttributeName = "System.Diagnostics.CodeAnalysis.SuppressMessage";
private static readonly string s_globalSuppressionsFileName = "GlobalSuppressions";
private static readonly string s_suppressionsFileCommentTemplate =
@"
......@@ -42,7 +42,7 @@ public bool CanBeSuppressedOrUnsuppressed(Diagnostic diagnostic)
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);
protected abstract SyntaxNode AddGlobalSuppressMessageAttribute(SyntaxNode newRoot, ISymbol targetSymbol, Diagnostic diagnostic, Workspace workspace, CancellationToken cancellationToken);
protected abstract string DefaultFileExtension { get; }
protected abstract string SingleLineCommentStart { get; }
......
......@@ -28,14 +28,14 @@ public async override Task RegisterCodeFixesAsync(CodeFixContext context)
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);
var suppressionFixes = await _suppressionFixProvider.GetSuppressionsAsync(context.Document, context.Span, documentDiagnostics, 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);
var suppressionFixes = await _suppressionFixProvider.GetSuppressionsAsync(context.Project, projectDiagnostics, context.CancellationToken).ConfigureAwait(false);
RegisterSuppressionFixes(context, suppressionFixes);
}
}
......
......@@ -414,8 +414,8 @@ protected async Task<DiagnosticAnalyzerDriver> GetDiagnosticAnalyzerDriverAsync(
if (document != null)
{
Contract.Requires(stateType != StateType.Project);
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
return new DiagnosticAnalyzerDriver(document, root.FullSpan, root, this.Owner, cancellationToken);
var root = document.SupportsSyntaxTree ? await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false) : null;
return new DiagnosticAnalyzerDriver(document, root?.FullSpan, root, this.Owner, cancellationToken);
}
var project = documentOrProject as Project;
......
......@@ -2,10 +2,10 @@
Imports System.Composition
Imports System.Globalization
Imports System.Threading
Imports Microsoft.CodeAnalysis.CodeFixes
Imports Microsoft.CodeAnalysis.CodeFixes.Suppression
Imports Microsoft.CodeAnalysis.Formatting
Imports Microsoft.CodeAnalysis.Simplification
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
......@@ -119,7 +119,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.Suppression
Return token.Kind = SyntaxKind.EndOfFileToken
End Function
Protected Overrides Function AddGlobalSuppressMessageAttribute(newRoot As SyntaxNode, targetSymbol As ISymbol, diagnostic As Diagnostic) As SyntaxNode
Protected Overrides Function AddGlobalSuppressMessageAttribute(newRoot As SyntaxNode, targetSymbol As ISymbol, diagnostic As Diagnostic, workspace As Workspace, cancellationToken As CancellationToken) As SyntaxNode
Dim compilationRoot = DirectCast(newRoot, CompilationUnitSyntax)
Dim isFirst = Not compilationRoot.Attributes.Any()
Dim attributeList = CreateAttributeList(targetSymbol, diagnostic)
......@@ -130,7 +130,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.Suppression
attributeStatement = attributeStatement.WithLeadingTrivia(attributeStatement.GetLeadingTrivia.Add(SyntaxFactory.ElasticCarriageReturnLineFeed))
End If
attributeStatement = attributeStatement.WithAdditionalAnnotations(Formatter.Annotation)
attributeStatement = CType(Formatter.Format(attributeStatement, workspace, cancellationToken:=cancellationToken), AttributesStatementSyntax)
Dim leadingTrivia = If(isFirst AndAlso Not compilationRoot.HasLeadingTrivia,
SyntaxFactory.TriviaList(SyntaxFactory.CommentTrivia(GlobalSuppressionsFileHeaderComment)),
......@@ -145,8 +145,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.Suppression
Dim attributeName = SyntaxFactory.ParseName(SuppressMessageAttributeName)
Dim attributeArguments = CreateAttributeArguments(targetSymbol, diagnostic)
Dim attribute As AttributeSyntax = SyntaxFactory.Attribute(attributeTarget, attributeName, attributeArguments) _
.WithAdditionalAnnotations(Simplifier.Annotation)
Dim attribute As AttributeSyntax = SyntaxFactory.Attribute(attributeTarget, attributeName, attributeArguments)
Return SyntaxFactory.AttributeList().AddAttributes(attribute)
End Function
......
......@@ -17,13 +17,11 @@
using Microsoft.CodeAnalysis.Editor.Implementation.Suggestions;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Microsoft.VisualStudio.LanguageServices.Implementation.TableDataSource;
using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Shell.TableControl;
using Roslyn.Utilities;
using Task = System.Threading.Tasks.Task;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.Suppression
{
/// <summary>
......@@ -35,6 +33,7 @@ internal sealed class VisualStudioSuppressionFixService : IVisualStudioSuppressi
private readonly VisualStudioWorkspaceImpl _workspace;
private readonly IWpfTableControl _tableControl;
private readonly IDiagnosticAnalyzerService _diagnosticService;
private readonly ExternalErrorDiagnosticUpdateSource _buildErrorDiagnosticService;
private readonly ICodeFixService _codeFixService;
private readonly IFixMultipleOccurrencesService _fixMultipleOccurencesService;
private readonly ICodeActionEditHandlerService _editHandlerService;
......@@ -46,6 +45,7 @@ internal sealed class VisualStudioSuppressionFixService : IVisualStudioSuppressi
SVsServiceProvider serviceProvider,
VisualStudioWorkspaceImpl workspace,
IDiagnosticAnalyzerService diagnosticService,
ExternalErrorDiagnosticUpdateSource buildErrorDiagnosticService,
ICodeFixService codeFixService,
ICodeActionEditHandlerService editHandlerService,
IVisualStudioDiagnosticListSuppressionStateService suppressionStateService,
......@@ -53,6 +53,7 @@ internal sealed class VisualStudioSuppressionFixService : IVisualStudioSuppressi
{
_workspace = workspace;
_diagnosticService = diagnosticService;
_buildErrorDiagnosticService = buildErrorDiagnosticService;
_codeFixService = codeFixService;
_suppressionStateService = (VisualStudioDiagnosticListSuppressionStateService)suppressionStateService;
_editHandlerService = editHandlerService;
......@@ -122,16 +123,48 @@ public bool RemoveSuppressions(bool selectedErrorListEntriesOnly, IVsHierarchy p
}
}
private async Task<ImmutableArray<DiagnosticData>> GetAllDiagnosticsAsync(Func<Project, bool> shouldFixInProject, CancellationToken cancellationToken)
private async Task<ImmutableArray<DiagnosticData>> GetAllBuildDiagnosticsAsync(Func<Project, bool> shouldFixInProject, CancellationToken cancellationToken)
{
var builder = ImmutableArray.CreateBuilder<DiagnosticData>();
var buildDiagnostics = _buildErrorDiagnosticService.GetBuildErrors().Where(d => d.ProjectId != null && d.Severity != DiagnosticSeverity.Hidden);
var solution = _workspace.CurrentSolution;
foreach (var project in solution.Projects)
foreach (var diagnosticsByProject in buildDiagnostics.GroupBy(d => d.ProjectId))
{
if (shouldFixInProject(project))
cancellationToken.ThrowIfCancellationRequested();
if (diagnosticsByProject.Key == null)
{
// Diagnostics with no projectId cannot be suppressed.
continue;
}
var project = solution.GetProject(diagnosticsByProject.Key);
if (project != null && shouldFixInProject(project))
{
var diagnostics = await _diagnosticService.GetDiagnosticsAsync(solution, project.Id, includeSuppressedDiagnostics: true, cancellationToken: cancellationToken).ConfigureAwait(false);
builder.AddRange(diagnostics.Where(d => d.Severity != DiagnosticSeverity.Hidden));
var diagnosticsByDocument = diagnosticsByProject.GroupBy(d => d.DocumentId);
foreach (var group in diagnosticsByDocument)
{
var documentId = group.Key;
if (documentId == null)
{
// Project diagnostics, just add all of them.
builder.AddRange(group);
continue;
}
// For document diagnostics, build does not have the computed text span info.
// So we explicitly calculate the text span from the source text for the diagnostics.
var document = project.GetDocument(documentId);
if (document != null)
{
var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
var text = await tree.GetTextAsync(cancellationToken).ConfigureAwait(false);
foreach (var diagnostic in group)
{
builder.Add(diagnostic.WithCalculatedSpan(text));
}
}
}
}
}
......@@ -150,16 +183,16 @@ private static string GetWaitDialogMessage(bool isAddSuppression)
private IEnumerable<DiagnosticData> GetDiagnosticsToFix(Func<Project, bool> shouldFixInProject, bool selectedEntriesOnly, bool isAddSuppression)
{
var diagnosticsToFix = SpecializedCollections.EmptyEnumerable<DiagnosticData>();
var diagnosticsToFix = ImmutableHashSet<DiagnosticData>.Empty;
Action<CancellationToken> computeDiagnosticsToFix = cancellationToken =>
{
// If we are fixing selected diagnostics in error list, then get the diagnostics from error list entry snapshots.
// Otherwise, get all diagnostics from the diagnostic service.
var diagnosticsToFixTask = selectedEntriesOnly ?
_suppressionStateService.GetSelectedItemsAsync(isAddSuppression, cancellationToken) :
GetAllDiagnosticsAsync(shouldFixInProject, cancellationToken);
_suppressionStateService.GetSelectedItemsAsync(isAddSuppression, cancellationToken):
GetAllBuildDiagnosticsAsync(shouldFixInProject, cancellationToken);
diagnosticsToFix = diagnosticsToFixTask.WaitAndGetResult(cancellationToken);
diagnosticsToFix = diagnosticsToFixTask.WaitAndGetResult(cancellationToken).ToImmutableHashSet();
};
var title = GetFixTitle(isAddSuppression);
......@@ -226,6 +259,8 @@ private bool ApplySuppressionFix(IEnumerable<DiagnosticData> diagnosticsToFix, F
return;
}
cancellationToken.ThrowIfCancellationRequested();
// Equivalence key determines what fix will be applied.
// Make sure we don't include any specific diagnostic ID, as we want all of the given diagnostics (which can have varied ID) to be fixed.
var equivalenceKey = isAddSuppression ?
......@@ -241,6 +276,8 @@ private bool ApplySuppressionFix(IEnumerable<DiagnosticData> diagnosticsToFix, F
// Use the Fix multiple occurrences service to compute a bulk suppression fix for the specified document and project diagnostics,
// show a preview changes dialog and then apply the fix to the workspace.
cancellationToken.ThrowIfCancellationRequested();
var documentDiagnosticsPerLanguage = GetDocumentDiagnosticsMappedToNewSolution(documentDiagnosticsToFixMap, newSolution, language);
if (!documentDiagnosticsPerLanguage.IsEmpty)
{
......@@ -256,7 +293,7 @@ private bool ApplySuppressionFix(IEnumerable<DiagnosticData> diagnosticsToFix, F
equivalenceKey,
title,
waitDialogMessage,
cancellationToken: CancellationToken.None);
cancellationToken);
if (newSolution == null)
{
// User cancelled or fixer threw an exception, so we just bail out.
......@@ -281,7 +318,7 @@ private bool ApplySuppressionFix(IEnumerable<DiagnosticData> diagnosticsToFix, F
equivalenceKey,
title,
waitDialogMessage,
CancellationToken.None);
cancellationToken);
if (newSolution == null)
{
// User cancelled or fixer threw an exception, so we just bail out.
......
......@@ -22,6 +22,7 @@ internal class ProjectExternalErrorReporter : IVsReportExternalErrors, IVsLangua
{
internal static readonly ImmutableDictionary<string, string> Properties = ImmutableDictionary<string, string>.Empty.Add(WellKnownDiagnosticPropertyNames.Origin, WellKnownDiagnosticTags.Build);
internal static readonly IReadOnlyList<string> CustomTags = ImmutableArray.Create(WellKnownDiagnosticTags.Telemetry);
internal static readonly IReadOnlyList<string> CompilerDiagnosticCustomTags = ImmutableArray.Create(WellKnownDiagnosticTags.Compiler, WellKnownDiagnosticTags.Telemetry);
private readonly ProjectId _projectId;
private readonly string _errorCodePrefix;
......@@ -219,6 +220,27 @@ private static DiagnosticSeverity GetDiagnosticSeverity(ExternalError error)
id, GetErrorId(error), error.bstrText, GetDiagnosticSeverity(error), null, 0, 0, 0, 0, null, 0, 0, 0, 0);
}
private static bool IsCompilerDiagnostic(string errorId)
{
if (!string.IsNullOrEmpty(errorId) && errorId.Length > 2)
{
var prefix = errorId.Substring(0, 2);
if (prefix.Equals("CS", StringComparison.OrdinalIgnoreCase) || prefix.Equals("BC", StringComparison.OrdinalIgnoreCase))
{
var suffix = errorId.Substring(2);
int id;
return int.TryParse(suffix, out id);
}
}
return false;
}
private static IReadOnlyList<string> GetCustomTags(string errorId)
{
return IsCompilerDiagnostic(errorId) ? CompilerDiagnosticCustomTags : CustomTags;
}
private DiagnosticData GetDiagnosticData(
DocumentId id, string errorId, string message, DiagnosticSeverity severity,
string mappedFilePath, int mappedStartLine, int mappedStartColumn, int mappedEndLine, int mappedEndColumn,
......@@ -228,12 +250,13 @@ private static DiagnosticSeverity GetDiagnosticSeverity(ExternalError error)
id: errorId,
category: WellKnownDiagnosticTags.Build,
message: message,
title: message,
enuMessageForBingSearch: message, // Unfortunately, there is no way to get ENU text for this since this is an external error.
severity: severity,
defaultSeverity: severity,
isEnabledByDefault: true,
warningLevel: GetWarningLevel(severity),
customTags: CustomTags,
customTags: GetCustomTags(errorId),
properties: Properties,
workspace: _workspace,
projectId: _projectId,
......
......@@ -138,12 +138,12 @@ protected virtual Task<Document> GetChangedDocumentAsync(CancellationToken cance
/// <summary>
/// used by batch fixer engine to get new solution
/// </summary>
internal async Task<Solution> GetChangedSolutionInternalAsync(CancellationToken cancellationToken)
internal async Task<Solution> GetChangedSolutionInternalAsync(bool postProcessChanges = true, CancellationToken cancellationToken = default(CancellationToken))
{
var solution = await GetChangedSolutionAsync(cancellationToken).ConfigureAwait(false);
if (solution == null)
if (solution == null || !postProcessChanges)
{
return null;
return solution;
}
return await this.PostProcessChangesAsync(solution, cancellationToken).ConfigureAwait(false);
......
......@@ -406,7 +406,7 @@ public virtual async Task<Solution> TryMergeFixesAsync(Solution oldSolution, IEn
{
cancellationToken.ThrowIfCancellationRequested();
// TODO: Parallelize GetChangedSolutionInternalAsync for codeActions
var changedSolution = await codeAction.GetChangedSolutionInternalAsync(cancellationToken).ConfigureAwait(false);
var changedSolution = await codeAction.GetChangedSolutionInternalAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
var solutionChanges = new SolutionChanges(changedSolution, oldSolution);
......
......@@ -63,6 +63,18 @@ internal sealed class DiagnosticDataLocation
OriginalEndLine = originalEndLine;
OriginalEndColumn = originalEndColumn;
}
internal DiagnosticDataLocation WithCalculatedSpan(TextSpan newSourceSpan)
{
Contract.ThrowIfTrue(this.SourceSpan.HasValue);
return new DiagnosticDataLocation(this.DocumentId,
newSourceSpan, this.OriginalFilePath,
this.OriginalStartLine, this.OriginalStartColumn,
this.OriginalEndLine, this.OriginalEndColumn,
this.MappedFilePath, this.MappedStartLine, this.MappedStartColumn,
this.MappedEndLine, this.MappedEndColumn);
}
}
internal sealed class DiagnosticData
......@@ -221,6 +233,20 @@ public TextSpan GetExistingOrCalculatedTextSpan(SourceText text)
return HasTextSpan ? TextSpan : GetTextSpan(this.DataLocation, text);
}
public DiagnosticData WithCalculatedSpan(SourceText text)
{
Contract.ThrowIfNull(this.DocumentId);
Contract.ThrowIfNull(this.DataLocation);
Contract.ThrowIfTrue(HasTextSpan);
var span = GetTextSpan(this.DataLocation, text);
var newLocation = this.DataLocation.WithCalculatedSpan(span);
return new DiagnosticData(this.Id, this.Category, this.Message, this.ENUMessageForBingSearch,
this.Severity, this.DefaultSeverity, this.IsEnabledByDefault, this.WarningLevel,
this.CustomTags, this.Properties, this.Workspace, this.ProjectId,
newLocation, this.AdditionalLocations, this.Title, this.Description, this.HelpLink, this.IsSuppressed);
}
public async Task<Diagnostic> ToDiagnosticAsync(Project project, CancellationToken cancellationToken)
{
var location = await this.DataLocation.ConvertLocationAsync(project, cancellationToken).ConfigureAwait(false);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册