diff --git a/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs b/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs index 2e7e57b3eaf49..88cef8127f556 100644 --- a/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs +++ b/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs @@ -4086,6 +4086,68 @@ public async Task TestStringEscape9(TestHost testHost) Escape(@"}}")); } + [Theory] + [CombinatorialData] + public async Task TestNotStringEscapeInRawLiteral1(TestHost testHost) + { + await TestInMethodAsync(@"var goo = """"""goo\r\nbar"""""";", + testHost, + Keyword("var")); + } + + [Theory] + [CombinatorialData] + public async Task TestNotStringEscapeInRawLiteral2(TestHost testHost) + { + await TestInMethodAsync(@"var goo = """""" + goo\r\nbar + """""";", + testHost, + Keyword("var")); + } + + [Theory] + [CombinatorialData] + public async Task TestNotStringEscapeInRawLiteral3(TestHost testHost) + { + await TestInMethodAsync(@"var goo = $"""""" + goo\r\nbar + """""";", + testHost, + Keyword("var")); + } + + [Theory] + [CombinatorialData] + public async Task TestNotStringEscapeInRawLiteral4(TestHost testHost) + { + await TestInMethodAsync(@"var goo = """"""\"""""";", + testHost, + Keyword("var")); + } + + [Theory] + [CombinatorialData] + public async Task TestNotStringEscapeInRawLiteral5(TestHost testHost) + { + await TestInMethodAsync(@"var goo = """""" + \ + """""";", + testHost, + Keyword("var")); + } + + [Theory] + [CombinatorialData] + public async Task TestNotStringEscapeInRawLiteral6(TestHost testHost) + { + await TestInMethodAsync(@"var goo = $"""""" + \ + """""";", + testHost, + Keyword("var")); + } + [WorkItem(31200, "https://github.com/dotnet/roslyn/issues/31200")] [Theory] [CombinatorialData] diff --git a/src/Workspaces/Core/Portable/EmbeddedLanguages/LanguageServices/FallbackSyntaxClassifier.cs b/src/Workspaces/Core/Portable/EmbeddedLanguages/LanguageServices/FallbackSyntaxClassifier.cs index 10cb42c5c7ba9..76bb4f5cd772a 100644 --- a/src/Workspaces/Core/Portable/EmbeddedLanguages/LanguageServices/FallbackSyntaxClassifier.cs +++ b/src/Workspaces/Core/Portable/EmbeddedLanguages/LanguageServices/FallbackSyntaxClassifier.cs @@ -36,6 +36,11 @@ public override void AddClassifications( if (virtualChars.IsDefaultOrEmpty) return; + // Can avoid any work if we got the same number of virtual characters back as characters in the string. In + // that case, there are clearly no escaped characters. + if (virtualChars.Length == token.Text.Length) + return; + foreach (var vc in virtualChars) { if (vc.Span.Length > 1) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/EmbeddedLanguages/VirtualChars/CSharpVirtualCharService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/EmbeddedLanguages/VirtualChars/CSharpVirtualCharService.cs index 769ea9ab8bfce..bbf3e04d0a3b1 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/EmbeddedLanguages/VirtualChars/CSharpVirtualCharService.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/EmbeddedLanguages/VirtualChars/CSharpVirtualCharService.cs @@ -54,7 +54,7 @@ protected override VirtualCharSequence TryConvertToVirtualCharsWorker(SyntaxToke return TryConvertStringToVirtualChars(token, "'", "'", escapeBraces: false); if (token.Kind() is SyntaxKind.SingleLineRawStringLiteralToken or SyntaxKind.MultiLineRawStringLiteralToken) - return TryConvertRawStringToVirtualChars(token); + return TryConvertRawStringToVirtualChars(token, skipDelimiterQuotes: true); if (token.Kind() == SyntaxKind.InterpolatedStringTextToken) { @@ -64,9 +64,16 @@ protected override VirtualCharSequence TryConvertToVirtualCharsWorker(SyntaxToke if (parent.Parent is InterpolatedStringExpressionSyntax interpolatedString) { - return interpolatedString.StringStartToken.Kind() == SyntaxKind.InterpolatedVerbatimStringStartToken - ? TryConvertVerbatimStringToVirtualChars(token, "", "", escapeBraces: true) - : TryConvertStringToVirtualChars(token, "", "", escapeBraces: true); + return interpolatedString.StringStartToken.Kind() switch + { + SyntaxKind.InterpolatedStringStartToken + => TryConvertStringToVirtualChars(token, "", "", escapeBraces: true), + SyntaxKind.InterpolatedVerbatimStringStartToken + => TryConvertVerbatimStringToVirtualChars(token, "", "", escapeBraces: true), + SyntaxKind.InterpolatedSingleLineRawStringStartToken or SyntaxKind.InterpolatedMultiLineRawStringStartToken + => TryConvertRawStringToVirtualChars(token, skipDelimiterQuotes: false), + _ => default, + }; } } @@ -89,25 +96,29 @@ private static bool IsInDirective(SyntaxNode? node) private static VirtualCharSequence TryConvertVerbatimStringToVirtualChars(SyntaxToken token, string startDelimiter, string endDelimiter, bool escapeBraces) => TryConvertSimpleDoubleQuoteString(token, startDelimiter, endDelimiter, escapeBraces); - private static VirtualCharSequence TryConvertRawStringToVirtualChars(SyntaxToken token) + private static VirtualCharSequence TryConvertRawStringToVirtualChars( + SyntaxToken token, bool skipDelimiterQuotes) { var tokenText = token.Text; var offset = token.SpanStart; - Contract.ThrowIfFalse(tokenText[0] == '"'); - Contract.ThrowIfFalse(tokenText[^1] == '"'); - using var _ = ArrayBuilder.GetInstance(out var result); var startIndexInclusive = 0; var endIndexExclusive = tokenText.Length; - while (tokenText[startIndexInclusive] == '"') + if (skipDelimiterQuotes) { - // All quotes should be paired at the end - Contract.ThrowIfFalse(tokenText[endIndexExclusive - 1] == '"'); - startIndexInclusive++; - endIndexExclusive--; + Contract.ThrowIfFalse(tokenText[0] == '"'); + Contract.ThrowIfFalse(tokenText[^1] == '"'); + + while (tokenText[startIndexInclusive] == '"') + { + // All quotes should be paired at the end + Contract.ThrowIfFalse(tokenText[endIndexExclusive - 1] == '"'); + startIndexInclusive++; + endIndexExclusive--; + } } for (var index = startIndexInclusive; index < endIndexExclusive;)