From a193a3a69980c86ac0fbecc5f80ec928acf57e65 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Oct 2025 00:26:44 +0000 Subject: [PATCH 1/2] Initial plan From 858aa8be54347c4dbfcf82291fafd5f41d08de6c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Oct 2025 00:57:22 +0000 Subject: [PATCH 2/2] Fix fully qualified component semantic tokens - Modified SemanticRange.CompareTo to prioritize Razor tokens over C# tokens when they have the same start position - Added containment check in TryWriteToken to filter out C# tokens that are fully contained within Razor tokens - Added test case for fully qualified component names with baseline files - Fixes issue where C# semantic tokens (namespace, operator, class) were showing through instead of being overridden by Razor component element tokens Co-authored-by: davidwengier <754264+davidwengier@users.noreply.github.com> --- .../AbstractRazorSemanticTokensInfoService.cs | 13 ++++++++++++ .../SemanticTokens/SemanticRange.cs | 21 ++++++++++--------- .../CohostSemanticTokensRangeEndpointTest.cs | 13 ++++++++++++ .../FullyQualifiedComponent.txt | 11 ++++++++++ .../FullyQualifiedComponent_misc_file.txt | 11 ++++++++++ ...ullyQualifiedComponent_with_background.txt | 11 ++++++++++ ...iedComponent_with_background_misc_file.txt | 11 ++++++++++ ...manticTokens\\FullyQualifiedComponent.txt" | 11 ++++++++++ ...ns\\FullyQualifiedComponent_misc_file.txt" | 11 ++++++++++ ...llyQualifiedComponent_with_background.txt" | 11 ++++++++++ ...edComponent_with_background_misc_file.txt" | 11 ++++++++++ 11 files changed, 125 insertions(+), 10 deletions(-) create mode 100644 src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles/SemanticTokens/FullyQualifiedComponent.txt create mode 100644 src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles/SemanticTokens/FullyQualifiedComponent_misc_file.txt create mode 100644 src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles/SemanticTokens/FullyQualifiedComponent_with_background.txt create mode 100644 src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles/SemanticTokens/FullyQualifiedComponent_with_background_misc_file.txt create mode 100644 "src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles\\SemanticTokens\\FullyQualifiedComponent.txt" create mode 100644 "src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles\\SemanticTokens\\FullyQualifiedComponent_misc_file.txt" create mode 100644 "src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles\\SemanticTokens\\FullyQualifiedComponent_with_background.txt" create mode 100644 "src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles\\SemanticTokens\\FullyQualifiedComponent_with_background_misc_file.txt" diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/SemanticTokens/AbstractRazorSemanticTokensInfoService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/SemanticTokens/AbstractRazorSemanticTokensInfoService.cs index bebeef99665..0f91f0f59c0 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/SemanticTokens/AbstractRazorSemanticTokensInfoService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/SemanticTokens/AbstractRazorSemanticTokensInfoService.cs @@ -346,6 +346,19 @@ static bool TryWriteToken( { return false; } + + // If the previous range was from Razor and the current range is from C#, + // and the current range is contained within the previous range, skip it. + // This handles cases like fully qualified component names where Razor provides + // a single token but C# provides multiple tokens for the parts. + if (previousRange.FromRazor && + !currentRange.FromRazor && + previousRange.EndLine == currentRange.EndLine && + previousRange.StartCharacter <= currentRange.StartCharacter && + previousRange.EndCharacter >= currentRange.EndCharacter) + { + return false; + } } else { diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/SemanticTokens/SemanticRange.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/SemanticTokens/SemanticRange.cs index f7c6602112a..a699d477e02 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/SemanticTokens/SemanticRange.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/SemanticTokens/SemanticRange.cs @@ -70,6 +70,17 @@ public int CompareTo(SemanticRange other) return result; } + // If we have ranges with the same start position, we want a Razor produced token to win over a non-Razor produced token + // This is checked before comparing end positions to ensure Razor tokens that span multiple C# tokens are processed first + if (FromRazor && !other.FromRazor) + { + return -1; + } + else if (other.FromRazor && !FromRazor) + { + return 1; + } + result = EndLine.CompareTo(other.EndLine); if (result != 0) { @@ -82,16 +93,6 @@ public int CompareTo(SemanticRange other) return result; } - // If we have ranges that are the same, we want a Razor produced token to win over a non-Razor produced token - if (FromRazor && !other.FromRazor) - { - return -1; - } - else if (other.FromRazor && !FromRazor) - { - return 1; - } - return 0; } diff --git a/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/Endpoints/Shared/CohostSemanticTokensRangeEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/Endpoints/Shared/CohostSemanticTokensRangeEndpointTest.cs index 806d69b0e92..ba5266f1418 100644 --- a/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/Endpoints/Shared/CohostSemanticTokensRangeEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/Endpoints/Shared/CohostSemanticTokensRangeEndpointTest.cs @@ -94,6 +94,19 @@ @section MySection { await VerifySemanticTokensAsync(input, colorBackground, miscellaneousFile, fileKind: RazorFileKind.Legacy); } + [Theory] + [CombinatorialData] + public async Task FullyQualifiedComponent(bool colorBackground, bool miscellaneousFile) + { + var input = """ + @page "/" + + Hello + """; + + await VerifySemanticTokensAsync(input, colorBackground, miscellaneousFile); + } + [Theory] [CombinatorialData] public async Task Legacy_Compatibility(bool colorBackground, bool miscellaneousFile) diff --git a/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles/SemanticTokens/FullyQualifiedComponent.txt b/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles/SemanticTokens/FullyQualifiedComponent.txt new file mode 100644 index 00000000000..1d095c4e7a4 --- /dev/null +++ b/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles/SemanticTokens/FullyQualifiedComponent.txt @@ -0,0 +1,11 @@ +Line Δ, Char Δ, Length, Type, Modifier(s), Text +0 0 1 razorTransition [] [@] +0 1 4 razorDirective [] [page] +0 5 3 string [] ["/"] +2 0 1 markupTagDelimiter [] [<] +0 1 45 razorComponentElement [] [Microsoft.AspNetCore.Components.Web.PageTitle] +0 45 1 markupTagDelimiter [] [>] +0 6 1 markupTagDelimiter [] [<] +0 1 1 markupTagDelimiter [] [/] +0 1 45 razorComponentElement [] [Microsoft.AspNetCore.Components.Web.PageTitle] +0 45 1 markupTagDelimiter [] [>] diff --git a/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles/SemanticTokens/FullyQualifiedComponent_misc_file.txt b/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles/SemanticTokens/FullyQualifiedComponent_misc_file.txt new file mode 100644 index 00000000000..8d59eddd95f --- /dev/null +++ b/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles/SemanticTokens/FullyQualifiedComponent_misc_file.txt @@ -0,0 +1,11 @@ +Line Δ, Char Δ, Length, Type, Modifier(s), Text +0 0 1 razorTransition [] [@] +0 1 4 razorDirective [] [page] +0 5 3 string [] ["/"] +2 0 1 markupTagDelimiter [] [<] +0 1 45 markupElement [] [Microsoft.AspNetCore.Components.Web.PageTitle] +0 45 1 markupTagDelimiter [] [>] +0 6 1 markupTagDelimiter [] [<] +0 1 1 markupTagDelimiter [] [/] +0 1 45 markupElement [] [Microsoft.AspNetCore.Components.Web.PageTitle] +0 45 1 markupTagDelimiter [] [>] diff --git a/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles/SemanticTokens/FullyQualifiedComponent_with_background.txt b/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles/SemanticTokens/FullyQualifiedComponent_with_background.txt new file mode 100644 index 00000000000..eeae8ffea41 --- /dev/null +++ b/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles/SemanticTokens/FullyQualifiedComponent_with_background.txt @@ -0,0 +1,11 @@ +Line Δ, Char Δ, Length, Type, Modifier(s), Text +0 0 1 razorTransition [] [@] +0 1 4 razorDirective [] [page] +0 5 3 string [razorCode] ["/"] +2 0 1 markupTagDelimiter [] [<] +0 1 45 razorComponentElement [] [Microsoft.AspNetCore.Components.Web.PageTitle] +0 45 1 markupTagDelimiter [] [>] +0 6 1 markupTagDelimiter [] [<] +0 1 1 markupTagDelimiter [] [/] +0 1 45 razorComponentElement [] [Microsoft.AspNetCore.Components.Web.PageTitle] +0 45 1 markupTagDelimiter [] [>] diff --git a/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles/SemanticTokens/FullyQualifiedComponent_with_background_misc_file.txt b/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles/SemanticTokens/FullyQualifiedComponent_with_background_misc_file.txt new file mode 100644 index 00000000000..66fd299c73e --- /dev/null +++ b/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles/SemanticTokens/FullyQualifiedComponent_with_background_misc_file.txt @@ -0,0 +1,11 @@ +Line Δ, Char Δ, Length, Type, Modifier(s), Text +0 0 1 razorTransition [] [@] +0 1 4 razorDirective [] [page] +0 5 3 string [razorCode] ["/"] +2 0 1 markupTagDelimiter [] [<] +0 1 45 markupElement [] [Microsoft.AspNetCore.Components.Web.PageTitle] +0 45 1 markupTagDelimiter [] [>] +0 6 1 markupTagDelimiter [] [<] +0 1 1 markupTagDelimiter [] [/] +0 1 45 markupElement [] [Microsoft.AspNetCore.Components.Web.PageTitle] +0 45 1 markupTagDelimiter [] [>] diff --git "a/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles\\SemanticTokens\\FullyQualifiedComponent.txt" "b/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles\\SemanticTokens\\FullyQualifiedComponent.txt" new file mode 100644 index 00000000000..1d095c4e7a4 --- /dev/null +++ "b/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles\\SemanticTokens\\FullyQualifiedComponent.txt" @@ -0,0 +1,11 @@ +Line Δ, Char Δ, Length, Type, Modifier(s), Text +0 0 1 razorTransition [] [@] +0 1 4 razorDirective [] [page] +0 5 3 string [] ["/"] +2 0 1 markupTagDelimiter [] [<] +0 1 45 razorComponentElement [] [Microsoft.AspNetCore.Components.Web.PageTitle] +0 45 1 markupTagDelimiter [] [>] +0 6 1 markupTagDelimiter [] [<] +0 1 1 markupTagDelimiter [] [/] +0 1 45 razorComponentElement [] [Microsoft.AspNetCore.Components.Web.PageTitle] +0 45 1 markupTagDelimiter [] [>] diff --git "a/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles\\SemanticTokens\\FullyQualifiedComponent_misc_file.txt" "b/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles\\SemanticTokens\\FullyQualifiedComponent_misc_file.txt" new file mode 100644 index 00000000000..8d59eddd95f --- /dev/null +++ "b/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles\\SemanticTokens\\FullyQualifiedComponent_misc_file.txt" @@ -0,0 +1,11 @@ +Line Δ, Char Δ, Length, Type, Modifier(s), Text +0 0 1 razorTransition [] [@] +0 1 4 razorDirective [] [page] +0 5 3 string [] ["/"] +2 0 1 markupTagDelimiter [] [<] +0 1 45 markupElement [] [Microsoft.AspNetCore.Components.Web.PageTitle] +0 45 1 markupTagDelimiter [] [>] +0 6 1 markupTagDelimiter [] [<] +0 1 1 markupTagDelimiter [] [/] +0 1 45 markupElement [] [Microsoft.AspNetCore.Components.Web.PageTitle] +0 45 1 markupTagDelimiter [] [>] diff --git "a/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles\\SemanticTokens\\FullyQualifiedComponent_with_background.txt" "b/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles\\SemanticTokens\\FullyQualifiedComponent_with_background.txt" new file mode 100644 index 00000000000..eeae8ffea41 --- /dev/null +++ "b/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles\\SemanticTokens\\FullyQualifiedComponent_with_background.txt" @@ -0,0 +1,11 @@ +Line Δ, Char Δ, Length, Type, Modifier(s), Text +0 0 1 razorTransition [] [@] +0 1 4 razorDirective [] [page] +0 5 3 string [razorCode] ["/"] +2 0 1 markupTagDelimiter [] [<] +0 1 45 razorComponentElement [] [Microsoft.AspNetCore.Components.Web.PageTitle] +0 45 1 markupTagDelimiter [] [>] +0 6 1 markupTagDelimiter [] [<] +0 1 1 markupTagDelimiter [] [/] +0 1 45 razorComponentElement [] [Microsoft.AspNetCore.Components.Web.PageTitle] +0 45 1 markupTagDelimiter [] [>] diff --git "a/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles\\SemanticTokens\\FullyQualifiedComponent_with_background_misc_file.txt" "b/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles\\SemanticTokens\\FullyQualifiedComponent_with_background_misc_file.txt" new file mode 100644 index 00000000000..66fd299c73e --- /dev/null +++ "b/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/TestFiles\\SemanticTokens\\FullyQualifiedComponent_with_background_misc_file.txt" @@ -0,0 +1,11 @@ +Line Δ, Char Δ, Length, Type, Modifier(s), Text +0 0 1 razorTransition [] [@] +0 1 4 razorDirective [] [page] +0 5 3 string [razorCode] ["/"] +2 0 1 markupTagDelimiter [] [<] +0 1 45 markupElement [] [Microsoft.AspNetCore.Components.Web.PageTitle] +0 45 1 markupTagDelimiter [] [>] +0 6 1 markupTagDelimiter [] [<] +0 1 1 markupTagDelimiter [] [/] +0 1 45 markupElement [] [Microsoft.AspNetCore.Components.Web.PageTitle] +0 45 1 markupTagDelimiter [] [>]