| File: Microsoft.NetCore.Analyzers\Runtime\UseOrdinalStringComparison.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.Threading; using System.Threading.Tasks; using Analyzer.Utilities; using Analyzer.Utilities.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.NetAnalyzers; namespace Microsoft.NetCore.Analyzers.Runtime { public abstract class UseOrdinalStringComparisonFixerBase : SyntaxEditorBasedCodeFixProvider { public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(UseOrdinalStringComparisonAnalyzer.RuleId); public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { SyntaxNode root = await context.Document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); if (!await CanFixAsync(context.Document, root.FindNode(context.Span), context.CancellationToken).ConfigureAwait(false)) { return; } string title = MicrosoftNetCoreAnalyzersResources.UseOrdinalStringComparisonTitle; RegisterCodeFix(context, title, title); } /// <summary> /// Reports whether the fix rewrites <paramref name="node"/>. The analyzer reports every unacceptable /// overload, including the ones no added argument can turn into an acceptable one, so registering /// without this would offer an action that leaves the document unchanged. /// </summary> private async Task<bool> CanFixAsync(Document document, SyntaxNode node, CancellationToken cancellationToken) { if (IsInArgumentContext(node)) { return true; } if (!IsInIdentifierNameContext(node)) { return false; } SemanticModel model = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); return model.GetSymbolInfo(node, cancellationToken).Symbol is IMethodSymbol methodSymbol && CanAddStringComparison(methodSymbol, model); } protected sealed override async Task ApplyFixAsync(Document document, Diagnostic diagnostic, SyntaxEditor editor, CancellationToken cancellationToken) { SyntaxNode node = editor.OriginalRoot.FindNode(diagnostic.Location.SourceSpan); if (IsInArgumentContext(node)) { // StringComparison.CurrentCulture => StringComparison.Ordinal // StringComparison.CurrentCultureIgnoreCase => StringComparison.OrdinalIgnoreCase FixArgument(node, editor); return; } // string.Equals(a, b) => string.Equals(a, b, StringComparison.Ordinal) // string.Compare(a, b) => string.Compare(a, b, StringComparison.Ordinal) if (!IsInIdentifierNameContext(node) || GetInvocation(node) is not SyntaxNode invocation) { return; } SemanticModel model = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); if (model.GetSymbolInfo(node, cancellationToken).Symbol is not IMethodSymbol methodSymbol || !CanAddStringComparison(methodSymbol, model)) { return; } // The new invocation carries over the original's arguments, so it has to be built from the // invocation as the fixes before this one left it rather than off the original tree. editor.ReplaceNode( invocation, (currentInvocation, generator) => AddArgument( currentInvocation, generator.Argument(CreateOrdinalMemberAccess(generator, model)).WithAdditionalAnnotations(Formatter.Annotation))); } protected abstract bool IsInArgumentContext(SyntaxNode node); protected abstract void FixArgument(SyntaxNode argument, SyntaxEditor editor); protected abstract bool IsInIdentifierNameContext(SyntaxNode node); /// <summary> /// Returns the invocation <paramref name="identifier"/> names, or <see langword="null"/> when there is none. /// </summary> protected abstract SyntaxNode? GetInvocation(SyntaxNode identifier); /// <summary> /// Appends <paramref name="argument"/> to <paramref name="invocation"/>'s argument list. /// </summary> protected abstract SyntaxNode AddArgument(SyntaxNode invocation, SyntaxNode argument); internal static SyntaxNode CreateOrdinalMemberAccess(SyntaxGenerator generator, SemanticModel model) { INamedTypeSymbol stringComparisonType = model.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemStringComparison)!; return generator.MemberAccessExpression( generator.TypeExpressionForStaticMemberAccess(stringComparisonType), generator.IdentifierName(UseOrdinalStringComparisonAnalyzer.OrdinalText)); } protected static bool CanAddStringComparison(IMethodSymbol methodSymbol, SemanticModel model) { if (model.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemStringComparison) == null) { return false; } ImmutableArray<IParameterSymbol> parameters = methodSymbol.Parameters; switch (methodSymbol.Name) { case UseOrdinalStringComparisonAnalyzer.EqualsMethodName: // can fix .Equals() with (string), (string, string) switch (parameters.Length) { case 1: return parameters[0].Type.SpecialType == SpecialType.System_String; case 2: return parameters[0].Type.SpecialType == SpecialType.System_String && parameters[1].Type.SpecialType == SpecialType.System_String; } break; case UseOrdinalStringComparisonAnalyzer.CompareMethodName: // can fix .Compare() with (string, string), (string, int, string, int, int) switch (parameters.Length) { case 2: return parameters[0].Type.SpecialType == SpecialType.System_String && parameters[1].Type.SpecialType == SpecialType.System_String; case 5: return parameters[0].Type.SpecialType == SpecialType.System_String && parameters[1].Type.SpecialType == SpecialType.System_Int32 && parameters[2].Type.SpecialType == SpecialType.System_String && parameters[3].Type.SpecialType == SpecialType.System_Int32 && parameters[4].Type.SpecialType == SpecialType.System_Int32; } break; } return false; } } }