diff --git a/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs b/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs index 88da122eaaab69c0c1c5339a4c1a5a5950a8f108..8e2f14b399b1984b07c3f39cbbe3fc6897903b9b 100644 --- a/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs +++ b/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs @@ -2629,5 +2629,12 @@ public async Task TestStringEscape8() Escape(@"{{"), Escape(@"}}")); } + + [WorkItem(29451, "https://github.com/dotnet/roslyn/issues/29451")] + [Fact, Trait(Traits.Feature, Traits.Features.Classification)] + public async Task TestDirectiveStringLiteral() + { + await TestInMethodAsync(@"#line 1 ""a\b"""); + } } } diff --git a/src/EditorFeatures/VisualBasicTest/Classification/SemanticClassifierTests.vb b/src/EditorFeatures/VisualBasicTest/Classification/SemanticClassifierTests.vb index efc96ac60c338e3afd067da2395f4de0ace49a06..c612a107d8348e227478d1e09da4de08c23ee468 100644 --- a/src/EditorFeatures/VisualBasicTest/Classification/SemanticClassifierTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Classification/SemanticClassifierTests.vb @@ -676,5 +676,12 @@ End Operator" Escape("{{"), Escape("}}")) End Function + + + + Public Async Function TestDirectiveStringLiteral() As Task + Await TestAsync("#region ""goo""""bar""", + Escape("""""")) + End Function End Class End Namespace diff --git a/src/Workspaces/CSharp/Portable/EmbeddedLanguages/VirtualChars/CSharpVirtualCharService.cs b/src/Workspaces/CSharp/Portable/EmbeddedLanguages/VirtualChars/CSharpVirtualCharService.cs index 8ed7ca227f02abc875f3caecdcb25eeb833191bd..0d558da8b23225d1b887dbb1b61dbc8d278a72d4 100644 --- a/src/Workspaces/CSharp/Portable/EmbeddedLanguages/VirtualChars/CSharpVirtualCharService.cs +++ b/src/Workspaces/CSharp/Portable/EmbeddedLanguages/VirtualChars/CSharpVirtualCharService.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Immutable; using System.Composition; using System.Diagnostics; @@ -22,6 +23,23 @@ protected override bool IsStringLiteralToken(SyntaxToken token) protected override ImmutableArray TryConvertToVirtualCharsWorker(SyntaxToken token) { + // C# preprocessor directives can contain string literals. However, these string + // literals do not behave like normal literals. Because they are used for paths (i.e. + // in a #line directive), the language does not do any escaping within them. i.e. if + // you have a \ it's just a \ Note that this is not a verbatim string. You can't put + // a double quote in it either, and you cannot have newlines and whatnot. + // + // We technically could convert this trivially to an array of virtual chars. After all, + // there would just be a 1:1 correspondance with the literal contents and the chars + // returned. However, we don't even both returning anything here. That's because + // there's no useful features we can offer here. Because there are no escape characters + // we won't classify any escape characters. And there is no way that these strings would + // be Regex/Json snippets. So it's easier to just bail out and return nothing. + if (IsInDirective(token.Parent)) + { + return default; + } + Debug.Assert(!token.ContainsDiagnostics); if (token.Kind() == SyntaxKind.StringLiteralToken) { @@ -42,6 +60,21 @@ protected override ImmutableArray TryConvertToVirtualCharsWorker(Sy } } + private bool IsInDirective(SyntaxNode node) + { + while (node != null) + { + if (node is DirectiveTriviaSyntax) + { + return true; + } + + node = node.Parent; + } + + return false; + } + private ImmutableArray TryConvertVerbatimStringToVirtualChars(SyntaxToken token, string startDelimiter, string endDelimiter, bool escapeBraces) => TryConvertSimpleDoubleQuoteString(token, startDelimiter, endDelimiter, escapeBraces);