diff --git a/src/Analyzers/Core/Analyzers/IDEDiagnosticIdToOptionMappingHelper.cs b/src/Analyzers/Core/Analyzers/IDEDiagnosticIdToOptionMappingHelper.cs index 7d5cf8bebc633..aea5fefc28a35 100644 --- a/src/Analyzers/Core/Analyzers/IDEDiagnosticIdToOptionMappingHelper.cs +++ b/src/Analyzers/Core/Analyzers/IDEDiagnosticIdToOptionMappingHelper.cs @@ -27,6 +27,10 @@ public static bool TryGetMappedOptions(string diagnosticId, string language, [No (s_diagnosticIdToLanguageSpecificOptionsMap.TryGetValue(language, out var map) && map.TryGetValue(diagnosticId, out options)); + public static bool IsKnownIDEDiagnosticId(string diagnosticId) + => s_diagnosticIdToOptionMap.ContainsKey(diagnosticId) || + s_diagnosticIdToLanguageSpecificOptionsMap.Values.Any(map => map.ContainsKey(diagnosticId)); + public static void AddOptionMapping(string diagnosticId, ImmutableHashSet perLanguageOptions) { diagnosticId = diagnosticId ?? throw new ArgumentNullException(nameof(diagnosticId)); diff --git a/src/EditorFeatures/CSharp/CodeCleanup/CSharpCodeCleanupService.cs b/src/EditorFeatures/CSharp/CodeCleanup/CSharpCodeCleanupService.cs index e5fdf3c0feb56..9f9e69f32e1ca 100644 --- a/src/EditorFeatures/CSharp/CodeCleanup/CSharpCodeCleanupService.cs +++ b/src/EditorFeatures/CSharp/CodeCleanup/CSharpCodeCleanupService.cs @@ -267,8 +267,8 @@ internal class CSharpCodeCleanupService : AbstractCodeCleanupService [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpCodeCleanupService(ICodeFixService codeFixService) - : base(codeFixService) + public CSharpCodeCleanupService(ICodeFixService codeFixService, IDiagnosticAnalyzerService diagnosticAnalyzerService) + : base(codeFixService, diagnosticAnalyzerService) { } diff --git a/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.TestFixers.cs b/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.TestFixers.cs new file mode 100644 index 0000000000000..5665f1ed2ffcb --- /dev/null +++ b/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.TestFixers.cs @@ -0,0 +1,179 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Formatting +{ + public partial class CodeCleanupTests + { + private abstract class TestThirdPartyCodeFix : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create("HasDefaultCase"); + + public override Task RegisterCodeFixesAsync(CodeFixContext context) + { + foreach (var diagnostic in context.Diagnostics) + { + context.RegisterCodeFix( + CodeAction.Create( + "Remove default case", + async cancellationToken => + { + var root = await context.Document.GetSyntaxRootAsync(cancellationToken); + Assumes.NotNull(root); + var sourceTree = diagnostic.Location.SourceTree; + Assumes.NotNull(sourceTree); + var node = (await sourceTree.GetRootAsync(cancellationToken)).FindNode(diagnostic.Location.SourceSpan); + Assumes.NotNull(node?.Parent); + var newRoot = root.RemoveNode(node.Parent, SyntaxRemoveOptions.KeepNoTrivia); + Assumes.NotNull(newRoot); + return context.Document.WithSyntaxRoot(newRoot); + }, + nameof(TestThirdPartyCodeFix)), + diagnostic); + } + + return Task.CompletedTask; + } + } + + [PartNotDiscoverable, Shared, ExportCodeFixProvider(LanguageNames.CSharp)] + private class TestThirdPartyCodeFixWithFixAll : TestThirdPartyCodeFix + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public TestThirdPartyCodeFixWithFixAll() + { + } + + public override FixAllProvider GetFixAllProvider() => BatchFixAllProvider.Instance; + } + + [PartNotDiscoverable, Shared, ExportCodeFixProvider(LanguageNames.CSharp)] + private class TestThirdPartyCodeFixWithOutFixAll : TestThirdPartyCodeFix + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public TestThirdPartyCodeFixWithOutFixAll() + { + } + } + + [PartNotDiscoverable, Shared, ExportCodeFixProvider(LanguageNames.CSharp)] + private class TestThirdPartyCodeFixModifiesSolution : TestThirdPartyCodeFix + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public TestThirdPartyCodeFixModifiesSolution() + { + } + + public override FixAllProvider GetFixAllProvider() => new ModifySolutionFixAll(); + + private class ModifySolutionFixAll : FixAllProvider + { + public override Task GetFixAsync(FixAllContext fixAllContext) + { + var solution = fixAllContext.Solution; + return Task.FromResult(CodeAction.Create( + "Remove default case", + async cancellationToken => + { + var toFix = await fixAllContext.GetDocumentDiagnosticsToFixAsync(); + Project? project = null; + foreach (var kvp in toFix) + { + var document = kvp.Key; + project ??= document.Project; + var diagnostics = kvp.Value; + var root = await document.GetSyntaxRootAsync(cancellationToken); + Assumes.NotNull(root); + foreach (var diagnostic in diagnostics) + { + var sourceTree = diagnostic.Location.SourceTree; + Assumes.NotNull(sourceTree); + var node = (await sourceTree.GetRootAsync(cancellationToken)).FindNode(diagnostic.Location.SourceSpan); + Assumes.NotNull(node?.Parent); + var newRoot = root.RemoveNode(node.Parent, SyntaxRemoveOptions.KeepNoTrivia); + Assumes.NotNull(newRoot); + document = document.WithSyntaxRoot(newRoot); + } + + solution = solution.WithDocumentText(document.Id, await document.GetTextAsync()); + } + + Assumes.NotNull(project); + return solution.AddDocument(DocumentId.CreateNewId(project.Id), "new.cs", SourceText.From("")); + }, + nameof(TestThirdPartyCodeFix))); + } + } + } + + [PartNotDiscoverable, Shared, ExportCodeFixProvider(LanguageNames.CSharp)] + private class TestThirdPartyCodeFixDoesNotSupportDocumentScope : TestThirdPartyCodeFix + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public TestThirdPartyCodeFixDoesNotSupportDocumentScope() + { + } + + public override FixAllProvider GetFixAllProvider() => new ModifySolutionFixAll(); + + private class ModifySolutionFixAll : FixAllProvider + { + public override IEnumerable GetSupportedFixAllScopes() + { + return new[] { FixAllScope.Project, FixAllScope.Solution, FixAllScope.Custom }; + } + + public override Task GetFixAsync(FixAllContext fixAllContext) + { + var solution = fixAllContext.Solution; + return Task.FromResult(CodeAction.Create( + "Remove default case", + async cancellationToken => + { + var toFix = await fixAllContext.GetDocumentDiagnosticsToFixAsync(); + Project? project = null; + foreach (var kvp in toFix) + { + var document = kvp.Key; + project ??= document.Project; + var diagnostics = kvp.Value; + var root = await document.GetSyntaxRootAsync(cancellationToken); + Assumes.NotNull(root); + foreach (var diagnostic in diagnostics) + { + var sourceTree = diagnostic.Location.SourceTree; + Assumes.NotNull(sourceTree); + var node = (await sourceTree.GetRootAsync(cancellationToken)).FindNode(diagnostic.Location.SourceSpan); + Assumes.NotNull(node?.Parent); + var newRoot = root.RemoveNode(node.Parent, SyntaxRemoveOptions.KeepNoTrivia); + Assumes.NotNull(newRoot); + document = document.WithSyntaxRoot(newRoot); + } + + solution = solution.WithDocumentText(document.Id, await document.GetTextAsync()); + } + + Assumes.NotNull(project); + return solution.AddDocument(DocumentId.CreateNewId(project.Id), "new.cs", SourceText.From("")); + }, + nameof(TestThirdPartyCodeFix))); + } + } + } + } +} diff --git a/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.cs b/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.cs index ca55e71dfa792..a8c1fc1fc01bc 100644 --- a/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.cs +++ b/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.cs @@ -4,13 +4,16 @@ #nullable disable +using System; +using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; -using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.AddImport; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeCleanup; +using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Formatting; @@ -27,13 +30,15 @@ using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.UnitTests.Diagnostics; using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Formatting { [UseExportProvider] - public class CodeCleanupTests + public partial class CodeCleanupTests { [Fact] [Trait(Traits.Feature, Traits.Features.CodeCleanup)] @@ -104,7 +109,7 @@ public Task SortGlobalUsings() global using System; class Program { - static async Task Main(string[] args) + static Task Main(string[] args) { Barrier b = new Barrier(0); var list = new List(); @@ -121,7 +126,7 @@ static async Task Main(string[] args) internal class Program { - private static async Task Main(string[] args) + private static Task Main(string[] args) { Barrier b = new(0); List list = new(); @@ -572,6 +577,203 @@ public void VerifyAllCodeStyleFixersAreSupportedByCodeCleanup(string language, i Assert.Equal(expectedNumberOfUnsupportedDiagnosticIds, unsupportedDiagnosticIds.Length); } + private const string _code = @" +class C +{ + public void M1(int x, int y) + { + switch (x) + { + case 1: + case 10: + break; + default: + break; + } + + switch (y) + { + case 1: + break; + case 1000: + default: + break; + } + + switch (x) + { + case 1: + break; + case 1000: + break; + } + + switch (y) + { + default: + break; + } + + switch (y) { } + + switch (x) + { + case : + case 1000: + break; + } + } +} +"; + + private const string _fixed = @" +class C +{ + public void M1(int x, int y) + { + switch (x) + { + case 1: + case 10: + break; + } + + switch (y) + { + case 1: + break; + } + + switch (x) + { + case 1: + break; + case 1000: + break; + } + + switch (y) + { + } + + switch (y) { } + + switch (x) + { + case : + case 1000: + break; + } + } +} +"; + + [Fact] + [Trait(Traits.Feature, Traits.Features.CodeCleanup)] + public async Task RunThirdPartyFixer() + { + await TestThirdPartyCodeFixerApplied(_code, _fixed); + } + + [Fact] + [Trait(Traits.Feature, Traits.Features.CodeCleanup)] + public async Task DoNotRunThirdPartyFixerWithNoFixAll() + { + await TestThirdPartyCodeFixerNoChanges(_code); + } + + [Theory] + [InlineData(DiagnosticSeverity.Warning)] + [InlineData(DiagnosticSeverity.Error)] + [Trait(Traits.Feature, Traits.Features.CodeCleanup)] + public async Task RunThirdPartyFixerWithSeverityOfWarningOrHigher(DiagnosticSeverity severity) + { + await TestThirdPartyCodeFixerApplied(_code, _fixed, severity); + } + + [Theory] + [InlineData(DiagnosticSeverity.Hidden)] + [InlineData(DiagnosticSeverity.Info)] + [Trait(Traits.Feature, Traits.Features.CodeCleanup)] + public async Task DoNotRunThirdPartyFixerWithSeverityLessThanWarning(DiagnosticSeverity severity) + { + await TestThirdPartyCodeFixerNoChanges(_code, severity); + } + + [Fact] + [Trait(Traits.Feature, Traits.Features.CodeCleanup)] + public async Task DoNotRunThirdPartyFixerIfItDoesNotSupportDocumentScope() + { + await TestThirdPartyCodeFixerNoChanges(_code); + } + + [Fact] + [Trait(Traits.Feature, Traits.Features.CodeCleanup)] + public async Task DoNotApplyFixerIfChangesAreMadeOutsideDocument() + { + await TestThirdPartyCodeFixerNoChanges(_code); + } + + private static Task TestThirdPartyCodeFixerNoChanges(string code, DiagnosticSeverity severity = DiagnosticSeverity.Warning) + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodefix : CodeFixProvider, new() + { + return TestThirdPartyCodeFixer(code, code, severity); + } + + private static Task TestThirdPartyCodeFixerApplied(string code, string expected, DiagnosticSeverity severity = DiagnosticSeverity.Warning) + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodefix : CodeFixProvider, new() + { + return TestThirdPartyCodeFixer(code, expected, severity); + } + + private static async Task TestThirdPartyCodeFixer(string code = null, string expected = null, DiagnosticSeverity severity = DiagnosticSeverity.Warning) + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodefix : CodeFixProvider, new() + { + + using var workspace = TestWorkspace.CreateCSharp(code, composition: EditorTestCompositions.EditorFeaturesWpf.AddParts(typeof(TCodefix))); + + var options = CodeActionOptions.DefaultProvider; + + var project = workspace.CurrentSolution.Projects.Single(); + var analyzer = (DiagnosticAnalyzer)new TAnalyzer(); + var diagnosticIds = analyzer.SupportedDiagnostics.SelectAsArray(d => d.Id); + + var editorconfigText = "is_global = true"; + foreach (var diagnosticId in diagnosticIds) + { + editorconfigText += $"\ndotnet_diagnostic.{diagnosticId}.severity = {severity.ToEditorConfigString()}"; + } + + var map = new Dictionary>{ + { LanguageNames.CSharp, ImmutableArray.Create(analyzer) } + }; + + project = project.AddAnalyzerReference(new TestAnalyzerReferenceByLanguage(map)); + project = project.Solution.WithProjectFilePath(project.Id, @$"z:\\{project.FilePath}").GetProject(project.Id); + project = project.AddAnalyzerConfigDocument(".editorconfig", SourceText.From(editorconfigText), filePath: @"z:\\.editorconfig").Project; + workspace.TryApplyChanges(project.Solution); + + // register this workspace to solution crawler so that analyzer service associate itself with given workspace + var incrementalAnalyzerProvider = workspace.ExportProvider.GetExportedValue() as IIncrementalAnalyzerProvider; + incrementalAnalyzerProvider.CreateIncrementalAnalyzer(workspace); + + var hostdoc = workspace.Documents.Single(); + var document = workspace.CurrentSolution.GetDocument(hostdoc.Id); + + var codeCleanupService = document.GetLanguageService(); + + var enabledDiagnostics = codeCleanupService.GetAllDiagnostics(); + + var newDoc = await codeCleanupService.CleanupAsync( + document, enabledDiagnostics, new ProgressTracker(), options, CancellationToken.None); + + var actual = await newDoc.GetTextAsync(); + Assert.Equal(expected, actual.ToString()); + } + private static string[] GetSupportedDiagnosticIdsForCodeCleanupService(string language) { using var workspace = GetTestWorkspaceForLanguage(language); diff --git a/src/EditorFeatures/Test/EditAndContinue/Helpers/MockDiagnosticAnalyzerService.cs b/src/EditorFeatures/Test/EditAndContinue/Helpers/MockDiagnosticAnalyzerService.cs index f97caae14eb28..bf4affae92cd9 100644 --- a/src/EditorFeatures/Test/EditAndContinue/Helpers/MockDiagnosticAnalyzerService.cs +++ b/src/EditorFeatures/Test/EditAndContinue/Helpers/MockDiagnosticAnalyzerService.cs @@ -47,7 +47,7 @@ public Task> GetDiagnosticsAsync(Solution solutio public Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId = null, DocumentId? documentId = null, ImmutableHashSet? diagnosticIds = null, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default) => throw new NotImplementedException(); - public Task> GetDiagnosticsForSpanAsync(Document document, TextSpan? range, Func? shouldIncludeDiagnostic, bool includeSuppressedDiagnostics = false, CodeActionRequestPriority priority = CodeActionRequestPriority.None, Func? addOperationScope = null, CancellationToken cancellationToken = default) + public Task> GetDiagnosticsForSpanAsync(Document document, TextSpan? range, Func? shouldIncludeDiagnostic, bool includeCompilerDiagnostics, bool includeSuppressedDiagnostics = true, CodeActionRequestPriority priority = CodeActionRequestPriority.None, Func? addOperationScope = null, CancellationToken cancellationToken = default) => throw new NotImplementedException(); public Task> GetProjectDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId = null, ImmutableHashSet? diagnosticIds = null, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default) diff --git a/src/EditorFeatures/VisualBasic/CodeCleanup/VisualBasicCodeCleanupService.vb b/src/EditorFeatures/VisualBasic/CodeCleanup/VisualBasicCodeCleanupService.vb index d4fe3b3949c89..739704a1b50b9 100644 --- a/src/EditorFeatures/VisualBasic/CodeCleanup/VisualBasicCodeCleanupService.vb +++ b/src/EditorFeatures/VisualBasic/CodeCleanup/VisualBasicCodeCleanupService.vb @@ -82,8 +82,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeCleanup - Public Sub New(codeFixService As ICodeFixService) - MyBase.New(codeFixService) + Public Sub New(codeFixService As ICodeFixService, diagnosticAnalyzerService As IDiagnosticAnalyzerService) + MyBase.New(codeFixService, diagnosticAnalyzerService) End Sub Protected Overrides ReadOnly Property OrganizeImportsDescription As String = VBFeaturesResources.Organize_Imports diff --git a/src/EditorFeatures/VisualBasicTest/Formatting/CodeCleanUpTests.vb b/src/EditorFeatures/VisualBasicTest/Formatting/CodeCleanUpTests.vb index 1e57fc9d05efa..8334dd03b1f70 100644 --- a/src/EditorFeatures/VisualBasicTest/Formatting/CodeCleanUpTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Formatting/CodeCleanUpTests.vb @@ -2,29 +2,35 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. +Imports System.Collections.Immutable +Imports System.Composition Imports System.Threading Imports Microsoft.CodeAnalysis.AddImport Imports Microsoft.CodeAnalysis.CodeActions Imports Microsoft.CodeAnalysis.CodeCleanup Imports Microsoft.CodeAnalysis.CodeGeneration +Imports Microsoft.CodeAnalysis.CodeFixes Imports Microsoft.CodeAnalysis.Diagnostics Imports Microsoft.CodeAnalysis.Diagnostics.VisualBasic Imports Microsoft.CodeAnalysis.Editing Imports Microsoft.CodeAnalysis.Editor.UnitTests Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Formatting +Imports Microsoft.CodeAnalysis.Host.Mef Imports Microsoft.CodeAnalysis.MakeFieldReadonly Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.Shared.Utilities Imports Microsoft.CodeAnalysis.SolutionCrawler Imports Microsoft.CodeAnalysis.VisualBasic.CodeGeneration +Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.UnitTests.Diagnostics Imports Microsoft.CodeAnalysis.VisualBasic.Diagnostics.Analyzers Imports Microsoft.CodeAnalysis.VisualBasic.Formatting Imports Microsoft.CodeAnalysis.VisualBasic.Simplification Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Formatting - Public Class CodeCleanUpTests + Partial Public Class CodeCleanUpTests ' Format Document tests are handled by Format Document Test ' TESTS NEEDED but not found in C# @@ -323,6 +329,243 @@ End Class Return AssertCodeCleanupResultAsync(expected, code) End Function + Private Const _code As String = " +Class C + Public Sub M1(x As Integer) + Select Case x + Case 1, 2 + Exit Select + Case = 10 + Exit Select + Case Else + Exit Select + End Select + + Select Case x + Case 1 + Exit Select + Case = 1000 + Exit Select + Case Else + Exit Select + End Select + + Select Case x + Case 10 To 500 + Exit Select + Case = 1000 + Exit Select + Case Else + Exit Select + End Select + + Select Case x + Case 1, 980 To 985 + Exit Select + Case Else + Exit Select + End Select + + Select Case x + Case 1 to 3, 980 To 985 + Exit Select + End Select + + Select Case x + Case 1 + Exit Select + Case > 100000 + Exit Select + End Select + + Select Case x + Case Else + Exit Select + End Select + + Select Case x + End Select + + Select Case x + Case 1 + Exit Select + Case + Exit Select + End Select + + Select Case x + Case 1 + Exit Select + Case = + Exit Select + End Select + + Select Case x + Case 1 + Exit Select + Case 2 to + Exit Select + End Select + End Sub +End Class +" + + Private Const _expected As String = " +Class C + Public Sub M1(x As Integer) + Select Case x + Case 1, 2 + Exit Select + Case = 10 + Exit Select + End Select + + Select Case x + Case 1 + Exit Select + Case = 1000 + Exit Select + End Select + + Select Case x + Case 10 To 500 + Exit Select + Case = 1000 + Exit Select + End Select + + Select Case x + Case 1, 980 To 985 + Exit Select + End Select + + Select Case x + Case 1 to 3, 980 To 985 + Exit Select + End Select + + Select Case x + Case 1 + Exit Select + Case > 100000 + Exit Select + End Select + + Select Case x + End Select + + Select Case x + End Select + + Select Case x + Case 1 + Exit Select + Case + Exit Select + End Select + + Select Case x + Case 1 + Exit Select + Case = + Exit Select + End Select + + Select Case x + Case 1 + Exit Select + Case 2 to + Exit Select + End Select + End Sub +End Class +" + + + + Public Shared Async Function RunThirdPartyFixer() As Task + Await TestThirdPartyCodeFixer(Of TestThirdPartyCodeFixWithFixAll, CaseTestAnalyzer)(_expected, _code) + End Function + + + + + + Public Shared Async Function RunThirdPartyFixerWithSeverityOfWarningOrHigher(severity As DiagnosticSeverity) As Task + Await TestThirdPartyCodeFixer(Of TestThirdPartyCodeFixWithFixAll, CaseTestAnalyzer)(_expected, _code, severity) + End Function + + + + + + Public Shared Async Function DoNotRunThirdPartyFixerWithSeverityLessThanWarning(severity As DiagnosticSeverity) As Task + Await TestThirdPartyCodeFixer(Of TestThirdPartyCodeFixWithFixAll, CaseTestAnalyzer)(_code, _code, severity) + End Function + + + + Public Shared Async Function DoNotRunThirdPartyFixerIfItDoesNotSupportDocumentScope() As Task + Await TestThirdPartyCodeFixer(Of TestThirdPartyCodeFixDoesNotSupportDocumentScope, CaseTestAnalyzer)(_code, _code) + End Function + + + + Public Shared Async Function DoNotApplyFixerIfChangesAreMadeOutsideDocument() As Task + Await TestThirdPartyCodeFixer(Of TestThirdPartyCodeFixModifiesSolution, CaseTestAnalyzer)(_code, _code) + End Function + + + + Public Shared Async Function DoNotRunThirdPartyFixerWithNoFixAll() As Task + Await TestThirdPartyCodeFixer(Of TestThirdPartyCodeFixWithOutFixAll, CaseTestAnalyzer)(_code, _code) + End Function + + Private Shared Async Function TestThirdPartyCodeFixer(Of TCodefix As {CodeFixProvider, New}, TAnalyzer As {DiagnosticAnalyzer, New})(expected As String, code As String, Optional severity As DiagnosticSeverity = DiagnosticSeverity.Warning) As Task + Using workspace = TestWorkspace.CreateVisualBasic(code, composition:=EditorTestCompositions.EditorFeaturesWpf.AddParts(GetType(TCodefix))) + Dim options = CodeActionOptions.DefaultProvider + Dim project = workspace.CurrentSolution.Projects.Single() + Dim map = New Dictionary(Of String, ImmutableArray(Of DiagnosticAnalyzer)) From + { + {LanguageNames.VisualBasic, ImmutableArray.Create(Of DiagnosticAnalyzer)(New TAnalyzer())} + } + Dim analyzer As DiagnosticAnalyzer = New TAnalyzer() + Dim diagnosticIds = analyzer.SupportedDiagnostics.SelectAsArray(Function(d) d.Id) + + Dim editorconfigText = "is_global = true" + For Each diagnosticId In diagnosticIds + editorconfigText += $"{Environment.NewLine}dotnet_diagnostic.{diagnosticId}.severity = {severity.ToEditorConfigString()}" + Next + + project = project.AddAnalyzerReference(New TestAnalyzerReferenceByLanguage(map)) + project = project.Solution.WithProjectFilePath(project.Id, $"z:\\{project.FilePath}").GetProject(project.Id) + project = project.AddAnalyzerConfigDocument(".editorconfig", SourceText.From(editorconfigText), filePath:="z:\\.editorconfig").Project + workspace.TryApplyChanges(project.Solution) + + ' register this workspace to solution crawler so that analyzer service associate itself with given workspace + Dim incrementalAnalyzerProvider = TryCast(workspace.ExportProvider.GetExportedValue(Of IDiagnosticAnalyzerService)(), IIncrementalAnalyzerProvider) + incrementalAnalyzerProvider.CreateIncrementalAnalyzer(workspace) + + Dim hostdoc = workspace.Documents.[Single]() + Dim document = workspace.CurrentSolution.GetDocument(hostdoc.Id) + + Dim codeCleanupService = document.GetLanguageService(Of ICodeCleanupService)() + + Dim enabledDiagnostics = codeCleanupService.GetAllDiagnostics() + + Dim newDoc = Await codeCleanupService.CleanupAsync( + document, + enabledDiagnostics, + New ProgressTracker, + options, + CancellationToken.None) + + Dim actual = Await newDoc.GetTextAsync() + + AssertEx.EqualOrDiff(expected, actual.ToString()) + End Using + End Function + ''' ''' Assert the expected code value equals the actual processed input . ''' @@ -375,5 +618,138 @@ End Class End Using End Function + + Private Class TestThirdPartyCodeFixWithFixAll : Inherits TestThirdPartyCodeFix + + + + Public Sub New() + End Sub + + Public Overrides Function GetFixAllProvider() As FixAllProvider + Return BatchFixAllProvider.Instance + End Function + End Class + + + Private Class TestThirdPartyCodeFixWithOutFixAll : Inherits TestThirdPartyCodeFix + + + + Public Sub New() + End Sub + End Class + + + Private Class TestThirdPartyCodeFixModifiesSolution : Inherits TestThirdPartyCodeFix + + + + Public Sub New() + End Sub + + Public Overrides Function GetFixAllProvider() As FixAllProvider + Return New ModifySolutionFixAll + End Function + + Private Class ModifySolutionFixAll : Inherits FixAllProvider + + Public Overrides Function GetFixAsync(fixAllContext As FixAllContext) As Task(Of CodeAction) + Dim solution = fixAllContext.Solution + Return Task.FromResult(CodeAction.Create( + "Remove default case", + Async Function(cancellationToken) + Dim toFix = Await fixAllContext.GetDocumentDiagnosticsToFixAsync() + Dim project As Project = Nothing + For Each kvp In toFix + Dim document = kvp.Key + project = document.Project + Dim diagnostics = kvp.Value + Dim root = Await document.GetSyntaxRootAsync(cancellationToken) + For Each diagnostic In diagnostics + Dim node = (Await diagnostic.Location.SourceTree.GetRootAsync(cancellationToken)).FindNode(diagnostic.Location.SourceSpan) + document = document.WithSyntaxRoot(root.RemoveNode(node.Parent, SyntaxRemoveOptions.KeepNoTrivia)) + Next + + solution = solution.WithDocumentText(document.Id, Await document.GetTextAsync()) + Next + + Return solution.AddDocument(DocumentId.CreateNewId(project.Id), "new.vb", SourceText.From("")) + End Function, + NameOf(TestThirdPartyCodeFix))) + End Function + End Class + End Class + + + Private Class TestThirdPartyCodeFixDoesNotSupportDocumentScope : Inherits TestThirdPartyCodeFix + + + + Public Sub New() + End Sub + + Public Overrides Function GetFixAllProvider() As FixAllProvider + Return New ModifySolutionFixAll + End Function + + Private Class ModifySolutionFixAll : Inherits FixAllProvider + + Public Overrides Function GetSupportedFixAllScopes() As IEnumerable(Of FixAllScope) + Return {FixAllScope.Project, FixAllScope.Solution, FixAllScope.Custom} + End Function + + Public Overrides Function GetFixAsync(fixAllContext As FixAllContext) As Task(Of CodeAction) + Dim solution = fixAllContext.Solution + Return Task.FromResult(CodeAction.Create( + "Remove default case", + Async Function(cancellationToken) + Dim toFix = Await fixAllContext.GetDocumentDiagnosticsToFixAsync() + Dim project As Project = Nothing + For Each kvp In toFix + Dim document = kvp.Key + project = document.Project + Dim diagnostics = kvp.Value + Dim root = Await document.GetSyntaxRootAsync(cancellationToken) + For Each diagnostic In diagnostics + Dim node = (Await diagnostic.Location.SourceTree.GetRootAsync(cancellationToken)).FindNode(diagnostic.Location.SourceSpan) + document = document.WithSyntaxRoot(root.RemoveNode(node.Parent, SyntaxRemoveOptions.KeepNoTrivia)) + Next + + solution = solution.WithDocumentText(document.Id, Await document.GetTextAsync()) + Next + + Return solution.AddDocument(DocumentId.CreateNewId(project.Id), "new.vb", SourceText.From("")) + End Function, + NameOf(TestThirdPartyCodeFix))) + End Function + End Class + End Class + + Private Class TestThirdPartyCodeFix : Inherits CodeFixProvider + + Public Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) + Get + Return ImmutableArray.Create("HasDefaultCase") + End Get + End Property + + Public Overrides Function RegisterCodeFixesAsync(context As CodeFixContext) As Task + For Each diagnostic In context.Diagnostics + context.RegisterCodeFix( + CodeAction.Create( + "Remove default case", + Async Function(cancellationToken) + Dim root = Await context.Document.GetSyntaxRootAsync(cancellationToken) + Dim node = (Await diagnostic.Location.SourceTree.GetRootAsync(cancellationToken)).FindNode(diagnostic.Location.SourceSpan) + Return context.Document.WithSyntaxRoot(root.RemoveNode(node.Parent, SyntaxRemoveOptions.KeepNoTrivia)) + End Function, + NameOf(TestThirdPartyCodeFix)), + diagnostic) + Next + + Return Task.CompletedTask + End Function + End Class End Class End Namespace diff --git a/src/Features/Core/Portable/CodeFixes/DiagnosticExtensions.cs b/src/Features/Core/Portable/CodeFixes/DiagnosticExtensions.cs new file mode 100644 index 0000000000000..f01788ccef5d9 --- /dev/null +++ b/src/Features/Core/Portable/CodeFixes/DiagnosticExtensions.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.CodeFixes.Suppression; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.CodeAnalysis.CodeFixes +{ + internal static class DiagnosticExtensions + { + public static bool IsMoreSevereThanOrEqualTo(this DiagnosticSeverity left, DiagnosticSeverity right) + { + var leftInt = (int)left; + var rightInt = (int)right; + return leftInt >= rightInt; + } + } +} diff --git a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs index 61650e84b54c0..6db2371f1087a 100644 --- a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs @@ -75,7 +75,7 @@ internal interface IDiagnosticAnalyzerService /// This API will only force complete analyzers that support span based analysis, i.e. compiler analyzer and /// s that support . /// For the rest of the analyzers, it will only return diagnostics if the analyzer has already been executed. - /// Use + /// Use /// if you want to force complete all analyzers and get up-to-date diagnostics for all analyzers for the given span. /// Task<(ImmutableArray diagnostics, bool upToDate)> TryGetDiagnosticsForSpanAsync(Document document, TextSpan range, Func? shouldIncludeDiagnostic, bool includeSuppressedDiagnostics = false, CodeActionRequestPriority priority = CodeActionRequestPriority.None, CancellationToken cancellationToken = default); @@ -88,7 +88,7 @@ internal interface IDiagnosticAnalyzerService /// none of its reported diagnostics should be included in the result. /// /// - Task> GetDiagnosticsForSpanAsync(Document document, TextSpan? range, Func? shouldIncludeDiagnostic, bool includeSuppressedDiagnostics = false, CodeActionRequestPriority priority = CodeActionRequestPriority.None, Func? addOperationScope = null, CancellationToken cancellationToken = default); + Task> GetDiagnosticsForSpanAsync(Document document, TextSpan? range, Func? shouldIncludeDiagnostic, bool includeCompilerDiagnostics, bool includeSuppressedDiagnostics = false, CodeActionRequestPriority priority = CodeActionRequestPriority.None, Func? addOperationScope = null, CancellationToken cancellationToken = default); } internal static class IDiagnosticAnalyzerServiceExtensions @@ -108,7 +108,7 @@ public static Task> GetDiagnosticsForSpanAsync(th { Func? shouldIncludeDiagnostic = diagnosticId != null ? id => id == diagnosticId : null; return service.GetDiagnosticsForSpanAsync(document, range, shouldIncludeDiagnostic, - includeSuppressedDiagnostics, priority, addOperationScope, cancellationToken); + includeCompilerDiagnostics: true, includeSuppressedDiagnostics, priority: priority, addOperationScope: addOperationScope, cancellationToken: cancellationToken); } } } diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx index b2298e49df4d0..5589332b3fda7 100644 --- a/src/Features/Core/Portable/FeaturesResources.resx +++ b/src/Features/Core/Portable/FeaturesResources.resx @@ -3144,4 +3144,7 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Replace conditional expression with statements + + Fixing '{0}' + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf index 24803c52757d1..787b263612e26 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf @@ -735,6 +735,11 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Opravit překlep {0} + + Fixing '{0}' + Fixing '{0}' + + Formatting document Formátuje se dokument. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf index e3f423fbca6ad..f32a02014c958 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf @@ -735,6 +735,11 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Tippfehler "{0}" korrigieren + + Fixing '{0}' + Fixing '{0}' + + Formatting document Dokument wird formatiert diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf index 2a15405494064..763d91b9ac534 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf @@ -735,6 +735,11 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Corregir error de escritura "{0}" + + Fixing '{0}' + Fixing '{0}' + + Formatting document Aplicando formato al documento diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf index 5b4c9ef59b468..6e8add98f43a1 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf @@ -735,6 +735,11 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Corriger la faute de frappe '{0}' + + Fixing '{0}' + Fixing '{0}' + + Formatting document Mise en forme du document diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf index 99e38ec6b6ff4..9170e8b5b15ce 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf @@ -735,6 +735,11 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Correggi l'errore di ortografia '{0}' + + Fixing '{0}' + Fixing '{0}' + + Formatting document Formattazione del documento diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf index b4c9b1511445d..a24d4ebc4357c 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf @@ -735,6 +735,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma '{0}' の入力ミスを修正します + + Fixing '{0}' + Fixing '{0}' + + Formatting document ドキュメントの書式設定 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf index 8df70c372262d..475e015362d82 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf @@ -735,6 +735,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 오타 '{0}' 수정 + + Fixing '{0}' + Fixing '{0}' + + Formatting document 문서 서식을 지정하는 중 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf index 1a5b22fafbbd1..e960ab4b4c12a 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf @@ -735,6 +735,11 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Popraw błąd pisowni „{0}” + + Fixing '{0}' + Fixing '{0}' + + Formatting document Trwa formatowanie dokumentu... diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf index 44164e7829180..fd661d0d8e806 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf @@ -735,6 +735,11 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Corrigir erro de digitação '{0}' + + Fixing '{0}' + Fixing '{0}' + + Formatting document Formatando documento diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf index c07793baced8e..5b6acf3ea4bde 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf @@ -735,6 +735,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Исправьте опечатку "{0}" + + Fixing '{0}' + Fixing '{0}' + + Formatting document Форматирование документа diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf index 47ce6c296c3cc..8118e6552fa6d 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf @@ -735,6 +735,11 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be '{0}' yazım hatasını düzeltin + + Fixing '{0}' + Fixing '{0}' + + Formatting document Belge biçimlendiriliyor diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf index 16556a079ddfd..e0adb869cdcf9 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf @@ -735,6 +735,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 修正笔误“{0}” + + Fixing '{0}' + Fixing '{0}' + + Formatting document 设置文档格式 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf index c50f0645fa18f..7d169048a1669 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf @@ -735,6 +735,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 修正錯字 '{0}' + + Fixing '{0}' + Fixing '{0}' + + Formatting document 正在將文件格式化 diff --git a/src/Features/LanguageServer/Protocol/Features/CodeCleanup/AbstractCodeCleanupService.cs b/src/Features/LanguageServer/Protocol/Features/CodeCleanup/AbstractCodeCleanupService.cs index 910c5c28e0331..77d84a1a2d9a2 100644 --- a/src/Features/LanguageServer/Protocol/Features/CodeCleanup/AbstractCodeCleanupService.cs +++ b/src/Features/LanguageServer/Protocol/Features/CodeCleanup/AbstractCodeCleanupService.cs @@ -4,11 +4,13 @@ using System; using System.Collections.Immutable; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CodeFixesAndRefactorings; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.OrganizeImports; @@ -22,10 +24,12 @@ namespace Microsoft.CodeAnalysis.CodeCleanup internal abstract class AbstractCodeCleanupService : ICodeCleanupService { private readonly ICodeFixService _codeFixService; + private readonly IDiagnosticAnalyzerService _diagnosticService; - protected AbstractCodeCleanupService(ICodeFixService codeFixService) + protected AbstractCodeCleanupService(ICodeFixService codeFixService, IDiagnosticAnalyzerService diagnosticAnalyzerService) { _codeFixService = codeFixService; + _diagnosticService = diagnosticAnalyzerService; } protected abstract string OrganizeImportsDescription { get; } @@ -38,7 +42,15 @@ public async Task CleanupAsync( CodeActionOptionsProvider fallbackOptions, CancellationToken cancellationToken) { - // add one item for the 'format' action we'll do last + // add one item for the code fixers we get from nuget, we'll do last + var thirdPartyDiagnosticIdsAndTitles = ImmutableArray<(string diagnosticId, string? title)>.Empty; + if (enabledDiagnostics.RunThirdPartyFixers) + { + thirdPartyDiagnosticIdsAndTitles = await GetThirdPartyDiagnosticIdsAndTitlesAsync(document, cancellationToken).ConfigureAwait(false); + progressTracker.AddItems(thirdPartyDiagnosticIdsAndTitles.Length); + } + + // add one item for the 'format' action if (enabledDiagnostics.FormatDocument) { progressTracker.AddItems(1); @@ -52,9 +64,20 @@ public async Task CleanupAsync( progressTracker.AddItems(1); } + if (enabledDiagnostics.Diagnostics.Any()) + { + progressTracker.AddItems(enabledDiagnostics.Diagnostics.Length); + } + document = await ApplyCodeFixesAsync( document, enabledDiagnostics.Diagnostics, progressTracker, fallbackOptions, cancellationToken).ConfigureAwait(false); + if (enabledDiagnostics.RunThirdPartyFixers) + { + document = await ApplyThirdPartyCodeFixesAsync( + document, thirdPartyDiagnosticIdsAndTitles, progressTracker, fallbackOptions, cancellationToken).ConfigureAwait(false); + } + // do the remove usings after code fix, as code fix might remove some code which can results in unused usings. if (organizeUsings) { @@ -76,6 +99,12 @@ public async Task CleanupAsync( } } + if (enabledDiagnostics.RunThirdPartyFixers) + { + document = await ApplyThirdPartyCodeFixesAsync( + document, thirdPartyDiagnosticIdsAndTitles, progressTracker, fallbackOptions, cancellationToken).ConfigureAwait(false); + } + return document; } @@ -110,8 +139,6 @@ private async Task ApplyCodeFixesAsync( IProgressTracker progressTracker, CodeActionOptionsProvider fallbackOptions, CancellationToken cancellationToken) { // Add a progress item for each enabled option we're going to fixup. - progressTracker.AddItems(enabledDiagnosticSets.Length); - foreach (var diagnosticSet in enabledDiagnosticSets) { cancellationToken.ThrowIfCancellationRequested(); @@ -162,7 +189,80 @@ private async Task ApplyCodeFixesForSpecificDiagnosticIdAsync(Document return solution.GetDocument(document.Id) ?? throw new NotSupportedException(FeaturesResources.Removal_of_document_not_supported); } + private async Task> GetThirdPartyDiagnosticIdsAndTitlesAsync(Document document, CancellationToken cancellationToken) + { + var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var range = new TextSpan(0, tree.Length); + + // Compute diagnostics for everything that is not an IDE analyzer + var diagnostics = (await _diagnosticService.GetDiagnosticsForSpanAsync(document, range, + shouldIncludeDiagnostic: static diagnosticId => !(IDEDiagnosticIdToOptionMappingHelper.IsKnownIDEDiagnosticId(diagnosticId)), + includeCompilerDiagnostics: true, includeSuppressedDiagnostics: false, + cancellationToken: cancellationToken).ConfigureAwait(false)); + + // ensure more than just known diagnostics were returned + if (!diagnostics.Any()) + { + return ImmutableArray<(string diagnosticId, string? title)>.Empty; + } + + return diagnostics.SelectAsArray(static d => (d.Id, d.Title)).Distinct(); + } + + private async Task ApplyThirdPartyCodeFixesAsync( + Document document, + ImmutableArray<(string diagnosticId, string? title)> diagnosticIds, + IProgressTracker progressTracker, + CodeActionOptionsProvider fallbackOptions, + CancellationToken cancellationToken) + { + foreach (var (diagnosticId, title) in diagnosticIds) + { + cancellationToken.ThrowIfCancellationRequested(); + + progressTracker.Description = string.Format(FeaturesResources.Fixing_0, title ?? diagnosticId); + // Apply codefixes for diagnostics with a severity of warning or higher + var updatedDocument = await _codeFixService.ApplyCodeFixesForSpecificDiagnosticIdAsync( + document, diagnosticId, DiagnosticSeverity.Warning, progressTracker, fallbackOptions, cancellationToken).ConfigureAwait(false); + + // If changes were made to the solution snap shot outside the current document discard the changes. + // The assumption here is that if we are applying a third party code fix to a document it only affects the document. + // Symbol renames and other complex refactorings we do not want to include in code cleanup. + // We can revisit this if we get feedback to the contrary + if (!ChangesMadeOutsideDocument(document, updatedDocument)) + { + document = updatedDocument; + } + + progressTracker.ItemCompleted(); + } + + return document; + + static bool ChangesMadeOutsideDocument(Document currentDocument, Document updatedDocument) + { + var solutionChanges = updatedDocument.Project.Solution.GetChanges(currentDocument.Project.Solution); + return + solutionChanges.GetAddedProjects().Any() || + solutionChanges.GetRemovedProjects().Any() || + solutionChanges.GetAddedAnalyzerReferences().Any() || + solutionChanges.GetRemovedAnalyzerReferences().Any() || + solutionChanges.GetProjectChanges().Any( + projectChanges => projectChanges.GetAddedProjectReferences().Any() || + projectChanges.GetRemovedProjectReferences().Any() || + projectChanges.GetAddedMetadataReferences().Any() || + projectChanges.GetRemovedMetadataReferences().Any() || + projectChanges.GetAddedAnalyzerReferences().Any() || + projectChanges.GetRemovedAnalyzerReferences().Any() || + projectChanges.GetAddedDocuments().Any() || + projectChanges.GetAddedAdditionalDocuments().Any() || + projectChanges.GetAddedAnalyzerConfigDocuments().Any() || + projectChanges.GetChangedDocuments().Any(documentId => documentId != updatedDocument.Id) || + projectChanges.GetChangedAdditionalDocuments().Any(documentId => documentId != updatedDocument.Id) || + projectChanges.GetChangedAnalyzerConfigDocuments().Any(documentId => documentId != updatedDocument.Id)); + } + } public EnabledDiagnosticOptions GetAllDiagnostics() - => new EnabledDiagnosticOptions(formatDocument: true, GetDiagnosticSets(), new OrganizeUsingsSet(isRemoveUnusedImportEnabled: true, isSortImportsEnabled: true)); + => new(FormatDocument: true, RunThirdPartyFixers: true, Diagnostics: GetDiagnosticSets(), OrganizeUsings: new OrganizeUsingsSet(isRemoveUnusedImportEnabled: true, isSortImportsEnabled: true)); } } diff --git a/src/Features/LanguageServer/Protocol/Features/CodeCleanup/EnabledDiagnosticOptions.cs b/src/Features/LanguageServer/Protocol/Features/CodeCleanup/EnabledDiagnosticOptions.cs index 7a26f263207d3..5095d988cace9 100644 --- a/src/Features/LanguageServer/Protocol/Features/CodeCleanup/EnabledDiagnosticOptions.cs +++ b/src/Features/LanguageServer/Protocol/Features/CodeCleanup/EnabledDiagnosticOptions.cs @@ -11,19 +11,5 @@ namespace Microsoft.CodeAnalysis.CodeCleanup /// /// Indicates which features are enabled for a code cleanup operation. /// - internal sealed class EnabledDiagnosticOptions - { - public bool FormatDocument { get; } - - public ImmutableArray Diagnostics { get; } - - public OrganizeUsingsSet OrganizeUsings { get; } - - public EnabledDiagnosticOptions(bool formatDocument, ImmutableArray diagnostics, OrganizeUsingsSet organizeUsings) - { - FormatDocument = formatDocument; - Diagnostics = diagnostics; - OrganizeUsings = organizeUsings; - } - } + internal sealed record EnabledDiagnosticOptions(bool FormatDocument, bool RunThirdPartyFixers, ImmutableArray Diagnostics, OrganizeUsingsSet OrganizeUsings); } diff --git a/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs b/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs index 098f4a9f2f30d..8506b92f9c976 100644 --- a/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs +++ b/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs @@ -173,7 +173,8 @@ public async IAsyncEnumerable StreamFixesAsync( var diagnostics = await _diagnosticService.GetDiagnosticsForSpanAsync( document, range, GetShouldIncludeDiagnosticPredicate(document, priority), - includeSuppressionFixes, priority, addOperationScope, cancellationToken).ConfigureAwait(false); + includeCompilerDiagnostics: true, includeSuppressedDiagnostics: includeSuppressionFixes, priority: priority, + addOperationScope: addOperationScope, cancellationToken: cancellationToken).ConfigureAwait(false); if (diagnostics.IsEmpty) yield break; @@ -230,33 +231,73 @@ private static SortedDictionary> ConvertToMap( return spanToDiagnostics; } - public async Task GetDocumentFixAllForIdInSpanAsync( + public Task GetDocumentFixAllForIdInSpanAsync( Document document, TextSpan range, string diagnosticId, CodeActionOptionsProvider fallbackOptions, CancellationToken cancellationToken) + => GetDocumentFixAllForIdInSpanAsync(document, range, diagnosticId, DiagnosticSeverity.Hidden, fallbackOptions, cancellationToken); + + public async Task GetDocumentFixAllForIdInSpanAsync( + Document document, TextSpan range, string diagnosticId, DiagnosticSeverity minimumSeverity, CodeActionOptionsProvider fallbackOptions, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - var diagnostics = (await _diagnosticService.GetDiagnosticsForSpanAsync(document, range, diagnosticId, includeSuppressedDiagnostics: false, cancellationToken: cancellationToken).ConfigureAwait(false)).ToList(); - if (diagnostics.Count == 0) + var diagnostics = await _diagnosticService.GetDiagnosticsForSpanAsync( + document, range, diagnosticId, includeSuppressedDiagnostics: false, cancellationToken: cancellationToken).ConfigureAwait(false); + diagnostics = diagnostics.WhereAsArray(d => d.Severity.IsMoreSevereThanOrEqualTo(minimumSeverity)); + if (!diagnostics.Any()) return null; using var resultDisposer = ArrayBuilder.GetInstance(out var result); var spanToDiagnostics = new SortedDictionary> { - { range, diagnostics }, + { range, diagnostics.ToList() }, }; await foreach (var collection in StreamFixesAsync( document, spanToDiagnostics, fixAllForInSpan: true, CodeActionRequestPriority.None, fallbackOptions, isBlocking: false, addOperationScope: static _ => null, cancellationToken).ConfigureAwait(false)) { - // TODO: Just get the first fix for now until we have a way to config user's preferred fix - // https://github.com/dotnet/roslyn/issues/27066 - return collection; + if (collection.FixAllState is not null && collection.SupportedScopes.Contains(FixAllScope.Document)) + { + // TODO: Just get the first fix for now until we have a way to config user's preferred fix + // https://github.com/dotnet/roslyn/issues/27066 + return collection; + } } return null; } + public Task ApplyCodeFixesForSpecificDiagnosticIdAsync(Document document, string diagnosticId, IProgressTracker progressTracker, CodeActionOptionsProvider fallbackOptions, CancellationToken cancellationToken) + => ApplyCodeFixesForSpecificDiagnosticIdAsync(document, diagnosticId, DiagnosticSeverity.Hidden, progressTracker, fallbackOptions, cancellationToken); + + public async Task ApplyCodeFixesForSpecificDiagnosticIdAsync( + Document document, + string diagnosticId, + DiagnosticSeverity severity, + IProgressTracker progressTracker, + CodeActionOptionsProvider fallbackOptions, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var textSpan = new TextSpan(0, tree.Length); + + var fixCollection = await GetDocumentFixAllForIdInSpanAsync( + document, textSpan, diagnosticId, severity, fallbackOptions, cancellationToken).ConfigureAwait(false); + if (fixCollection == null) + { + return document; + } + + var fixAllService = document.Project.Solution.Workspace.Services.GetRequiredService(); + + var solution = await fixAllService.GetFixAllChangedSolutionAsync( + new FixAllContext(fixCollection.FixAllState, progressTracker, cancellationToken)).ConfigureAwait(false); + + return solution.GetDocument(document.Id) ?? throw new NotSupportedException(FeaturesResources.Removal_of_document_not_supported); + } + private bool TryGetWorkspaceFixersMap(Document document, [NotNullWhen(true)] out Lazy>>? fixerMap) { if (_lazyWorkspaceFixersMap == null) diff --git a/src/Features/LanguageServer/Protocol/Features/CodeFixes/ICodeFixService.cs b/src/Features/LanguageServer/Protocol/Features/CodeFixes/ICodeFixService.cs index 773cf0a989ddb..88a4cb254fc08 100644 --- a/src/Features/LanguageServer/Protocol/Features/CodeFixes/ICodeFixService.cs +++ b/src/Features/LanguageServer/Protocol/Features/CodeFixes/ICodeFixService.cs @@ -25,7 +25,8 @@ internal interface ICodeFixService /// Task GetMostSevereFixAsync(Document document, TextSpan range, CodeActionRequestPriority priority, CodeActionOptionsProvider fallbackOptions, bool isBlocking, CancellationToken cancellationToken); - Task GetDocumentFixAllForIdInSpanAsync(Document document, TextSpan textSpan, string diagnosticId, CodeActionOptionsProvider fallbackOptions, CancellationToken cancellationToken); + Task GetDocumentFixAllForIdInSpanAsync(Document document, TextSpan textSpan, string diagnosticId, DiagnosticSeverity severity, CodeActionOptionsProvider fallbackOptions, CancellationToken cancellationToken); + Task ApplyCodeFixesForSpecificDiagnosticIdAsync(Document document, string diagnosticId, DiagnosticSeverity severity, IProgressTracker progressTracker, CodeActionOptionsProvider fallbackOptions, CancellationToken cancellationToken); CodeFixProvider? GetSuppressionFixer(string language, IEnumerable diagnosticIds); } @@ -39,5 +40,11 @@ public static Task> GetFixesAsync(this ICodeFi public static Task> GetFixesAsync(this ICodeFixService service, Document document, TextSpan textSpan, CodeActionRequestPriority priority, CodeActionOptionsProvider fallbackOptions, bool isBlocking, Func addOperationScope, CancellationToken cancellationToken) => service.StreamFixesAsync(document, textSpan, priority, fallbackOptions, isBlocking, addOperationScope, cancellationToken).ToImmutableArrayAsync(cancellationToken); + + public static Task GetDocumentFixAllForIdInSpanAsync(this ICodeFixService service, Document document, TextSpan range, string diagnosticId, CodeActionOptionsProvider fallbackOptions, CancellationToken cancellationToken) + => service.GetDocumentFixAllForIdInSpanAsync(document, range, diagnosticId, DiagnosticSeverity.Hidden, fallbackOptions, cancellationToken); + + public static Task ApplyCodeFixesForSpecificDiagnosticIdAsync(this ICodeFixService service, Document document, string diagnosticId, IProgressTracker progressTracker, CodeActionOptionsProvider fallbackOptions, CancellationToken cancellationToken) + => service.ApplyCodeFixesForSpecificDiagnosticIdAsync(document, diagnosticId, DiagnosticSeverity.Hidden, progressTracker, fallbackOptions, cancellationToken); } } diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs index d4e9a1b32e100..b447b8757f2e0 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs @@ -83,7 +83,7 @@ public void Reanalyze(Workspace workspace, IEnumerable? projectIds = using var _ = ArrayBuilder.GetInstance(out var diagnostics); var upToDate = await analyzer.TryAppendDiagnosticsForSpanAsync( document, range, diagnostics, shouldIncludeDiagnostic, - includeSuppressedDiagnostics, priority, blockForData: false, + includeSuppressedDiagnostics, true, priority, blockForData: false, addOperationScope: null, cancellationToken).ConfigureAwait(false); return (diagnostics.ToImmutable(), upToDate); }, cancellationToken); @@ -96,6 +96,7 @@ public Task> GetDiagnosticsForSpanAsync( Document document, TextSpan? range, Func? shouldIncludeDiagnostic, + bool includeCompilerDiagnostics, bool includeSuppressedDiagnostics, CodeActionRequestPriority priority, Func? addOperationScope, @@ -105,8 +106,8 @@ public Task> GetDiagnosticsForSpanAsync( { // always make sure that analyzer is called on background thread. return Task.Run(() => analyzer.GetDiagnosticsForSpanAsync( - document, range, shouldIncludeDiagnostic, includeSuppressedDiagnostics, priority, - blockForData: true, addOperationScope, cancellationToken), cancellationToken); + document, range, shouldIncludeDiagnostic, includeSuppressedDiagnostics, includeCompilerDiagnostics, + priority, blockForData: true, addOperationScope, cancellationToken), cancellationToken); } return SpecializedTasks.EmptyImmutableArray(); diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs index 438176ac7e334..0c2bb8bfc7ad7 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs @@ -22,11 +22,11 @@ internal partial class DiagnosticIncrementalAnalyzer { public async Task TryAppendDiagnosticsForSpanAsync( Document document, TextSpan? range, ArrayBuilder result, Func? shouldIncludeDiagnostic, - bool includeSuppressedDiagnostics, CodeActionRequestPriority priority, bool blockForData, + bool includeSuppressedDiagnostics, bool includeCompilerDiagnostics, CodeActionRequestPriority priority, bool blockForData, Func? addOperationScope, CancellationToken cancellationToken) { var getter = await LatestDiagnosticsForSpanGetter.CreateAsync( - this, document, range, blockForData, addOperationScope, includeSuppressedDiagnostics, + this, document, range, blockForData, addOperationScope, includeSuppressedDiagnostics, includeCompilerDiagnostics, priority, shouldIncludeDiagnostic, cancellationToken).ConfigureAwait(false); return await getter.TryGetAsync(result, cancellationToken).ConfigureAwait(false); } @@ -36,6 +36,7 @@ public async Task> GetDiagnosticsForSpanAsync( TextSpan? range, Func? shouldIncludeDiagnostic, bool includeSuppressedDiagnostics, + bool includeCompilerDiagnostics, CodeActionRequestPriority priority, bool blockForData, Func? addOperationScope, @@ -43,7 +44,7 @@ public async Task> GetDiagnosticsForSpanAsync( { using var _ = ArrayBuilder.GetInstance(out var list); var result = await TryAppendDiagnosticsForSpanAsync( - document, range, list, shouldIncludeDiagnostic, includeSuppressedDiagnostics, + document, range, list, shouldIncludeDiagnostic, includeSuppressedDiagnostics, includeCompilerDiagnostics, priority, blockForData, addOperationScope, cancellationToken).ConfigureAwait(false); Debug.Assert(result); return list.ToImmutable(); @@ -70,6 +71,7 @@ private sealed class LatestDiagnosticsForSpanGetter private readonly bool _includeSuppressedDiagnostics; private readonly CodeActionRequestPriority _priority; private readonly Func? _shouldIncludeDiagnostic; + private readonly bool _includeCompilerDiagnostics; private readonly Func? _addOperationScope; private delegate Task> DiagnosticsGetterAsync(DiagnosticAnalyzer analyzer, DocumentAnalysisExecutor executor, CancellationToken cancellationToken); @@ -81,6 +83,7 @@ public static async Task CreateAsync( bool blockForData, Func? addOperationScope, bool includeSuppressedDiagnostics, + bool includeCompilerDiagnostics, CodeActionRequestPriority priority, Func? shouldIncludeDiagnostic, CancellationToken cancellationToken) @@ -93,8 +96,8 @@ public static async Task CreateAsync( var compilationWithAnalyzers = await GetOrCreateCompilationWithAnalyzersAsync(document.Project, ideOptions, stateSets, includeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); return new LatestDiagnosticsForSpanGetter( - owner, compilationWithAnalyzers, document, stateSets, shouldIncludeDiagnostic, range, - blockForData, addOperationScope, includeSuppressedDiagnostics, priority); + owner, compilationWithAnalyzers, document, stateSets, shouldIncludeDiagnostic, includeCompilerDiagnostics, + range, blockForData, addOperationScope, includeSuppressedDiagnostics, priority); } private static async Task GetOrCreateCompilationWithAnalyzersAsync( @@ -129,6 +132,7 @@ private LatestDiagnosticsForSpanGetter( Document document, IEnumerable stateSets, Func? shouldIncludeDiagnostic, + bool includeCompilerDiagnostics, TextSpan? range, bool blockForData, Func? addOperationScope, @@ -140,6 +144,7 @@ private LatestDiagnosticsForSpanGetter( _document = document; _stateSets = stateSets; _shouldIncludeDiagnostic = shouldIncludeDiagnostic; + _includeCompilerDiagnostics = includeCompilerDiagnostics; _range = range; _blockForData = blockForData; _addOperationScope = addOperationScope; @@ -318,6 +323,7 @@ private bool ShouldInclude(DiagnosticData diagnostic) return diagnostic.DocumentId == _document.Id && (_range == null || _range.Value.IntersectsWith(diagnostic.GetTextSpan())) && (_includeSuppressedDiagnostics || !diagnostic.IsSuppressed) + && (_includeCompilerDiagnostics || !diagnostic.CustomTags.Any(static t => t is WellKnownDiagnosticTags.Compiler)) && (_shouldIncludeDiagnostic == null || _shouldIncludeDiagnostic(diagnostic.Id)); } } diff --git a/src/VisualStudio/Core/Def/CodeCleanup/AbstractCodeCleanUpFixer.cs b/src/VisualStudio/Core/Def/CodeCleanup/AbstractCodeCleanUpFixer.cs index 30ff5045dd35f..dbd9eacf9bc56 100644 --- a/src/VisualStudio/Core/Def/CodeCleanup/AbstractCodeCleanUpFixer.cs +++ b/src/VisualStudio/Core/Def/CodeCleanup/AbstractCodeCleanUpFixer.cs @@ -41,6 +41,8 @@ internal abstract class AbstractCodeCleanUpFixer : ICodeCleanUpFixer protected internal const string FormatDocumentFixId = nameof(FormatDocumentFixId); protected internal const string RemoveUnusedImportsFixId = nameof(RemoveUnusedImportsFixId); protected internal const string SortImportsFixId = nameof(SortImportsFixId); + protected internal const string ApplyThirdPartyFixersId = nameof(ApplyThirdPartyFixersId); + protected internal const string ApplyAllAnalyzerFixersId = nameof(ApplyAllAnalyzerFixersId); private readonly IThreadingContext _threadingContext; private readonly VisualStudioWorkspaceImpl _workspace; @@ -328,29 +330,33 @@ private static async Task FixDocumentAsync( var codeCleanupService = document.GetRequiredLanguageService(); - var allDiagnostics = codeCleanupService.GetAllDiagnostics(); - - var enabedDiagnosticSets = ArrayBuilder.GetInstance(); - - foreach (var diagnostic in allDiagnostics.Diagnostics) + var enabledDiagnostics = codeCleanupService.GetAllDiagnostics(); + if (!enabledFixIds.IsFixIdEnabled(ApplyAllAnalyzerFixersId)) { - foreach (var diagnosticId in diagnostic.DiagnosticIds) + var enabledDiagnosticSets = ArrayBuilder.GetInstance(); + + foreach (var diagnostic in enabledDiagnostics.Diagnostics) { - if (enabledFixIds.IsFixIdEnabled(diagnosticId)) + foreach (var diagnosticId in diagnostic.DiagnosticIds) { - enabedDiagnosticSets.Add(diagnostic); - break; + if (enabledFixIds.IsFixIdEnabled(diagnosticId)) + { + enabledDiagnosticSets.Add(diagnostic); + break; + } } } - } - var isFormatDocumentEnabled = enabledFixIds.IsFixIdEnabled(FormatDocumentFixId); - var isRemoveUnusedUsingsEnabled = enabledFixIds.IsFixIdEnabled(RemoveUnusedImportsFixId); - var isSortUsingsEnabled = enabledFixIds.IsFixIdEnabled(SortImportsFixId); - var enabledDiagnostics = new EnabledDiagnosticOptions( - isFormatDocumentEnabled, - enabedDiagnosticSets.ToImmutableArray(), - new OrganizeUsingsSet(isRemoveUnusedUsingsEnabled, isSortUsingsEnabled)); + var isFormatDocumentEnabled = enabledFixIds.IsFixIdEnabled(FormatDocumentFixId); + var isRemoveUnusedUsingsEnabled = enabledFixIds.IsFixIdEnabled(RemoveUnusedImportsFixId); + var isSortUsingsEnabled = enabledFixIds.IsFixIdEnabled(SortImportsFixId); + var isApplyThirdPartyFixersEnabled = enabledFixIds.IsFixIdEnabled(ApplyThirdPartyFixersId); + enabledDiagnostics = new EnabledDiagnosticOptions( + isFormatDocumentEnabled, + isApplyThirdPartyFixersEnabled, + enabledDiagnosticSets.ToImmutableArray(), + new OrganizeUsingsSet(isRemoveUnusedUsingsEnabled, isSortUsingsEnabled)); + } return await codeCleanupService.CleanupAsync( document, enabledDiagnostics, progressTracker, ideOptions.CreateProvider(), cancellationToken).ConfigureAwait(false); diff --git a/src/VisualStudio/Core/Def/CodeCleanup/CommonCodeCleanUpFixerDiagnosticIds.cs b/src/VisualStudio/Core/Def/CodeCleanup/CommonCodeCleanUpFixerDiagnosticIds.cs index 4e5fdf2c41488..b5f9c55fbb18f 100644 --- a/src/VisualStudio/Core/Def/CodeCleanup/CommonCodeCleanUpFixerDiagnosticIds.cs +++ b/src/VisualStudio/Core/Def/CodeCleanup/CommonCodeCleanUpFixerDiagnosticIds.cs @@ -329,5 +329,23 @@ internal static class CommonCodeCleanUpFixerDiagnosticIds [HelpLink($"https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/{IDEDiagnosticIds.ValueAssignedIsUnusedDiagnosticId}")] [LocalizedName(typeof(FeaturesResources), nameof(FeaturesResources.Apply_unused_value_preferences))] public static readonly FixIdDefinition? ValueAssignedIsUnusedDiagnosticId; + + [Export] + [FixId(AbstractCodeCleanUpFixer.ApplyThirdPartyFixersId)] + [Name(AbstractCodeCleanUpFixer.ApplyThirdPartyFixersId)] + [Order(After = IDEDiagnosticIds.RemoveUnnecessaryCastDiagnosticId)] + [ConfigurationKey("unused")] + [HelpLink($"https://microsoft.com/")] + [LocalizedName(typeof(ServicesVSResources), nameof(ServicesVSResources.Fix_analyzer_warnings_and_errors_set_in_EditorConfig))] + public static readonly FixIdDefinition? ThirdPartyAnalyzers; + + [Export] + [FixId(AbstractCodeCleanUpFixer.ApplyAllAnalyzerFixersId)] + [Name(AbstractCodeCleanUpFixer.ApplyAllAnalyzerFixersId)] + [Order(After = IDEDiagnosticIds.RemoveUnnecessaryCastDiagnosticId)] + [ConfigurationKey("unused")] + [HelpLink($"https://microsoft.com/")] + [LocalizedName(typeof(ServicesVSResources), nameof(ServicesVSResources.Fix_all_warnings_and_errors_set_in_EditorConfig))] + public static readonly FixIdDefinition? AllAnalyzers; } } diff --git a/src/VisualStudio/Core/Def/ServicesVSResources.resx b/src/VisualStudio/Core/Def/ServicesVSResources.resx index 512658dda8c32..676a0e893ee18 100644 --- a/src/VisualStudio/Core/Def/ServicesVSResources.resx +++ b/src/VisualStudio/Core/Def/ServicesVSResources.resx @@ -1971,4 +1971,13 @@ Additional information: {1} Rename asynchronously experimental + + Fix analyzer warnings and errors + + + Fix analyzer warnings and errors set in EditorConfig + + + Fix all warnings and errors set in EditorConfig + \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf index 49882ad7eafba..410fc4135d3d9 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf @@ -467,6 +467,21 @@ Fix text pasted into string literals (experimental) + + Fix analyzer warnings and errors + Fix analyzer warnings and errors + + + + Fix all warnings and errors set in EditorConfig + Fix all warnings and errors set in EditorConfig + + + + Fix analyzer warnings and errors set in EditorConfig + Fix analyzer warnings and errors set in EditorConfig + + For non interface members Pro členy mimo rozhraní diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf index f5ae0c56f6017..2a98139ada8c0 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf @@ -467,6 +467,21 @@ Fix text pasted into string literals (experimental) + + Fix analyzer warnings and errors + Fix analyzer warnings and errors + + + + Fix all warnings and errors set in EditorConfig + Fix all warnings and errors set in EditorConfig + + + + Fix analyzer warnings and errors set in EditorConfig + Fix analyzer warnings and errors set in EditorConfig + + For non interface members Für Nichtschnittstellenmember diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf index 2f19705cab247..965071375c21e 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf @@ -467,6 +467,21 @@ Fix text pasted into string literals (experimental) + + Fix analyzer warnings and errors + Fix analyzer warnings and errors + + + + Fix all warnings and errors set in EditorConfig + Fix all warnings and errors set in EditorConfig + + + + Fix analyzer warnings and errors set in EditorConfig + Fix analyzer warnings and errors set in EditorConfig + + For non interface members Para miembros que no son de interfaz diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf index 1f88b67805949..783310dcf401d 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf @@ -467,6 +467,21 @@ Fix text pasted into string literals (experimental) + + Fix analyzer warnings and errors + Fix analyzer warnings and errors + + + + Fix all warnings and errors set in EditorConfig + Fix all warnings and errors set in EditorConfig + + + + Fix analyzer warnings and errors set in EditorConfig + Fix analyzer warnings and errors set in EditorConfig + + For non interface members Pour les membres non d’interface diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf index b41d35ef75445..4ca13ac893c04 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf @@ -467,6 +467,21 @@ Fix text pasted into string literals (experimental) + + Fix analyzer warnings and errors + Fix analyzer warnings and errors + + + + Fix all warnings and errors set in EditorConfig + Fix all warnings and errors set in EditorConfig + + + + Fix analyzer warnings and errors set in EditorConfig + Fix analyzer warnings and errors set in EditorConfig + + For non interface members Per i membri non di interfaccia diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf index 23ff9c75b6b61..8f3b4b755a72e 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf @@ -467,6 +467,21 @@ Fix text pasted into string literals (experimental) + + Fix analyzer warnings and errors + Fix analyzer warnings and errors + + + + Fix all warnings and errors set in EditorConfig + Fix all warnings and errors set in EditorConfig + + + + Fix analyzer warnings and errors set in EditorConfig + Fix analyzer warnings and errors set in EditorConfig + + For non interface members 非インターフェイスメンバーの場合 diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf index 316b5f58b0965..b9ec52597d46d 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf @@ -467,6 +467,21 @@ Fix text pasted into string literals (experimental) + + Fix analyzer warnings and errors + Fix analyzer warnings and errors + + + + Fix all warnings and errors set in EditorConfig + Fix all warnings and errors set in EditorConfig + + + + Fix analyzer warnings and errors set in EditorConfig + Fix analyzer warnings and errors set in EditorConfig + + For non interface members 비 인터페이스 멤버의 경우 diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf index 15b5fd76c617a..87afaf4d8b910 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf @@ -467,6 +467,21 @@ Fix text pasted into string literals (experimental) + + Fix analyzer warnings and errors + Fix analyzer warnings and errors + + + + Fix all warnings and errors set in EditorConfig + Fix all warnings and errors set in EditorConfig + + + + Fix analyzer warnings and errors set in EditorConfig + Fix analyzer warnings and errors set in EditorConfig + + For non interface members Dla elementów innych niż składowe interfejsu diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf index 50c1c304c2591..37c000891e839 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf @@ -467,6 +467,21 @@ Fix text pasted into string literals (experimental) + + Fix analyzer warnings and errors + Fix analyzer warnings and errors + + + + Fix all warnings and errors set in EditorConfig + Fix all warnings and errors set in EditorConfig + + + + Fix analyzer warnings and errors set in EditorConfig + Fix analyzer warnings and errors set in EditorConfig + + For non interface members Para membros sem interface diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf index 6f252fbcc9ba3..c63b64eea36bf 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf @@ -467,6 +467,21 @@ Fix text pasted into string literals (experimental) + + Fix analyzer warnings and errors + Fix analyzer warnings and errors + + + + Fix all warnings and errors set in EditorConfig + Fix all warnings and errors set in EditorConfig + + + + Fix analyzer warnings and errors set in EditorConfig + Fix analyzer warnings and errors set in EditorConfig + + For non interface members Для элементов без интерфейса diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf index b428e641109f8..56e3f32caadb1 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf @@ -467,6 +467,21 @@ Fix text pasted into string literals (experimental) + + Fix analyzer warnings and errors + Fix analyzer warnings and errors + + + + Fix all warnings and errors set in EditorConfig + Fix all warnings and errors set in EditorConfig + + + + Fix analyzer warnings and errors set in EditorConfig + Fix analyzer warnings and errors set in EditorConfig + + For non interface members Arabirim olmayan üyeler için diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf index 9689e5f2474f8..96da70c75caa3 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf @@ -467,6 +467,21 @@ Fix text pasted into string literals (experimental) + + Fix analyzer warnings and errors + Fix analyzer warnings and errors + + + + Fix all warnings and errors set in EditorConfig + Fix all warnings and errors set in EditorConfig + + + + Fix analyzer warnings and errors set in EditorConfig + Fix analyzer warnings and errors set in EditorConfig + + For non interface members 对于非接口成员 diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf index 5e9fa8c2eada3..8ce90f5612f28 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf @@ -467,6 +467,21 @@ Fix text pasted into string literals (experimental) + + Fix analyzer warnings and errors + Fix analyzer warnings and errors + + + + Fix all warnings and errors set in EditorConfig + Fix all warnings and errors set in EditorConfig + + + + Fix analyzer warnings and errors set in EditorConfig + Fix analyzer warnings and errors set in EditorConfig + + For non interface members 對於非介面成員 diff --git a/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb b/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb index 4482ad3bc2698..bdc17af3f8604 100644 --- a/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb +++ b/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb @@ -521,7 +521,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Public Sub Reanalyze(workspace As Workspace, Optional projectIds As IEnumerable(Of ProjectId) = Nothing, Optional documentIds As IEnumerable(Of DocumentId) = Nothing, Optional highPriority As Boolean = False) Implements IDiagnosticAnalyzerService.Reanalyze End Sub - Public Function GetDiagnosticsForSpanAsync(document As Document, range As TextSpan?, shouldIncludeDiagnostic As Func(Of String, Boolean), Optional includeSuppressedDiagnostics As Boolean = False, Optional priority As CodeActionRequestPriority = CodeActionRequestPriority.None, Optional addOperationScope As Func(Of String, IDisposable) = Nothing, Optional cancellationToken As CancellationToken = Nothing) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetDiagnosticsForSpanAsync + Public Function GetDiagnosticsForSpanAsync(document As Document, range As TextSpan?, shouldIncludeDiagnostic As Func(Of String, Boolean), includeCompilerDiagnostics As Boolean, Optional includeSuppressedDiagnostics As Boolean = False, Optional priority As CodeActionRequestPriority = CodeActionRequestPriority.None, Optional addOperationScope As Func(Of String, IDisposable) = Nothing, Optional cancellationToken As CancellationToken = Nothing) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetDiagnosticsForSpanAsync Return SpecializedTasks.EmptyImmutableArray(Of DiagnosticData) End Function