diff --git a/src/EditorFeatures/CSharpTest/ConvertToInterpolatedString/ConvertConcatenationToInterpolatedStringTests.cs b/src/EditorFeatures/CSharpTest/ConvertToInterpolatedString/ConvertConcatenationToInterpolatedStringTests.cs index f1313905dbf74..7ccbb8527d930 100644 --- a/src/EditorFeatures/CSharpTest/ConvertToInterpolatedString/ConvertConcatenationToInterpolatedStringTests.cs +++ b/src/EditorFeatures/CSharpTest/ConvertToInterpolatedString/ConvertConcatenationToInterpolatedStringTests.cs @@ -54,6 +54,19 @@ void M() }"); } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)] + public async Task TestMissingOnConcatenatedStrings3() + { + await TestMissingInRegularAndScriptAsync( +@"public class C +{ + void M() + { + var v = ""string"" + '.' + [||]""string""; + } +}"); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)] public async Task TestWithStringOnLeft() { @@ -270,6 +283,19 @@ void M() }"); } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)] + public async Task TestMissingWithMixedStringTypes3() + { + await TestMissingInRegularAndScriptAsync( +@"public class C +{ + void M() + { + var v = 1 + @""string"" + 2 + [||]'\n'; + } +}"); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)] public async Task TestWithOverloadedOperator() { @@ -728,6 +754,77 @@ void M() { var v = $""{1}{(""string"")}""; } +}"); + } + + [WorkItem(37324, "https://github.com/dotnet/roslyn/issues/37324")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)] + public async Task TestConcatenationWithChar() + { + await TestInRegularAndScriptAsync( +@"public class C +{ + void M() + { + var hello = ""hello""; + var world = ""world""; + var str = hello [||]+ ' ' + world; + } +}", +@"public class C +{ + void M() + { + var hello = ""hello""; + var world = ""world""; + var str = $""{hello} {world}""; + } +}"); + } + + [WorkItem(37324, "https://github.com/dotnet/roslyn/issues/37324")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)] + public async Task TestConcatenationWithCharAfterStringLiteral() + { + await TestInRegularAndScriptAsync( +@"public class C +{ + void M() + { + var world = ""world""; + var str = ""hello"" [||]+ ' ' + world; + } +}", +@"public class C +{ + void M() + { + var world = ""world""; + var str = $""hello {world}""; + } +}"); + } + + [WorkItem(37324, "https://github.com/dotnet/roslyn/issues/37324")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)] + public async Task TestConcatenationWithCharBeforeStringLiteral() + { + await TestInRegularAndScriptAsync( +@"public class C +{ + void M() + { + var hello = ""hello""; + var str = hello [||]+ ' ' + ""world""; + } +}", +@"public class C +{ + void M() + { + var hello = ""hello""; + var str = $""{hello} world""; + } }"); } } diff --git a/src/EditorFeatures/VisualBasicTest/ConvertToInterpolatedString/ConvertConcatenationToInterpolatedStringTests.vb b/src/EditorFeatures/VisualBasicTest/ConvertToInterpolatedString/ConvertConcatenationToInterpolatedStringTests.vb index 347fc57bb42ab..2d65b3246c30e 100644 --- a/src/EditorFeatures/VisualBasicTest/ConvertToInterpolatedString/ConvertConcatenationToInterpolatedStringTests.vb +++ b/src/EditorFeatures/VisualBasicTest/ConvertToInterpolatedString/ConvertConcatenationToInterpolatedStringTests.vb @@ -462,6 +462,68 @@ Public Class C Sub M() dim v = $""{3}string{1}string"" End Sub +End Class") + End Function + + + + Public Async Function TestConcatenationWithChar() As Task + Await TestInRegularAndScriptAsync( +" +Public Class C + Private Sub M() + Dim hello = ""hello"" + Dim world = ""world"" + Dim str = hello [||]& "" ""c & world + End Sub +End Class", +" +Public Class C + Private Sub M() + Dim hello = ""hello"" + Dim world = ""world"" + Dim str = $""{hello} {world}"" + End Sub +End Class") + End Function + + + + Public Async Function TestConcatenationWithCharAfterStringLiteral() As Task + Await TestInRegularAndScriptAsync( +" +Public Class C + Private Sub M() + Dim world = ""world"" + Dim str = ""hello"" [||]& "" ""c & world + End Sub +End Class", +" +Public Class C + Private Sub M() + Dim world = ""world"" + Dim str = $""hello {world}"" + End Sub +End Class") + End Function + + + + Public Async Function TestConcatenationWithCharBeforeStringLiteral() As Task + Await TestInRegularAndScriptAsync( +" +Public Class C + Private Sub M() + Dim hello = ""hello"" + Dim str = hello [||]& "" ""c & ""world"" + End Sub +End Class", +" +Public Class C + Private Sub M() + Dim hello = ""hello"" + Dim str = $""{hello} world"" + End Sub End Class") End Function End Class diff --git a/src/Features/CSharp/Portable/ConvertToInterpolatedString/CSharpConvertConcatenationToInterpolatedStringRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertToInterpolatedString/CSharpConvertConcatenationToInterpolatedStringRefactoringProvider.cs index f9f3445950e07..4008120f95161 100644 --- a/src/Features/CSharp/Portable/ConvertToInterpolatedString/CSharpConvertConcatenationToInterpolatedStringRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertToInterpolatedString/CSharpConvertConcatenationToInterpolatedStringRefactoringProvider.cs @@ -27,7 +27,7 @@ protected override SyntaxToken CreateInterpolatedStringStartToken(bool isVerbati protected override SyntaxToken CreateInterpolatedStringEndToken() => SyntaxFactory.Token(SyntaxKind.InterpolatedStringEndToken); - protected override string GetTextWithoutQuotes(string text, bool isVerbatim) + protected override string GetTextWithoutQuotes(string text, bool isVerbatim, bool isCharacterLiteral) => isVerbatim ? text.Substring("@'".Length, text.Length - "@''".Length) : text.Substring("'".Length, text.Length - "''".Length); diff --git a/src/Features/Core/Portable/ConvertToInterpolatedString/AbstractConvertConcatenationToInterpolatedStringRefactoringProvider.cs b/src/Features/Core/Portable/ConvertToInterpolatedString/AbstractConvertConcatenationToInterpolatedStringRefactoringProvider.cs index 077e3e29187f6..99b1b3580abcf 100644 --- a/src/Features/Core/Portable/ConvertToInterpolatedString/AbstractConvertConcatenationToInterpolatedStringRefactoringProvider.cs +++ b/src/Features/Core/Portable/ConvertToInterpolatedString/AbstractConvertConcatenationToInterpolatedStringRefactoringProvider.cs @@ -60,7 +60,9 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte var pieces = new List(); CollectPiecesDown(syntaxFacts, pieces, top, semanticModel, cancellationToken); - var stringLiterals = pieces.Where(syntaxFacts.IsStringLiteralExpression).ToImmutableArray(); + var stringLiterals = pieces + .Where(x => syntaxFacts.IsStringLiteralExpression(x) || syntaxFacts.IsCharacterLiteralExpression(x)) + .ToImmutableArray(); // If the entire expression is just concatenated strings, then don't offer to // make an interpolated string. The user likely manually split this for @@ -113,12 +115,13 @@ private Task UpdateDocumentAsync(Document document, SyntaxNode root, S var previousContentWasStringLiteralExpression = false; foreach (var piece in pieces) { - var currentContentIsStringLiteral = syntaxFacts.IsStringLiteralExpression(piece); - if (currentContentIsStringLiteral) + var isCharacterLiteral = syntaxFacts.IsCharacterLiteralExpression(piece); + var currentContentIsStringOrCharacterLiteral = syntaxFacts.IsStringLiteralExpression(piece) || isCharacterLiteral; + if (currentContentIsStringOrCharacterLiteral) { var text = piece.GetFirstToken().Text; var textWithEscapedBraces = text.Replace("{", "{{").Replace("}", "}}"); - var textWithoutQuotes = GetTextWithoutQuotes(textWithEscapedBraces, isVerbatimStringLiteral); + var textWithoutQuotes = GetTextWithoutQuotes(textWithEscapedBraces, isVerbatimStringLiteral, isCharacterLiteral); if (previousContentWasStringLiteralExpression) { // Last part we added to the content list was also an interpolated-string-text-node. @@ -145,7 +148,7 @@ private Task UpdateDocumentAsync(Document document, SyntaxNode root, S } // Update this variable to be true every time we encounter a new string literal expression // so we know to concatinate future string literals together if we encounter them. - previousContentWasStringLiteralExpression = currentContentIsStringLiteral; + previousContentWasStringLiteralExpression = currentContentIsStringOrCharacterLiteral; } return generator.InterpolatedStringExpression(startToken, content, endToken); @@ -158,7 +161,7 @@ private static SyntaxNode ConcatinateTextToTextNode(SyntaxGenerator generator, S return generator.InterpolatedStringText(generator.InterpolatedStringTextToken(newText)); } - protected abstract string GetTextWithoutQuotes(string text, bool isVerbatimStringLiteral); + protected abstract string GetTextWithoutQuotes(string text, bool isVerbatimStringLiteral, bool isCharacterLiteral); protected abstract SyntaxToken CreateInterpolatedStringStartToken(bool isVerbatimStringLiteral); protected abstract SyntaxToken CreateInterpolatedStringEndToken(); diff --git a/src/Features/VisualBasic/Portable/ConvertToInterpolatedString/VisualBasicConvertConcatenationToInterpolatedStringRefactoringProvider.vb b/src/Features/VisualBasic/Portable/ConvertToInterpolatedString/VisualBasicConvertConcatenationToInterpolatedStringRefactoringProvider.vb index 0e1c5d8501dd2..21de699e54066 100644 --- a/src/Features/VisualBasic/Portable/ConvertToInterpolatedString/VisualBasicConvertConcatenationToInterpolatedStringRefactoringProvider.vb +++ b/src/Features/VisualBasic/Portable/ConvertToInterpolatedString/VisualBasicConvertConcatenationToInterpolatedStringRefactoringProvider.vb @@ -21,8 +21,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ConvertToInterpolatedString Return SyntaxFactory.Token(SyntaxKind.DoubleQuoteToken) End Function - Protected Overrides Function GetTextWithoutQuotes(text As String, isVerbatim As Boolean) As String - Return text.Substring("'".Length, text.Length - "''".Length) + Protected Overrides Function GetTextWithoutQuotes(text As String, isVerbatim As Boolean, isCharacterLiteral As Boolean) As String + If isCharacterLiteral Then + Return text.Substring("'".Length, text.Length - "''C".Length) + Else + Return text.Substring("'".Length, text.Length - "''".Length) + End If End Function End Class End Namespace diff --git a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs index 51564133faf8a..3323ddcf2fee4 100644 --- a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs +++ b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs @@ -1311,6 +1311,9 @@ public override bool IsInterpolatedStringTextToken(SyntaxToken token) public bool IsStringLiteralExpression(SyntaxNode node) => node.Kind() == SyntaxKind.StringLiteralExpression; + public bool IsCharacterLiteralExpression(SyntaxNode node) + => node.Kind() == SyntaxKind.CharacterLiteralExpression; + public bool IsVerbatimStringLiteral(SyntaxToken token) => token.IsVerbatimStringLiteral(); diff --git a/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs b/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs index 6471fa87bbda4..b2dad6afe9c76 100644 --- a/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs +++ b/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs @@ -84,6 +84,7 @@ internal interface ISyntaxFactsService : ILanguageService bool IsVerbatimStringLiteral(SyntaxToken token); bool IsInterpolatedStringTextToken(SyntaxToken token); bool IsStringLiteralExpression(SyntaxNode node); + bool IsCharacterLiteralExpression(SyntaxNode node); bool IsTypeNamedVarInVariableOrFieldDeclaration(SyntaxToken token, SyntaxNode parent); bool IsTypeNamedDynamic(SyntaxToken token, SyntaxNode parent); diff --git a/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb b/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb index 0c2ebfacda3cb..e257bcfef6a66 100644 --- a/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb +++ b/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb @@ -1362,6 +1362,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return node.Kind() = SyntaxKind.StringLiteralExpression End Function + Public Function IsCharacterLiteralExpression(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsCharacterLiteralExpression + Return node.Kind() = SyntaxKind.CharacterLiteralExpression + End Function + Public Function IsVerbatimStringLiteral(token As SyntaxToken) As Boolean Implements ISyntaxFactsService.IsVerbatimStringLiteral ' VB does not have verbatim strings Return False