| File: Microsoft.NetCore.Analyzers\Runtime\PreferAsSpanOverSubstring.Fixer.cs | Web Access |
| Project: src\sdk\src\Microsoft.CodeAnalysis.NetAnalyzers\src\Microsoft.CodeAnalysis.NetAnalyzers\Microsoft.CodeAnalysis.NetAnalyzers.csproj (Microsoft.CodeAnalysis.NetAnalyzers) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Analyzer.Utilities; using Analyzer.Utilities.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Operations; using RequiredSymbols = Microsoft.NetCore.Analyzers.Runtime.PreferAsSpanOverSubstring.RequiredSymbols; namespace Microsoft.NetCore.Analyzers.Runtime { public abstract class PreferAsSpanOverSubstringFixer : CodeFixProvider { private const string SubstringStartIndexArgumentName = "startIndex"; private const string AsSpanStartArgumentName = "start"; private protected abstract void ReplaceNonConditionalInvocationMethodName(SyntaxEditor editor, SyntaxNode memberInvocation, string newName); private protected abstract void ReplaceNamedArgumentName(SyntaxEditor editor, SyntaxNode invocation, string oldArgumentName, string newArgumentName); public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(PreferAsSpanOverSubstring.RuleId); public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { var document = context.Document; var token = context.CancellationToken; SyntaxNode root = await document.GetRequiredSyntaxRootAsync(token).ConfigureAwait(false); SemanticModel model = await document.GetRequiredSemanticModelAsync(token).ConfigureAwait(false); var compilation = model.Compilation; if (!RequiredSymbols.TryGetSymbols(compilation, out RequiredSymbols symbols) || root.FindNode(context.Span, getInnermostNodeForTie: true) is not SyntaxNode reportedNode || model.GetOperation(reportedNode, token) is not IInvocationOperation reportedInvocation) { return; } var bestCandidates = PreferAsSpanOverSubstring.GetBestSpanBasedOverloads(symbols, reportedInvocation, context.CancellationToken); // We only apply the fix if there is an unambiguous best overload. if (bestCandidates.Length != 1) return; IMethodSymbol spanBasedOverload = bestCandidates[0]; string title = MicrosoftNetCoreAnalyzersResources.PreferAsSpanOverSubstringCodefixTitle; var codeAction = CodeAction.Create(title, CreateChangedDocument, title); context.RegisterCodeFix(codeAction, context.Diagnostics); return; async Task<Document> CreateChangedDocument(CancellationToken token) { var editor = await DocumentEditor.CreateAsync(document, token).ConfigureAwait(false); foreach (var argument in reportedInvocation.Arguments) { IOperation value = argument.Value.WalkDownConversion(c => c.IsImplicit); IParameterSymbol newParameter = spanBasedOverload.Parameters[argument.Parameter!.Ordinal]; // Convert Substring invocations to equivalent AsSpan invocations. if (symbols.IsAnySubstringInvocation(value) && SymbolEqualityComparer.Default.Equals(newParameter.Type, symbols.ReadOnlySpanOfCharType)) { ReplaceNonConditionalInvocationMethodName(editor, value.Syntax, nameof(MemoryExtensions.AsSpan)); // Ensure named Substring arguments get renamed to their equivalent AsSpan counterparts. ReplaceNamedArgumentName(editor, value.Syntax, SubstringStartIndexArgumentName, AsSpanStartArgumentName); } // Ensure named arguments on the original overload are renamed to their // ordinal counterparts on the new overload. string oldArgumentName = argument.Parameter.Name; string newArgumentName = newParameter.Name; ReplaceNamedArgumentName(editor, reportedInvocation.Syntax, oldArgumentName, newArgumentName); } // Import System namespace if necessary. if (!IsMemoryExtensionsInScope(symbols, reportedInvocation)) { SyntaxNode withoutSystemImport = editor.GetChangedRoot(); SyntaxNode systemNamespaceImportStatement = editor.Generator.NamespaceImportDeclaration(nameof(System)); SyntaxNode withSystemImport = editor.Generator.AddNamespaceImports(withoutSystemImport, systemNamespaceImportStatement); editor.ReplaceNode(editor.OriginalRoot, withSystemImport); } return editor.GetChangedDocument(); } } public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; private static bool IsMemoryExtensionsInScope(in RequiredSymbols symbols, IInvocationOperation invocation) { var model = invocation.SemanticModel!; int position = invocation.Syntax.SpanStart; const string name = nameof(MemoryExtensions); return model.LookupNamespacesAndTypes(position, name: name) .Contains(symbols.MemoryExtensionsType, SymbolEqualityComparer.Default); } } }