| File: Microsoft.NetCore.Analyzers\Performance\PreferReadOnlySpanOverSpan.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.Collections.Immutable; using System.Composition; 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.NetAnalyzers; namespace Microsoft.NetCore.Analyzers.Performance { /// <summary> /// CA1517: Use ReadOnlySpan<T> or ReadOnlyMemory<T> instead of Span<T> or Memory<T> /// </summary> [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(PreferReadOnlySpanOverSpanFixer))] [Shared] public sealed class PreferReadOnlySpanOverSpanFixer : CodeFixProvider { public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(PreferReadOnlySpanOverSpanAnalyzer.RuleId); // The title names the target type, so one document can offer several distinct actions and a // fix-all has to apply only the one that was invoked. public sealed override FixAllProvider GetFixAllProvider() => SyntaxEditorFixAllProvider.Create<string?>( fixAllContext => fixAllContext.CodeActionEquivalenceKey, async (document, diagnostic, editor, equivalenceKey, cancellationToken) => { var node = editor.OriginalRoot.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); if (semanticModel.GetDeclaredSymbol(node, cancellationToken) is IParameterSymbol parameterSymbol && GetReadOnlyTypeName(parameterSymbol.Type) is { } targetTypeName && (equivalenceKey is null || GetTitle(targetTypeName) == equivalenceKey)) { ChangeParameterType(editor, semanticModel.Compilation, node, (INamedTypeSymbol)parameterSymbol.Type); } }); public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); var node = root.FindNode(context.Span, getInnermostNodeForTie: true); var semanticModel = await context.Document.GetRequiredSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); if (semanticModel.GetDeclaredSymbol(node, context.CancellationToken) is IParameterSymbol parameterSymbol && GetReadOnlyTypeName(parameterSymbol.Type) is { } targetTypeName) { var title = GetTitle(targetTypeName); context.RegisterCodeFix( CodeAction.Create( title: title, createChangedDocument: c => ChangeParameterTypeAsync(context.Document, node, c), equivalenceKey: title), context.Diagnostics[0]); } } private static string GetTitle(string targetTypeName) => string.Format(MicrosoftNetCoreAnalyzersResources.PreferReadOnlySpanOverSpanCodeFixTitle, targetTypeName); private static string? GetReadOnlyTypeName(ITypeSymbol typeSymbol) => typeSymbol is INamedTypeSymbol namedType && namedType.OriginalDefinition.Name is "Span" or "Memory" ? $"ReadOnly{typeSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)}" : null; private static void ChangeParameterType(SyntaxEditor editor, Compilation compilation, SyntaxNode node, INamedTypeSymbol parameterType) { if (parameterType.TypeArguments.Length != 1) { return; } var typeName = parameterType.OriginalDefinition.Name; INamedTypeSymbol? readOnlyType = typeName is "Span" ? compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlySpan1) : typeName is "Memory" ? compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlyMemory1) : null; if (readOnlyType is null) { return; } // Construct the generic type with the same type argument var newTypeNode = editor.Generator.TypeExpression(readOnlyType.Construct(parameterType.TypeArguments[0])); // Replace the parameter's type editor.ReplaceNode(node, (currentNode, gen) => gen.WithType(currentNode, newTypeNode)); } private static async Task<Document> ChangeParameterTypeAsync( Document document, SyntaxNode node, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); // Get the parameter symbol to construct the correct type if (semanticModel.GetDeclaredSymbol(node, cancellationToken) is IParameterSymbol { Type: INamedTypeSymbol namedType }) { ChangeParameterType(editor, semanticModel.Compilation, node, namedType); } return editor.GetChangedDocument(); } } }