diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/Suppression/SuppressionTest_FixAllTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/Suppression/SuppressionTest_FixAllTests.cs index 53cfd52f71bafc4ffe06b913786de6f0ee0b2c4f..bc5696dc8e3c402f40573d970c283f83049d6bb2 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/Suppression/SuppressionTest_FixAllTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/Suppression/SuppressionTest_FixAllTests.cs @@ -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("<", "<").Replace(">", ">"); +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"", Scope = ""type"", Target = ""~T:Class2"")]" + .Replace("<", "<").Replace(">", ">"); var expected = @" @@ -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("<", "<").Replace(">", ">"); +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""InfoDiagnostic"", ""InfoDiagnostic:InfoDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"", Scope = ""type"", Target = ""~T:Class3"")]" + .Replace("<", "<").Replace(">", ">"); var expected = @" @@ -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("<", "<").Replace(">", ">"); +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""NoLocationDiagnostic"", ""NoLocationDiagnostic:NoLocationDiagnostic"", Justification = ""{FeaturesResources.SuppressionPendingJustification}"")]" + .Replace("<", "<").Replace(">", ">"); var expected = @" diff --git a/src/EditorFeatures/Core/Implementation/Suggestions/FixAllGetFixesService.cs b/src/EditorFeatures/Core/Implementation/Suggestions/FixAllGetFixesService.cs index f4fed95073f0b43459502f4fcae5c65983b76a8e..b1ae6823054d895d9a798b8e60bd79ea7d87bd07 100644 --- a/src/EditorFeatures/Core/Implementation/Suggestions/FixAllGetFixesService.cs +++ b/src/EditorFeatures/Core/Implementation/Suggestions/FixAllGetFixesService.cs @@ -43,7 +43,7 @@ public async Task 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> GetFixAllOperationsAsync(FixAllProvider fixAllProvider, FixAllContext fixAllContext, string fixAllTitle, string waitDialogMessage, bool showPreviewChangesDialog) @@ -126,7 +126,7 @@ private async Task> GetFixAllOperationsAsync(Co } cancellationToken.ThrowIfCancellationRequested(); - var newSolution = await codeAction.GetChangedSolutionInternalAsync(cancellationToken).ConfigureAwait(false); + var newSolution = await codeAction.GetChangedSolutionInternalAsync(cancellationToken: cancellationToken).ConfigureAwait(false); if (showPreviewChangesDialog) { diff --git a/src/EditorFeatures/Core/Implementation/Suggestions/FixMultipleSuggestedAction.cs b/src/EditorFeatures/Core/Implementation/Suggestions/FixMultipleSuggestedAction.cs index 7bd83f9a4cdd32bcb2b411eeed852bc76a39fea2..f439f1ee574782143006c49cb8fecbc18722bc2f 100644 --- a/src/EditorFeatures/Core/Implementation/Suggestions/FixMultipleSuggestedAction.cs +++ b/src/EditorFeatures/Core/Implementation/Suggestions/FixMultipleSuggestedAction.cs @@ -70,7 +70,8 @@ public Solution GetChangedSolution(CancellationToken cancellationToken) var extensionManager = this.Workspace.Services.GetService(); 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; diff --git a/src/EditorFeatures/VisualBasicTest/Diagnostics/Suppression/SuppressionTests.vb b/src/EditorFeatures/VisualBasicTest/Diagnostics/Suppression/SuppressionTests.vb index 8672d25f6384531ed9ac9ac67c0a9c014158bd2d..c7f004020707adc1aaec465b92e6ade90d87e5ca 100644 --- a/src/EditorFeatures/VisualBasicTest/Diagnostics/Suppression/SuppressionTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Diagnostics/Suppression/SuppressionTests.vb @@ -1482,7 +1482,7 @@ End Class]]> ' a specific target and scoped to a namespace, type, member, etc. "", Scope:=""type"", Target:=""Class1"")> - + " 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. - + " Test(source.ToString(), expected) @@ -1564,7 +1564,7 @@ End Class ' a specific target and scoped to a namespace, type, member, etc. "", Scope:=""type"", Target:=""Class1"")> - + " Test(source.ToString(), expected) diff --git a/src/Features/CSharp/Portable/CodeFixes/Suppression/CSharpSuppressionCodeFixProvider.cs b/src/Features/CSharp/Portable/CodeFixes/Suppression/CSharpSuppressionCodeFixProvider.cs index 3d39a0c1c23fb11137b8b370ce4a09ce856ea8fd..ee48072087da845919f96f54a1c5c2fad751998d 100644 --- a/src/Features/CSharp/Portable/CodeFixes/Suppression/CSharpSuppressionCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/CodeFixes/Suppression/CSharpSuppressionCodeFixProvider.cs @@ -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().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) diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.FixAllProvider.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.FixAllProvider.cs index 2f345c42b99ab01dc61f28b4be95adfb7f76b739..9243b7154fd5802584b2894ef38659509cd9e4ea 100644 --- a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.FixAllProvider.cs +++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.FixAllProvider.cs @@ -1,16 +1,10 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.Collections.Immutable; -using System.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 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 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> 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> diagnosticsByProject) - { - return new CodeAction.SolutionChangeAction(title, - ct => GlobalSuppressMessageFixAllCodeAction.CreateChangedSolutionAsync(fixer, triggerProject, diagnosticsByProject, ct), - equivalenceKey: title); - } } } } diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.GlobalSuppressMessageCodeAction.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.GlobalSuppressMessageCodeAction.cs index d51f7078e4ac92d0b1ffc9ba6fd9554c882d6afc..b7e2d5da839b6b71f379ff121f966a767ee1b22b 100644 --- a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.GlobalSuppressMessageCodeAction.cs +++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.GlobalSuppressMessageCodeAction.cs @@ -23,9 +23,10 @@ public GlobalSuppressMessageCodeAction(ISymbol targetSymbol, Project project, Di protected override async Task 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); } diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.GlobalSuppressMessageFixAllCodeAction.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.GlobalSuppressMessageFixAllCodeAction.cs index ba32cff59c5d727a63f78a48ec41d21580845a76..c6680b568f7ad808d33dfd68b1240dcec863dd92 100644 --- a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.GlobalSuppressMessageFixAllCodeAction.cs +++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.GlobalSuppressMessageFixAllCodeAction.cs @@ -1,10 +1,12 @@ // 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>> _diagnosticsBySymbol; - + private GlobalSuppressMessageFixAllCodeAction(AbstractSuppressionCodeFixProvider fixer, IEnumerable>> diagnosticsBySymbol, Project project) : base(fixer, project) { _diagnosticsBySymbol = diagnosticsBySymbol; } - internal static async Task CreateChangedSolutionAsync(AbstractSuppressionCodeFixProvider fixer, Document triggerDocument, ImmutableDictionary> diagnosticsByDocument, CancellationToken cancellationToken) + internal static CodeAction Create(string title, AbstractSuppressionCodeFixProvider fixer, Document triggerDocument, ImmutableDictionary> diagnosticsByDocument) + { + return new GlobalSuppressionSolutionChangeAction(title, + ct => CreateChangedSolutionAsync(fixer, triggerDocument, diagnosticsByDocument, ct), + equivalenceKey: title); + } + + internal static CodeAction Create(string title, AbstractSuppressionCodeFixProvider fixer, Project triggerProject, ImmutableDictionary> diagnosticsByProject) + { + return new GlobalSuppressionSolutionChangeAction(title, + ct => CreateChangedSolutionAsync(fixer, triggerProject, diagnosticsByProject, ct), + equivalenceKey: title); + } + + private class GlobalSuppressionSolutionChangeAction : SolutionChangeAction + { + public GlobalSuppressionSolutionChangeAction(string title, Func> createChangedSolution, string equivalenceKey) + : base(title, createChangedSolution, equivalenceKey) + { + } + + protected override Task 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 CreateChangedSolutionAsync(AbstractSuppressionCodeFixProvider fixer, Document triggerDocument, ImmutableDictionary> 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 CreateChangedSolutionAsync(AbstractSuppress return currentSolution; } - internal static async Task CreateChangedSolutionAsync(AbstractSuppressionCodeFixProvider fixer, Project triggerProject, ImmutableDictionary> diagnosticsByProject, CancellationToken cancellationToken) + private static async Task CreateChangedSolutionAsync(AbstractSuppressionCodeFixProvider fixer, Project triggerProject, ImmutableDictionary> diagnosticsByProject, CancellationToken cancellationToken) { var currentSolution = triggerProject.Solution; foreach (var kvp in diagnosticsByProject) @@ -65,8 +95,9 @@ internal static async Task CreateChangedSolutionAsync(AbstractSuppress protected override async Task 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 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); } } diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.cs index dd3b5c0fb09d03827636a5e54c350c9f3dc933bc..003d57a9d95824a4cbc3c7e85fd88556a98edb30 100644 --- a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.cs +++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.cs @@ -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 formatNode, bool needsLeadingEndOfLine, bool needsTrailingEndOfLine); protected abstract SyntaxTriviaList CreatePragmaRestoreDirectiveTrivia(Diagnostic diagnostic, Func formatNode, bool needsLeadingEndOfLine, bool needsTrailingEndOfLine); - protected abstract SyntaxNode AddGlobalSuppressMessageAttribute(SyntaxNode newRoot, ISymbol targetSymbol, Diagnostic diagnostic); + protected abstract SyntaxNode AddGlobalSuppressMessageAttribute(SyntaxNode newRoot, ISymbol targetSymbol, Diagnostic diagnostic, Workspace workspace, CancellationToken cancellationToken); protected abstract string DefaultFileExtension { get; } protected abstract string SingleLineCommentStart { get; } diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/WrapperCodeFixProvider.cs b/src/Features/Core/Portable/CodeFixes/Suppression/WrapperCodeFixProvider.cs index f968b32c27daba9ac1f27e5c59312bf32f6acdc9..dd7e921285bf39945f5f602a155c333c63ce561c 100644 --- a/src/Features/Core/Portable/CodeFixes/Suppression/WrapperCodeFixProvider.cs +++ b/src/Features/Core/Portable/CodeFixes/Suppression/WrapperCodeFixProvider.cs @@ -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); } } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index c07dccfa8459c152f5537aaf0f61dfb82ebe940f..1d0b16e0ed6bec66f4a21fb41c3ca9e24c4cf828 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -414,8 +414,8 @@ protected async Task 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; diff --git a/src/Features/VisualBasic/Portable/CodeFixes/Suppression/VisualBasicSuppressionCodeFixProvider.vb b/src/Features/VisualBasic/Portable/CodeFixes/Suppression/VisualBasicSuppressionCodeFixProvider.vb index 15e2c4127bd18f70001e279ed87e1ae36d7fd3b3..ea1479929481d5cffcdc2745c1b3f74fb65fb153 100644 --- a/src/Features/VisualBasic/Portable/CodeFixes/Suppression/VisualBasicSuppressionCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/CodeFixes/Suppression/VisualBasicSuppressionCodeFixProvider.vb @@ -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 diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs index 8c464e8c7dd38c7dd14c1dff1639798ce1877900..fcea20c73acce4c7f8ca3d2b08b889b46b2a66a2 100644 --- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs +++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs @@ -17,6 +17,7 @@ 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; @@ -35,6 +36,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 +48,7 @@ internal sealed class VisualStudioSuppressionFixService : IVisualStudioSuppressi SVsServiceProvider serviceProvider, VisualStudioWorkspaceImpl workspace, IDiagnosticAnalyzerService diagnosticService, + ExternalErrorDiagnosticUpdateSource buildErrorDiagnosticService, ICodeFixService codeFixService, ICodeActionEditHandlerService editHandlerService, IVisualStudioDiagnosticListSuppressionStateService suppressionStateService, @@ -53,6 +56,7 @@ internal sealed class VisualStudioSuppressionFixService : IVisualStudioSuppressi { _workspace = workspace; _diagnosticService = diagnosticService; + _buildErrorDiagnosticService = buildErrorDiagnosticService; _codeFixService = codeFixService; _suppressionStateService = (VisualStudioDiagnosticListSuppressionStateService)suppressionStateService; _editHandlerService = editHandlerService; @@ -122,16 +126,48 @@ public bool RemoveSuppressions(bool selectedErrorListEntriesOnly, IVsHierarchy p } } - private async Task> GetAllDiagnosticsAsync(Func shouldFixInProject, CancellationToken cancellationToken) + private async Task> GetAllBuildDiagnosticsAsync(Func shouldFixInProject, CancellationToken cancellationToken) { var builder = ImmutableArray.CreateBuilder(); + 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) { - var diagnostics = await _diagnosticService.GetDiagnosticsAsync(solution, project.Id, includeSuppressedDiagnostics: true, cancellationToken: cancellationToken).ConfigureAwait(false); - builder.AddRange(diagnostics.Where(d => d.Severity != DiagnosticSeverity.Hidden)); + // Diagnostics with no projectId cannot be suppressed. + continue; + } + + var project = solution.GetProject(diagnosticsByProject.Key); + if (project != null && shouldFixInProject(project)) + { + 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 +186,16 @@ private static string GetWaitDialogMessage(bool isAddSuppression) private IEnumerable GetDiagnosticsToFix(Func shouldFixInProject, bool selectedEntriesOnly, bool isAddSuppression) { - var diagnosticsToFix = SpecializedCollections.EmptyEnumerable(); + var diagnosticsToFix = ImmutableHashSet.Empty; Action 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 +262,8 @@ private bool ApplySuppressionFix(IEnumerable 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 +279,8 @@ private bool ApplySuppressionFix(IEnumerable 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 +296,7 @@ private bool ApplySuppressionFix(IEnumerable 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 +321,7 @@ private bool ApplySuppressionFix(IEnumerable diagnosticsToFix, F equivalenceKey, title, waitDialogMessage, - CancellationToken.None); + cancellationToken); if (newSolution == null) { // User cancelled or fixer threw an exception, so we just bail out. diff --git a/src/VisualStudio/Core/Def/Implementation/TaskList/ProjectExternalErrorReporter.cs b/src/VisualStudio/Core/Def/Implementation/TaskList/ProjectExternalErrorReporter.cs index 5ba62857c89c7a084aaeff5a849c62874ea042c1..15fd5ed2e2d198b37312b9533f224db9ffc26eee 100644 --- a/src/VisualStudio/Core/Def/Implementation/TaskList/ProjectExternalErrorReporter.cs +++ b/src/VisualStudio/Core/Def/Implementation/TaskList/ProjectExternalErrorReporter.cs @@ -22,6 +22,7 @@ internal class ProjectExternalErrorReporter : IVsReportExternalErrors, IVsLangua { internal static readonly ImmutableDictionary Properties = ImmutableDictionary.Empty.Add(WellKnownDiagnosticPropertyNames.Origin, WellKnownDiagnosticTags.Build); internal static readonly IReadOnlyList CustomTags = ImmutableArray.Create(WellKnownDiagnosticTags.Telemetry); + internal static readonly IReadOnlyList 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 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, diff --git a/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs b/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs index 4fb866867f5405529a25b95ed225a3add43f68c5..094f2f3d600f5fc6fa60f4f7d79eeaffe805826e 100644 --- a/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs +++ b/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs @@ -138,12 +138,12 @@ protected virtual Task GetChangedDocumentAsync(CancellationToken cance /// /// used by batch fixer engine to get new solution /// - internal async Task GetChangedSolutionInternalAsync(CancellationToken cancellationToken) + internal async Task 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); diff --git a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/BatchFixAllProvider.cs b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/BatchFixAllProvider.cs index 093132bfbca1293f8e7a39b3f309a0b1836a8a93..1489323b9eb189f3353d38c5c08781c945fa2682 100644 --- a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/BatchFixAllProvider.cs +++ b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/BatchFixAllProvider.cs @@ -406,7 +406,7 @@ public virtual async Task 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); diff --git a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs index ede854a4833ccd9cdac7b1a9c8dabc105f0c9d25..3d192542061800b45be3df907bd7670c918fe867 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs @@ -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 ToDiagnosticAsync(Project project, CancellationToken cancellationToken) { var location = await this.DataLocation.ConvertLocationAsync(project, cancellationToken).ConfigureAwait(false);