diff --git a/src/Analyzers/CSharp/CodeFixes/HideBase/HideBaseCodeFixProvider.AddNewKeywordAction.cs b/src/Analyzers/CSharp/CodeFixes/HideBase/HideBaseCodeFixProvider.AddNewKeywordAction.cs index 566daa3dd5da9..097323ff0681d 100644 --- a/src/Analyzers/CSharp/CodeFixes/HideBase/HideBaseCodeFixProvider.AddNewKeywordAction.cs +++ b/src/Analyzers/CSharp/CodeFixes/HideBase/HideBaseCodeFixProvider.AddNewKeywordAction.cs @@ -2,17 +2,16 @@ // 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.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.LanguageService; using Microsoft.CodeAnalysis.CSharp.OrderModifiers; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.OrderModifiers; using Microsoft.CodeAnalysis.Shared.Extensions; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.CodeFixes.HideBase; @@ -20,46 +19,36 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeFixes.HideBase; internal sealed partial class HideBaseCodeFixProvider { - private sealed class AddNewKeywordAction(Document document, SyntaxNode node) : CodeAction + private static async Task GetChangedDocumentAsync( + Document document, SyntaxNode node, CancellationToken cancellationToken) { - private readonly Document _document = document; - private readonly SyntaxNode _node = node; + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var modifierOrder = await GetModifierOrderAsync(document, cancellationToken).ConfigureAwait(false); - public override string Title => CSharpCodeFixesResources.Hide_base_member; - - protected override async Task GetChangedDocumentAsync(CancellationToken cancellationToken) - { - var root = await _document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var configOptions = await _document.GetHostAnalyzerConfigOptionsAsync(cancellationToken).ConfigureAwait(false); - - var newNode = GetNewNode(_node, configOptions.GetOption(CSharpCodeStyleOptions.PreferredModifierOrder).Value); - var newRoot = root.ReplaceNode(_node, newNode); + return document.WithSyntaxRoot(root.ReplaceNode(node, GetNewNode(node, modifierOrder))); + } - return _document.WithSyntaxRoot(newRoot); - } + private static SyntaxNode GetNewNode(SyntaxNode node, Dictionary? preferredOrder) + { + var syntaxFacts = CSharpSyntaxFacts.Instance; + var modifiers = syntaxFacts.GetModifiers(node); + var newModifiers = modifiers.Add(NewKeyword); - private static SyntaxNode GetNewNode(SyntaxNode node, string preferredModifierOrder) + if (preferredOrder is null || + !AbstractOrderModifiersHelpers.IsOrdered(preferredOrder, modifiers)) { - var syntaxFacts = CSharpSyntaxFacts.Instance; - var modifiers = syntaxFacts.GetModifiers(node); - var newModifiers = modifiers.Add(NewKeyword); - - if (!CSharpOrderModifiersHelper.Instance.TryGetOrComputePreferredOrder(preferredModifierOrder, out var preferredOrder) || - !AbstractOrderModifiersHelpers.IsOrdered(preferredOrder, modifiers)) - { - return syntaxFacts.WithModifiers(node, newModifiers); - } + return syntaxFacts.WithModifiers(node, newModifiers); + } - var orderedModifiers = new SyntaxTokenList( - newModifiers.OrderBy(CompareModifiers)); + var orderedModifiers = new SyntaxTokenList( + newModifiers.OrderBy(CompareModifiers)); - return syntaxFacts.WithModifiers(node, orderedModifiers); + return syntaxFacts.WithModifiers(node, orderedModifiers); - int CompareModifiers(SyntaxToken left, SyntaxToken right) - => GetOrder(left) - GetOrder(right); + int CompareModifiers(SyntaxToken left, SyntaxToken right) + => GetOrder(left) - GetOrder(right); - int GetOrder(SyntaxToken token) - => preferredOrder.TryGetValue(token.RawKind, out var value) ? value : int.MaxValue; - } + int GetOrder(SyntaxToken token) + => preferredOrder.TryGetValue(token.RawKind, out var value) ? value : int.MaxValue; } } diff --git a/src/Analyzers/CSharp/CodeFixes/HideBase/HideBaseCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/HideBase/HideBaseCodeFixProvider.cs index cfc813e2319e6..0143983311f31 100644 --- a/src/Analyzers/CSharp/CodeFixes/HideBase/HideBaseCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/HideBase/HideBaseCodeFixProvider.cs @@ -2,13 +2,20 @@ // 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.Collections.Generic; using System.Collections.Immutable; using System.Composition; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.OrderModifiers; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.CSharp.CodeFixes.HideBase; @@ -16,31 +23,60 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeFixes.HideBase; [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.AddNew), Shared] [method: ImportingConstructor] [method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] -internal sealed partial class HideBaseCodeFixProvider() : CodeFixProvider +internal sealed partial class HideBaseCodeFixProvider() : SyntaxEditorBasedCodeFixProvider { internal const string CS0108 = nameof(CS0108); // 'SomeClass.SomeMember' hides inherited member 'SomeClass.SomeMember'. Use the new keyword if hiding was intended. public override ImmutableArray FixableDiagnosticIds => [CS0108]; - public override FixAllProvider GetFixAllProvider() - => WellKnownFixAllProviders.BatchFixer; - - public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + private static SyntaxNode? GetOriginalNode(SyntaxNode root, Diagnostic diagnostic) { - var root = await context.Document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - - var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; - var token = root.FindToken(diagnosticSpan.Start); var originalNode = token.GetAncestor() ?? token.GetAncestor() ?? (SyntaxNode?)token.GetAncestor(); + return originalNode; + } + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + var originalNode = GetOriginalNode(root, context.Diagnostics.First()); if (originalNode == null) return; - context.RegisterCodeFix(new AddNewKeywordAction(context.Document, originalNode), context.Diagnostics); + context.RegisterCodeFix(CodeAction.Create( + CSharpCodeFixesResources.Hide_base_member, + cancellationToken => GetChangedDocumentAsync(context.Document, originalNode, cancellationToken), + nameof(CSharpCodeFixesResources.Hide_base_member)), + context.Diagnostics); + } + + protected override async Task FixAllAsync( + Document document, ImmutableArray diagnostics, SyntaxEditor editor, CancellationToken cancellationToken) + { + var modifierOrder = await GetModifierOrderAsync(document, cancellationToken).ConfigureAwait(false); + + var root = editor.OriginalRoot; + foreach (var diagnostic in diagnostics) + { + var originalNode = GetOriginalNode(root, diagnostic); + if (originalNode == null) + continue; + + var newNode = GetNewNode(originalNode, modifierOrder); + editor.ReplaceNode(originalNode, newNode); + } + } + + private static async Task?> GetModifierOrderAsync(Document document, CancellationToken cancellationToken) + { + var configOptions = await document.GetHostAnalyzerConfigOptionsAsync(cancellationToken).ConfigureAwait(false); + var modifierOrder = configOptions.GetOption(CSharpCodeStyleOptions.PreferredModifierOrder).Value; + CSharpOrderModifiersHelper.Instance.TryGetOrComputePreferredOrder(modifierOrder, out var preferredOrder); + return preferredOrder; } }