| File: Microsoft.NetCore.Analyzers\Runtime\PreferStringContainsOverIndexOf.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.Composition; using System.Threading; using System.Threading.Tasks; using Analyzer.Utilities; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Operations; namespace Microsoft.NetCore.Analyzers.Runtime { [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic), Shared] public sealed class PreferStringContainsOverIndexOfFixer : CodeFixProvider { public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(PreferStringContainsOverIndexOfAnalyzer.RuleId); public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { Document doc = context.Document; CancellationToken cancellationToken = context.CancellationToken; SyntaxNode root = await doc.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); if (root.FindNode(context.Span) is not SyntaxNode expression) { return; } SemanticModel semanticModel = await doc.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var operation = semanticModel.GetOperation(expression, cancellationToken); // Not offering a code-fix for the variable declaration case if (operation is not IBinaryOperation binaryOperation) { return; } IInvocationOperation invocationOperation; IOperation otherOperation; if (binaryOperation.LeftOperand is IInvocationOperation invocationOperationOperand) { invocationOperation = invocationOperationOperand; otherOperation = binaryOperation.RightOperand; } else { invocationOperation = (IInvocationOperation)binaryOperation.RightOperand; otherOperation = binaryOperation.LeftOperand; } switch (invocationOperation.Arguments.Length) { case 1: case 2: break; default: return; } var instanceOperation = invocationOperation.Instance!; context.RegisterCodeFix( CodeAction.Create( title: MicrosoftNetCoreAnalyzersResources.PreferStringContainsOverIndexOfCodeFixTitle, createChangedDocument: c => ReplaceBinaryOperationWithContains(doc, instanceOperation.Syntax, invocationOperation.Arguments, binaryOperation, c), equivalenceKey: MicrosoftNetCoreAnalyzersResources.PreferStringContainsOverIndexOfCodeFixTitle), context.Diagnostics); return; async Task<Document> ReplaceBinaryOperationWithContains(Document document, SyntaxNode syntaxNode, ImmutableArray<IArgumentOperation> indexOfMethodArguments, IBinaryOperation binaryOperation, CancellationToken cancellationToken) { DocumentEditor editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); SyntaxGenerator generator = editor.Generator; var containsExpression = generator.MemberAccessExpression(syntaxNode, "Contains"); SyntaxNode? containsInvocation = null; int numberOfArguments = indexOfMethodArguments.Length; if (numberOfArguments == 1) { var firstArgument = indexOfMethodArguments[0]; if (firstArgument.Parameter?.Type.SpecialType == SpecialType.System_Char) { containsInvocation = generator.InvocationExpression(containsExpression, firstArgument.Syntax); } else { var systemNode = generator.IdentifierName("System"); var argument = generator.MemberAccessExpression(generator.MemberAccessExpression(systemNode, "StringComparison"), "CurrentCulture"); containsInvocation = generator.InvocationExpression(containsExpression, firstArgument.Syntax, argument); } } else { int stringOrCharArgumentIndex, ordinalArgumentIndex; if (indexOfMethodArguments[0].Value.Type?.SpecialType is SpecialType.System_String or SpecialType.System_Char) { stringOrCharArgumentIndex = 0; ordinalArgumentIndex = 1; } else { stringOrCharArgumentIndex = 1; ordinalArgumentIndex = 0; } var ordinalArgumentValue = indexOfMethodArguments[ordinalArgumentIndex].Value; if (ordinalArgumentValue.ConstantValue.HasValue && ordinalArgumentValue.ConstantValue.Value is int intValue && (StringComparison)intValue == StringComparison.Ordinal) { containsInvocation = generator.InvocationExpression(containsExpression, indexOfMethodArguments[stringOrCharArgumentIndex].Syntax); } else { containsInvocation = generator.InvocationExpression(containsExpression, indexOfMethodArguments[0].Syntax, indexOfMethodArguments[1].Syntax); } } // We first check for "IndexOf() == -1" which translates to "!Contains()". All other covered cases do not need negation. SyntaxNode newIfCondition = binaryOperation.OperatorKind == BinaryOperatorKind.Equals && (int)otherOperation.ConstantValue.Value! == -1 ? generator.LogicalNotExpression(containsInvocation) : containsInvocation; newIfCondition = newIfCondition.WithTriviaFrom(binaryOperation.Syntax); editor.ReplaceNode(binaryOperation.Syntax, newIfCondition); var newRoot = editor.GetChangedRoot(); return document.WithSyntaxRoot(newRoot); } } } }