| File: ConvertToInterpolatedString\ConvertRegularStringToInterpolatedStringRefactoringProvider.cs | Web Access |
| Project: src\roslyn\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Composition; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.ConvertToInterpolatedString; /// <summary> /// Code refactoring that converts a regular string containing braces to an interpolated string /// </summary> [ExportCodeRefactoringProvider(LanguageNames.CSharp, LanguageNames.VisualBasic, Name = PredefinedCodeRefactoringProviderNames.ConvertToInterpolatedString), Shared] internal sealed class ConvertRegularStringToInterpolatedStringRefactoringProvider : CodeRefactoringProvider { [ImportingConstructor] [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] public ConvertRegularStringToInterpolatedStringRefactoringProvider() { } public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { var (document, _, cancellationToken) = context; var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); if (root is null) return; var token = root.FindToken(context.Span.Start); if (!context.Span.IntersectsWith(token.Span)) return; var syntaxKinds = document.GetRequiredLanguageService<ISyntaxKindsService>(); if (token.RawKind != syntaxKinds.StringLiteralToken) return; var literalExpression = token.GetRequiredParent(); // Check the string literal for errors. This will ensure that we do not try to fixup an incomplete string. if (literalExpression.GetDiagnostics().Any(d => d.Severity == DiagnosticSeverity.Error)) return; if (!token.Text.Contains('{') && !token.Text.Contains('}')) return; var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>(); if (!syntaxFacts.SupportsConstantInterpolatedStrings(document.Project.ParseOptions!)) { // If there is a const keyword, do not offer the refactoring (an interpolated string is not const) var declarator = literalExpression.FirstAncestorOrSelf<SyntaxNode>(syntaxFacts.IsVariableDeclarator); if (declarator != null) { var generator = SyntaxGenerator.GetGenerator(document); if (generator.GetModifiers(declarator).IsConst) return; } // Attributes also only allow constant values. var attribute = literalExpression.FirstAncestorOrSelf<SyntaxNode>(syntaxFacts.IsAttribute); if (attribute != null) return; } context.RegisterRefactoring( CodeAction.Create( FeaturesResources.Convert_to_interpolated_string, _ => UpdateDocumentAsync(document, root, token), nameof(FeaturesResources.Convert_to_interpolated_string), CodeActionPriority.Low), literalExpression.Span); } private static string GetTextWithoutQuotes(string text, bool isVerbatim) { // Trim off an extra character (@ symbol) for verbatim strings var startIndex = isVerbatim ? 2 : 1; return text[startIndex..^1]; } private static SyntaxNode CreateInterpolatedString(Document document, SyntaxNode literalExpression, bool isVerbatim) { var generator = SyntaxGenerator.GetGenerator(document); var text = literalExpression.GetFirstToken().Text; var valueText = literalExpression.GetFirstToken().ValueText; var newNode = generator.InterpolatedStringText( generator.InterpolatedStringTextToken( GetTextWithoutQuotes(text.Replace("{", "{{").Replace("}", "}}"), isVerbatim), valueText)); return generator.InterpolatedStringExpression( generator.CreateInterpolatedStringStartToken(isVerbatim), [newNode], generator.CreateInterpolatedStringEndToken()).WithTriviaFrom(literalExpression); } private static Task<Document> UpdateDocumentAsync(Document document, SyntaxNode root, SyntaxToken token) { var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>(); var literalExpression = token.GetRequiredParent(); return Task.FromResult(document.WithSyntaxRoot( root.ReplaceNode( literalExpression, CreateInterpolatedString(document, literalExpression, syntaxFacts.IsVerbatimStringLiteral(token))))); } }