// 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 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; namespace Microsoft.NetCore.Analyzers.Runtime { [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic), Shared] public class PreferConstCharOverConstUnitStringFixer : CodeFixProvider { public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(PreferConstCharOverConstUnitStringAnalyzer.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 SyntaxNode expression) { SemanticModel semanticModel = await doc.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var operation = semanticModel.GetOperation(expression, cancellationToken); if (operation is IArgumentOperation argumentOperation) { var localReferenceOperation = argumentOperation.Value as ILocalReferenceOperation; var literalOperation = argumentOperation.Value as ILiteralOperation; if (localReferenceOperation == null && literalOperation == null) { return; } IVariableDeclaratorOperation? variableDeclaratorOperation = default; if (localReferenceOperation != null) { ILocalSymbol localArgumentDeclaration = localReferenceOperation.Local; SyntaxReference? declaringSyntaxReference = localArgumentDeclaration.DeclaringSyntaxReferences.FirstOrDefault(); if (declaringSyntaxReference is null) { return; } variableDeclaratorOperation = semanticModel.GetOperationWalkingUpParentChain(await declaringSyntaxReference.GetSyntaxAsync(cancellationToken).ConfigureAwait(false), cancellationToken) as IVariableDeclaratorOperation; if (variableDeclaratorOperation == null) { return; } var variableInitializerOperation = variableDeclaratorOperation.GetVariableInitializer(); if (variableInitializerOperation == null) { return; } var variableDeclarationOperation = (IVariableDeclarationOperation?)variableDeclaratorOperation.Parent; if (variableDeclarationOperation == null) { return; } var variableGroupDeclarationOperation = (IVariableDeclarationGroupOperation?)variableDeclarationOperation.Parent; if (variableGroupDeclarationOperation?.Declarations.Length != 1) { return; } if (variableDeclarationOperation.Declarators.Length != 1) { return; } } context.RegisterCodeFix( CodeAction.Create( title: MicrosoftNetCoreAnalyzersResources.PreferConstCharOverConstUnitStringInStringBuilderTitle, createChangedDocument: async c => { if (literalOperation != null) { return await HandleStringLiteral(literalOperation, doc, root, cancellationToken).ConfigureAwait(false); } else { RoslynDebug.Assert(variableDeclaratorOperation != null); return await HandleVariableDeclarator(variableDeclaratorOperation!, doc, root, cancellationToken).ConfigureAwait(false); } }, equivalenceKey: MicrosoftNetCoreAnalyzersResources.PreferConstCharOverConstUnitStringInStringBuilderMessage), context.Diagnostics); static async Task<Document> HandleStringLiteral(ILiteralOperation argumentLiteral, Document doc, SyntaxNode root, CancellationToken cancellationToken) { var unitString = (string)argumentLiteral.ConstantValue.Value!; DocumentEditor editor = await DocumentEditor.CreateAsync(doc, cancellationToken).ConfigureAwait(false); SyntaxGenerator generator = editor.Generator; char charValue = unitString[0]; SyntaxNode charLiteralExpressionNode = generator.LiteralExpression(charValue); var newRoot = generator.ReplaceNode(root, argumentLiteral.Syntax, charLiteralExpressionNode); return doc.WithSyntaxRoot(newRoot); } static async Task<Document> HandleVariableDeclarator(IVariableDeclaratorOperation variableDeclaratorOperation, Document doc, SyntaxNode root, CancellationToken cancellationToken) { IVariableDeclarationOperation variableDeclarationOperation = (IVariableDeclarationOperation)variableDeclaratorOperation.Parent!; IVariableDeclarationGroupOperation variableGroupDeclarationOperation = (IVariableDeclarationGroupOperation)variableDeclarationOperation.Parent!; DocumentEditor editor = await DocumentEditor.CreateAsync(doc, cancellationToken).ConfigureAwait(false); SyntaxGenerator generator = editor.Generator; ILocalSymbol currentSymbol = variableDeclaratorOperation.Symbol; var variableInitializerOperation = variableDeclaratorOperation.GetVariableInitializer()!; string unitString = (string)variableInitializerOperation.Value.ConstantValue.Value!; char charValue = unitString[0]; SyntaxNode charLiteralExpressionNode = generator.LiteralExpression(charValue); var charTypeNode = generator.TypeExpression(SpecialType.System_Char); var charSyntaxNode = generator.LocalDeclarationStatement(charTypeNode, currentSymbol.Name, charLiteralExpressionNode, isConst: true); charSyntaxNode = charSyntaxNode.WithTriviaFrom(variableGroupDeclarationOperation.Syntax); var newRoot = generator.ReplaceNode(root, variableGroupDeclarationOperation.Syntax, charSyntaxNode); return doc.WithSyntaxRoot(newRoot); } } } } } }