| File: ConvertBetweenRegularAndVerbatimString\AbstractConvertBetweenRegularAndVerbatimStringCodeRefactoringProvider.cs | Web Access |
| Project: src\roslyn\src\Features\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Features.csproj (Microsoft.CodeAnalysis.CSharp.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.Diagnostics; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.CSharp.ConvertBetweenRegularAndVerbatimString; internal abstract class AbstractConvertBetweenRegularAndVerbatimStringCodeRefactoringProvider< TStringExpressionSyntax> : CodeRefactoringProvider where TStringExpressionSyntax : ExpressionSyntax { private const char OpenBrace = '{'; private const char CloseBrace = '}'; protected const char DoubleQuote = '"'; protected abstract bool IsInterpolation { get; } protected abstract bool IsAppropriateLiteralKind(TStringExpressionSyntax literalExpression); protected abstract void AddSubStringTokens(TStringExpressionSyntax literalExpression, ArrayBuilder<SyntaxToken> subTokens); protected abstract bool IsVerbatim(TStringExpressionSyntax literalExpression); protected abstract TStringExpressionSyntax CreateVerbatimStringExpression(IVirtualCharService charService, StringBuilder sb, TStringExpressionSyntax stringExpression); protected abstract TStringExpressionSyntax CreateRegularStringExpression(IVirtualCharService charService, StringBuilder sb, TStringExpressionSyntax stringExpression); public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { var literalExpression = await context.TryGetRelevantNodeAsync<TStringExpressionSyntax>().ConfigureAwait(false); if (literalExpression == null || !IsAppropriateLiteralKind(literalExpression)) return; var (document, _, cancellationToken) = context; var charService = document.GetRequiredLanguageService<IVirtualCharLanguageService>(); using var _ = ArrayBuilder<SyntaxToken>.GetInstance(out var subStringTokens); // First, ensure that we understand all text parts of the interpolation. AddSubStringTokens(literalExpression, subStringTokens); foreach (var subToken in subStringTokens) { var chars = charService.TryConvertToVirtualChars(subToken); if (chars.IsDefault) return; } // Note: This is a generally useful feature on strings. But it's not likely to be something // people want to use a lot. Make low priority so it doesn't interfere with more // commonly useful refactorings. if (IsVerbatim(literalExpression)) { // always offer to convert from verbatim string to normal string. context.RegisterRefactoring(CodeAction.Create( CSharpFeaturesResources.Convert_to_regular_string, c => ConvertToRegularStringAsync(document, literalExpression, c), nameof(CSharpFeaturesResources.Convert_to_regular_string), CodeActionPriority.Low)); } else if (ContainsSimpleEscape(charService, subStringTokens)) { // Offer to convert to a verbatim string if the normal string contains simple // escapes that can be directly embedded in the verbatim string. context.RegisterRefactoring(CodeAction.Create( CSharpFeaturesResources.Convert_to_verbatim_string, c => ConvertToVerbatimStringAsync(document, literalExpression, c), nameof(CSharpFeaturesResources.Convert_to_verbatim_string), CodeActionPriority.Low)); } } private static async Task<Document> ConvertAsync( Func<IVirtualCharService, StringBuilder, TStringExpressionSyntax, TStringExpressionSyntax> convert, Document document, TStringExpressionSyntax stringExpression, CancellationToken cancellationToken) { using var _ = PooledStringBuilder.GetInstance(out var sb); var charService = document.GetRequiredLanguageService<IVirtualCharLanguageService>(); var newStringExpression = convert(charService, sb, stringExpression).WithTriviaFrom(stringExpression); var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); return document.WithSyntaxRoot(root.ReplaceNode(stringExpression, newStringExpression)); } private Task<Document> ConvertToVerbatimStringAsync(Document document, TStringExpressionSyntax stringExpression, CancellationToken cancellationToken) => ConvertAsync(CreateVerbatimStringExpression, document, stringExpression, cancellationToken); private Task<Document> ConvertToRegularStringAsync(Document document, TStringExpressionSyntax stringExpression, CancellationToken cancellationToken) => ConvertAsync(CreateRegularStringExpression, document, stringExpression, cancellationToken); protected void AddVerbatimStringText( IVirtualCharService charService, StringBuilder sb, SyntaxToken stringToken) { var isInterpolation = IsInterpolation; var chars = charService.TryConvertToVirtualChars(stringToken); foreach (var ch in chars) { // just build the verbatim string by concatenating all the chars in the original // string. The only exceptions are double-quotes which need to be doubled up in the // final string, and curlies which need to be doubled in interpolations. sb.Append(ch); if (ShouldDouble(ch, isInterpolation)) sb.Append(ch); } static bool ShouldDouble(VirtualChar ch, bool isInterpolation) { if (ch == DoubleQuote) return true; if (isInterpolation) return IsOpenOrCloseBrace(ch); return false; } } private static bool IsOpenOrCloseBrace(VirtualChar ch) => ch == OpenBrace || ch == CloseBrace; protected void AddRegularStringText( IVirtualCharService charService, StringBuilder sb, SyntaxToken stringToken) { var isInterpolation = IsInterpolation; var chars = charService.TryConvertToVirtualChars(stringToken); foreach (var ch in chars) { if (charService.TryGetEscapeCharacter(ch, out var escaped)) { sb.Append('\\'); sb.Append(escaped); } else { sb.Append(ch); // if it's an interpolation, we need to double-up open/close braces. if (isInterpolation && IsOpenOrCloseBrace(ch)) sb.Append(ch); } } } private static bool ContainsSimpleEscape( IVirtualCharService charService, ArrayBuilder<SyntaxToken> subTokens) { foreach (var subToken in subTokens) { var chars = charService.TryConvertToVirtualChars(subToken); // This was checked above. Debug.Assert(!chars.IsDefault); if (ContainsSimpleEscape(chars)) return true; } return false; } private static bool ContainsSimpleEscape(VirtualCharSequence chars) { foreach (var ch in chars) { // look for two-character escapes that start with \ . i.e. \n . Note: \0 // cannot be encoded into a verbatim string, so don't offer to convert if we have // that. if (ch.Span.Length == 2 && ch != 0) return true; } return false; } }