From 96ba294262842e30811e3711b03be33357ef803f Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 15 Jul 2024 16:21:04 +1000 Subject: [PATCH 1/7] Share some files --- .../LanguageServer}/TestClientCapabilitiesService.cs | 0 .../LanguageServer}/TestRazorSemanticTokensLegendService.cs | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/Razor/test/{Microsoft.AspNetCore.Razor.LanguageServer.Test => Microsoft.AspNetCore.Razor.Test.Common.Tooling/LanguageServer}/TestClientCapabilitiesService.cs (100%) rename src/Razor/test/{Microsoft.AspNetCore.Razor.LanguageServer.Test/Semantic => Microsoft.AspNetCore.Razor.Test.Common.Tooling/LanguageServer}/TestRazorSemanticTokensLegendService.cs (100%) diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/TestClientCapabilitiesService.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/LanguageServer/TestClientCapabilitiesService.cs similarity index 100% rename from src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/TestClientCapabilitiesService.cs rename to src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/LanguageServer/TestClientCapabilitiesService.cs diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Semantic/TestRazorSemanticTokensLegendService.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/LanguageServer/TestRazorSemanticTokensLegendService.cs similarity index 100% rename from src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Semantic/TestRazorSemanticTokensLegendService.cs rename to src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/LanguageServer/TestRazorSemanticTokensLegendService.cs From 2ff5ca055b0effdf83f1b20fc06d797828231feb Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 15 Jul 2024 16:34:55 +1000 Subject: [PATCH 2/7] Create semantic tokens tests for cohosting --- .../CohostSemanticTokensRangeEndpoint.cs | 17 +- .../CohostSemanticTokensRangeEndpointTest.cs | 202 ++++++++++++++++++ .../Cohost/CohostTestBase.cs | 38 +++- ...lStudio.LanguageServices.Razor.Test.csproj | 4 + .../TestFiles/SemanticTokens/Legacy.txt | 54 +++++ .../SemanticTokens/Legacy_with_background.txt | 60 ++++++ .../TestFiles/SemanticTokens/Razor.txt | 71 ++++++ .../SemanticTokens/Razor_with_background.txt | 86 ++++++++ 8 files changed, 527 insertions(+), 5 deletions(-) create mode 100644 src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostSemanticTokensRangeEndpointTest.cs create mode 100644 src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/TestFiles/SemanticTokens/Legacy.txt create mode 100644 src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/TestFiles/SemanticTokens/Legacy_with_background.txt create mode 100644 src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/TestFiles/SemanticTokens/Razor.txt create mode 100644 src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/TestFiles/SemanticTokens/Razor_with_background.txt diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostSemanticTokensRangeEndpoint.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostSemanticTokensRangeEndpoint.cs index d59809ccb74..cf12d711944 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostSemanticTokensRangeEndpoint.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostSemanticTokensRangeEndpoint.cs @@ -9,11 +9,13 @@ using Microsoft.AspNetCore.Razor; using Microsoft.AspNetCore.Razor.LanguageServer.Hosting; using Microsoft.AspNetCore.Razor.Telemetry; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; using Microsoft.CodeAnalysis.Razor.Logging; using Microsoft.CodeAnalysis.Razor.Remote; using Microsoft.CodeAnalysis.Razor.SemanticTokens; using Microsoft.CodeAnalysis.Razor.Workspaces; +using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; using Microsoft.VisualStudio.Razor.LanguageClient.Extensions; using Microsoft.VisualStudio.Razor.Settings; @@ -68,11 +70,16 @@ internal sealed class CohostSemanticTokensRangeEndpoint( protected override RazorTextDocumentIdentifier? GetRazorTextDocumentIdentifier(SemanticTokensRangeParams request) => request.TextDocument.ToRazorTextDocumentIdentifier(); - protected override async Task HandleRequestAsync(SemanticTokensRangeParams request, RazorCohostRequestContext context, CancellationToken cancellationToken) + protected override Task HandleRequestAsync(SemanticTokensRangeParams request, RazorCohostRequestContext context, CancellationToken cancellationToken) { var razorDocument = context.TextDocument.AssumeNotNull(); var span = request.Range.ToLinePositionSpan(); + return HandleRequestAsync(razorDocument, span, cancellationToken); + } + + private async Task HandleRequestAsync(TextDocument razorDocument, LinePositionSpan span, CancellationToken cancellationToken) + { var colorBackground = _clientSettingsManager.GetClientSettings().AdvancedSettings.ColorBackground; var correlationId = Guid.NewGuid(); @@ -93,4 +100,12 @@ internal sealed class CohostSemanticTokensRangeEndpoint( return null; } + + internal TestAccessor GetTestAccessor() => new(this); + + internal readonly struct TestAccessor(CohostSemanticTokensRangeEndpoint instance) + { + public Task HandleRequestAsync(TextDocument razorDocument, LinePositionSpan span, CancellationToken cancellationToken) + => instance.HandleRequestAsync(razorDocument, span, cancellationToken); + } } diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostSemanticTokensRangeEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostSemanticTokensRangeEndpointTest.cs new file mode 100644 index 00000000000..55f80474f43 --- /dev/null +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostSemanticTokensRangeEndpointTest.cs @@ -0,0 +1,202 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using System.IO; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.AspNetCore.Razor.LanguageServer.Semantic; +using Microsoft.AspNetCore.Razor.PooledObjects; +using Microsoft.AspNetCore.Razor.Telemetry; +using Microsoft.CodeAnalysis.Razor.Settings; +using Microsoft.CodeAnalysis.Razor.Workspaces; +using Microsoft.CodeAnalysis.Remote.Razor.SemanticTokens; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Razor.Settings; +using Microsoft.VisualStudio.Utilities; +using Roslyn.Test.Utilities; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost; + +public class CohostSemanticTokensRangeEndpointTest(ITestOutputHelper testOutputHelper) : CohostTestBase(testOutputHelper) +{ + [Theory] + [CombinatorialData] + public async Task Razor(bool colorBackground) + { + var input = """ + @page "/" + @using System + +
This is some HTML
+ + + + @* hello there *@ + + + @if (true) + { + Html! + } + + @code + { + // I am also good, thanks for asking + + /* + No problem. + */ + + private string someValue; + + public void M() + { + RenderFragment x = @
This is some HTML in a render fragment
; + } + } + """; + + await VerifySemanticTokensAsync(input, colorBackground); + } + + [Theory] + [CombinatorialData] + public async Task Legacy(bool colorBackground) + { + var input = """ + @page "/" + @using System + +
This is some HTML
+ + + + @functions + { + public void M() + { + } + } + + @section MySection { +
Section content
+ } + """; + + await VerifySemanticTokensAsync(input, colorBackground, fileKind: FileKinds.Legacy); + } + + private async Task VerifySemanticTokensAsync(string input, bool colorBackground, string? fileKind = null, [CallerMemberName] string? testName = null) + { + var document = CreateProjectAndRazorDocument(input, fileKind); + var sourceText = await document.GetTextAsync(DisposalToken); + + var legend = TestRazorSemanticTokensLegendService.Instance; + + // We need to manually initialize the OOP service so we can get semantic token info later + RemoteSemanticTokensLegendService.SetLegend(legend.TokenTypes.All, legend.TokenModifiers.All); + + var clientSettingsManager = new ClientSettingsManager([], null, null); + clientSettingsManager.Update(ClientAdvancedSettings.Default with { ColorBackground = colorBackground }); + + var endpoint = new CohostSemanticTokensRangeEndpoint(RemoteServiceProvider, clientSettingsManager, legend, NoOpTelemetryReporter.Instance, LoggerFactory); + + var span = new LinePositionSpan(new(0, 0), new(sourceText.Lines.Count, 0)); + + var result = await endpoint.GetTestAccessor().HandleRequestAsync(document, span, DisposalToken); + + var actualFileContents = GetTestOutput(sourceText, result?.Data); + + if (colorBackground) + { + testName += "_with_background"; + } + + var baselineFileName = $@"TestFiles\SemanticTokens\{testName}.txt"; + if (GenerateBaselines.ShouldGenerate) + { + WriteBaselineFile(actualFileContents, baselineFileName); + } + + var expectedFileContents = GetBaselineFileContents(baselineFileName); + AssertEx.EqualOrDiff(expectedFileContents, actualFileContents); + } + + private string GetBaselineFileContents(string baselineFileName) + { + var semanticFile = TestFile.Create(baselineFileName, GetType().Assembly); + if (!semanticFile.Exists()) + { + return string.Empty; + } + + return semanticFile.ReadAllText(); + } + + private static void WriteBaselineFile(string fileContents, string baselineFileName) + { + var projectPath = TestProject.GetProjectDirectory(typeof(CohostSemanticTokensRangeEndpointTest), layer: TestProject.Layer.Tooling); + var baselineFileFullPath = Path.Combine(projectPath, baselineFileName); + File.WriteAllText(baselineFileFullPath, fileContents); + } + + private static string GetTestOutput(SourceText sourceText, int[]? data) + { + if (data == null) + { + return string.Empty; + } + + using var _ = StringBuilderPool.GetPooledObject(out var builder); + builder.AppendLine("Line Δ, Char Δ, Length, Type, Modifier(s), Text"); + var tokenTypes = TestRazorSemanticTokensLegendService.Instance.TokenTypes.All; + var prevLength = 0; + var lineIndex = 0; + var lineOffset = 0; + for (var i = 0; i < data.Length; i += 5) + { + var lineDelta = data[i]; + var charDelta = data[i + 1]; + var length = data[i + 2]; + + Assert.False(i != 0 && lineDelta == 0 && charDelta == 0, "line delta and character delta are both 0, which is invalid as we shouldn't be producing overlapping tokens"); + Assert.False(i != 0 && lineDelta == 0 && charDelta < prevLength, "Previous length is longer than char offset from previous start, meaning tokens will overlap"); + + if (lineDelta != 0) + { + lineOffset = 0; + } + + lineIndex += lineDelta; + lineOffset += charDelta; + + var type = tokenTypes[data[i + 3]]; + var modifier = GetTokenModifierString(data[i + 4]); + var text = sourceText.GetSubTextString(new TextSpan(sourceText.Lines[lineIndex].Start + lineOffset, length)); + builder.AppendLine($"{lineDelta} {charDelta} {length} {type} {modifier} [{text}]"); + + prevLength = length; + } + + return builder.ToString(); + } + + private static string GetTokenModifierString(int tokenModifiers) + { + var modifiers = TestRazorSemanticTokensLegendService.Instance.TokenModifiers.All; + + var modifiersBuilder = ArrayBuilder.GetInstance(); + for (var i = 0; i < modifiers.Length; i++) + { + if ((tokenModifiers & (1 << i % 32)) != 0) + { + modifiersBuilder.Add(modifiers[i]); + } + } + + return $"[{string.Join(", ", modifiersBuilder.ToArrayAndFree())}]"; + } +} diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTestBase.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTestBase.cs index 61b0d52d5a8..614097f847e 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTestBase.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTestBase.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Basic.Reference.Assemblies; using Microsoft.AspNetCore.Razor; +using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Test.Common; using Microsoft.AspNetCore.Razor.Test.Common.Workspaces; using Microsoft.CodeAnalysis; @@ -18,6 +19,7 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost; public abstract class CohostTestBase(ITestOutputHelper testOutputHelper) : WorkspaceTestBase(testOutputHelper) { + private const string CSharpVirtualDocumentSuffix = ".g.cs"; private IRemoteServiceProvider? _remoteServiceProvider; private protected IRemoteServiceProvider RemoteServiceProvider => _remoteServiceProvider.AssumeNotNull(); @@ -28,12 +30,27 @@ protected override async Task InitializeAsync() var exportProvider = AddDisposable(await RemoteMefComposition.CreateExportProviderAsync()); _remoteServiceProvider = AddDisposable(new TestRemoteServiceProvider(exportProvider)); + + RemoteLanguageServerFeatureOptions.SetOptions(new() + { + CSharpVirtualDocumentSuffix = CSharpVirtualDocumentSuffix, + HtmlVirtualDocumentSuffix = ".g.html", + IncludeProjectKeyInGeneratedFilePath = false, + UsePreciseSemanticTokenRanges = false, + UseRazorCohostServer = true + }); } - protected TextDocument CreateProjectAndRazorDocument(string contents) + protected TextDocument CreateProjectAndRazorDocument(string contents, string? fileKind = null) { + // Using IsLegacy means null == component, so easier for test authors + var isComponent = !FileKinds.IsLegacy(fileKind); + + var documentFilePath = isComponent + ? TestProjectData.SomeProjectComponentFile1.FilePath + : TestProjectData.SomeProjectFile1.FilePath; + var projectFilePath = TestProjectData.SomeProject.FilePath; - var documentFilePath = TestProjectData.SomeProjectComponentFile1.FilePath; var projectName = Path.GetFileNameWithoutExtension(projectFilePath); var projectId = ProjectId.CreateNewId(debugName: projectName); var documentId = DocumentId.CreateNewId(projectId, debugName: documentFilePath); @@ -56,16 +73,29 @@ protected TextDocument CreateProjectAndRazorDocument(string contents) documentFilePath, SourceText.From(contents), filePath: documentFilePath) + .AddDocument( + DocumentId.CreateNewId(projectId), + name: documentFilePath + CSharpVirtualDocumentSuffix, + SourceText.From(""), + filePath: documentFilePath + CSharpVirtualDocumentSuffix) .AddAdditionalDocument( DocumentId.CreateNewId(projectId), - name: "_Imports.razor", + name: TestProjectData.SomeProjectComponentImportFile1.FilePath, text: SourceText.From(""" @using Microsoft.AspNetCore.Components @using Microsoft.AspNetCore.Components.Authorization + @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Web """), - filePath: TestProjectData.SomeProjectComponentImportFile1.FilePath); + filePath: TestProjectData.SomeProjectComponentImportFile1.FilePath) + .AddAdditionalDocument( + DocumentId.CreateNewId(projectId), + name: "_ViewImports.cshtml", + text: SourceText.From(""" + @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers + """), + filePath: TestProjectData.SomeProjectImportFile.FilePath); return solution.GetAdditionalDocument(documentId).AssumeNotNull(); } diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Microsoft.VisualStudio.LanguageServices.Razor.Test.csproj b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Microsoft.VisualStudio.LanguageServices.Razor.Test.csproj index 264ae60a47e..441ecb802fe 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Microsoft.VisualStudio.LanguageServices.Razor.Test.csproj +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Microsoft.VisualStudio.LanguageServices.Razor.Test.csproj @@ -30,4 +30,8 @@ + + + + diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/TestFiles/SemanticTokens/Legacy.txt b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/TestFiles/SemanticTokens/Legacy.txt new file mode 100644 index 00000000000..99325b3275a --- /dev/null +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/TestFiles/SemanticTokens/Legacy.txt @@ -0,0 +1,54 @@ +Line Δ, Char Δ, Length, Type, Modifier(s), Text +0 0 1 razorTransition [] [@] +0 1 4 razorDirective [] [page] +0 5 3 string [] ["/"] +1 0 1 razorTransition [] [@] +0 1 5 keyword [] [using] +0 6 6 namespace name [] [System] +2 0 1 markupTagDelimiter [] [<] +0 1 3 markupElement [] [div] +0 3 1 markupTagDelimiter [] [>] +0 18 1 markupTagDelimiter [] [<] +0 1 1 markupTagDelimiter [] [/] +0 1 3 markupElement [] [div] +0 3 1 markupTagDelimiter [] [>] +2 0 1 markupTagDelimiter [] [<] +0 1 9 razorTagHelperElement [] [component] +0 10 4 razorTagHelperAttribute [] [type] +0 4 1 markupOperator [] [=] +0 1 1 markupAttributeQuote [] ["] +0 1 6 keyword [] [typeof] +0 6 1 punctuation [] [(] +0 1 9 property name [] [Component] +0 9 1 punctuation [] [)] +0 1 1 markupAttributeQuote [] ["] +0 2 11 razorTagHelperAttribute [] [render-mode] +0 11 1 markupOperator [] [=] +0 1 1 markupAttributeQuote [] ["] +0 1 17 enum member name [] [ServerPrerendered] +0 17 1 markupAttributeQuote [] ["] +0 2 1 markupTagDelimiter [] [/] +0 1 1 markupTagDelimiter [] [>] +2 0 1 razorTransition [] [@] +0 1 9 razorDirective [] [functions] +1 0 1 razorTransition [] [{] +1 4 6 keyword [] [public] +0 7 4 keyword [] [void] +0 5 1 method name [] [M] +0 1 1 punctuation [] [(] +0 1 1 punctuation [] [)] +1 4 1 punctuation [] [{] +1 4 1 punctuation [] [}] +1 0 1 razorTransition [] [}] +2 0 1 razorTransition [] [@] +0 1 7 razorDirective [] [section] +0 8 9 local name [] [MySection] +0 10 1 razorTransition [] [{] +1 4 1 markupTagDelimiter [] [<] +0 1 3 markupElement [] [div] +0 3 1 markupTagDelimiter [] [>] +0 16 1 markupTagDelimiter [] [<] +0 1 1 markupTagDelimiter [] [/] +0 1 3 markupElement [] [div] +0 3 1 markupTagDelimiter [] [>] +1 0 1 razorTransition [] [}] diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/TestFiles/SemanticTokens/Legacy_with_background.txt b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/TestFiles/SemanticTokens/Legacy_with_background.txt new file mode 100644 index 00000000000..c2a10eb327f --- /dev/null +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/TestFiles/SemanticTokens/Legacy_with_background.txt @@ -0,0 +1,60 @@ +Line Δ, Char Δ, Length, Type, Modifier(s), Text +0 0 1 razorTransition [] [@] +0 1 4 razorDirective [] [page] +0 5 3 string [razorCode] ["/"] +1 0 1 razorTransition [] [@] +0 1 5 keyword [razorCode] [using] +0 5 1 markupTextLiteral [razorCode] [ ] +0 1 6 namespace name [razorCode] [System] +2 0 1 markupTagDelimiter [] [<] +0 1 3 markupElement [] [div] +0 3 1 markupTagDelimiter [] [>] +0 18 1 markupTagDelimiter [] [<] +0 1 1 markupTagDelimiter [] [/] +0 1 3 markupElement [] [div] +0 3 1 markupTagDelimiter [] [>] +2 0 1 markupTagDelimiter [] [<] +0 1 9 razorTagHelperElement [] [component] +0 10 4 razorTagHelperAttribute [] [type] +0 4 1 markupOperator [] [=] +0 1 1 markupAttributeQuote [] ["] +0 1 6 keyword [razorCode] [typeof] +0 6 1 punctuation [razorCode] [(] +0 1 9 property name [razorCode] [Component] +0 9 1 punctuation [razorCode] [)] +0 1 1 markupAttributeQuote [] ["] +0 2 11 razorTagHelperAttribute [] [render-mode] +0 11 1 markupOperator [] [=] +0 1 1 markupAttributeQuote [] ["] +0 1 17 enum member name [razorCode] [ServerPrerendered] +0 17 1 markupAttributeQuote [] ["] +0 2 1 markupTagDelimiter [] [/] +0 1 1 markupTagDelimiter [] [>] +2 0 1 razorTransition [] [@] +0 1 9 razorDirective [] [functions] +1 0 1 razorTransition [] [{] +1 0 4 markupTextLiteral [razorCode] [ ] +0 4 6 keyword [razorCode] [public] +0 6 1 markupTextLiteral [razorCode] [ ] +0 1 4 keyword [razorCode] [void] +0 4 1 markupTextLiteral [razorCode] [ ] +0 1 1 method name [razorCode] [M] +0 1 1 punctuation [razorCode] [(] +0 1 1 punctuation [razorCode] [)] +1 0 4 markupTextLiteral [razorCode] [ ] +0 4 1 punctuation [razorCode] [{] +1 0 4 markupTextLiteral [razorCode] [ ] +0 4 1 punctuation [razorCode] [}] +1 0 1 razorTransition [] [}] +2 0 1 razorTransition [] [@] +0 1 7 razorDirective [] [section] +0 8 9 local name [razorCode] [MySection] +0 10 1 razorTransition [] [{] +1 4 1 markupTagDelimiter [] [<] +0 1 3 markupElement [] [div] +0 3 1 markupTagDelimiter [] [>] +0 16 1 markupTagDelimiter [] [<] +0 1 1 markupTagDelimiter [] [/] +0 1 3 markupElement [] [div] +0 3 1 markupTagDelimiter [] [>] +1 0 1 razorTransition [] [}] diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/TestFiles/SemanticTokens/Razor.txt b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/TestFiles/SemanticTokens/Razor.txt new file mode 100644 index 00000000000..14db5713c1e --- /dev/null +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/TestFiles/SemanticTokens/Razor.txt @@ -0,0 +1,71 @@ +Line Δ, Char Δ, Length, Type, Modifier(s), Text +0 0 1 razorTransition [] [@] +0 1 4 razorDirective [] [page] +0 5 3 string [] ["/"] +1 0 1 razorTransition [] [@] +0 1 5 keyword [] [using] +0 6 6 namespace name [] [System] +2 0 1 markupTagDelimiter [] [<] +0 1 3 markupElement [] [div] +0 3 1 markupTagDelimiter [] [>] +0 18 1 markupTagDelimiter [] [<] +0 1 1 markupTagDelimiter [] [/] +0 1 3 markupElement [] [div] +0 3 1 markupTagDelimiter [] [>] +2 0 1 markupTagDelimiter [] [<] +0 1 9 razorComponentElement [] [InputText] +0 10 5 razorComponentAttribute [] [Value] +0 5 1 markupOperator [] [=] +0 1 1 markupAttributeQuote [] ["] +0 1 9 markupAttributeValue [] [someValue] +0 9 1 markupAttributeQuote [] ["] +0 2 1 markupTagDelimiter [] [/] +0 1 1 markupTagDelimiter [] [>] +2 0 1 razorCommentTransition [] [@] +0 1 1 razorCommentStar [] [*] +0 1 13 razorComment [] [ hello there ] +0 13 1 razorCommentStar [] [*] +0 1 1 razorCommentTransition [] [@] +1 0 4 markupCommentPunctuation [] [] +2 0 1 razorTransition [] [@] +0 1 2 keyword - control [] [if] +0 3 1 punctuation [] [(] +0 1 4 keyword [] [true] +0 4 1 punctuation [] [)] +1 0 1 punctuation [] [{] +1 4 6 razorDirective [] [] +0 11 7 razorDirective [] [] +1 0 1 punctuation [] [}] +2 0 1 razorTransition [] [@] +0 1 4 razorDirective [] [code] +1 0 1 razorTransition [] [{] +1 4 36 comment [] [// I am also good, thanks for asking] +2 4 2 comment [] [/*] +1 0 19 comment [] [ No problem.] +1 0 6 comment [] [ */] +2 4 7 keyword [] [private] +0 8 6 keyword [] [string] +0 7 9 field name [] [someValue] +0 9 1 punctuation [] [;] +2 4 6 keyword [] [public] +0 7 4 keyword [] [void] +0 5 1 method name [] [M] +0 1 1 punctuation [] [(] +0 1 1 punctuation [] [)] +1 4 1 punctuation [] [{] +1 8 14 delegate name [] [RenderFragment] +0 15 1 local name [] [x] +0 2 1 operator [] [=] +0 2 1 razorTransition [] [@] +0 1 1 markupTagDelimiter [] [<] +0 1 3 markupElement [] [div] +0 3 1 markupTagDelimiter [] [>] +0 39 1 markupTagDelimiter [] [<] +0 1 1 markupTagDelimiter [] [/] +0 1 3 markupElement [] [div] +0 3 1 markupTagDelimiter [] [>] +0 1 1 punctuation [] [;] +1 4 1 punctuation [] [}] +1 0 1 razorTransition [] [}] diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/TestFiles/SemanticTokens/Razor_with_background.txt b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/TestFiles/SemanticTokens/Razor_with_background.txt new file mode 100644 index 00000000000..59dc6ecd00f --- /dev/null +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/TestFiles/SemanticTokens/Razor_with_background.txt @@ -0,0 +1,86 @@ +Line Δ, Char Δ, Length, Type, Modifier(s), Text +0 0 1 razorTransition [] [@] +0 1 4 razorDirective [] [page] +0 5 3 string [razorCode] ["/"] +1 0 1 razorTransition [] [@] +0 1 5 keyword [razorCode] [using] +0 5 1 markupTextLiteral [razorCode] [ ] +0 1 6 namespace name [razorCode] [System] +2 0 1 markupTagDelimiter [] [<] +0 1 3 markupElement [] [div] +0 3 1 markupTagDelimiter [] [>] +0 18 1 markupTagDelimiter [] [<] +0 1 1 markupTagDelimiter [] [/] +0 1 3 markupElement [] [div] +0 3 1 markupTagDelimiter [] [>] +2 0 1 markupTagDelimiter [] [<] +0 1 9 razorComponentElement [] [InputText] +0 10 5 razorComponentAttribute [] [Value] +0 5 1 markupOperator [] [=] +0 1 1 markupAttributeQuote [] ["] +0 1 9 markupAttributeValue [] [someValue] +0 9 1 markupAttributeQuote [] ["] +0 2 1 markupTagDelimiter [] [/] +0 1 1 markupTagDelimiter [] [>] +2 0 1 razorCommentTransition [] [@] +0 1 1 razorCommentStar [] [*] +0 1 13 razorComment [] [ hello there ] +0 13 1 razorCommentStar [] [*] +0 1 1 razorCommentTransition [] [@] +1 0 4 markupCommentPunctuation [] [] +2 0 1 razorTransition [razorCode] [@] +0 1 2 keyword - control [razorCode] [if] +0 2 1 markupTextLiteral [razorCode] [ ] +0 1 1 punctuation [razorCode] [(] +0 1 4 keyword [razorCode] [true] +0 4 1 punctuation [razorCode] [)] +1 0 1 punctuation [razorCode] [{] +1 4 6 razorDirective [] [] +0 11 7 razorDirective [] [] +1 0 1 punctuation [razorCode] [}] +2 0 1 razorTransition [] [@] +0 1 4 razorDirective [] [code] +1 0 1 razorTransition [] [{] +1 0 4 markupTextLiteral [razorCode] [ ] +0 4 36 comment [razorCode] [// I am also good, thanks for asking] +2 0 4 markupTextLiteral [razorCode] [ ] +0 4 2 comment [razorCode] [/*] +1 0 19 comment [razorCode] [ No problem.] +1 0 6 comment [razorCode] [ */] +2 0 4 markupTextLiteral [razorCode] [ ] +0 4 7 keyword [razorCode] [private] +0 7 1 markupTextLiteral [razorCode] [ ] +0 1 6 keyword [razorCode] [string] +0 6 1 markupTextLiteral [razorCode] [ ] +0 1 9 field name [razorCode] [someValue] +0 9 1 punctuation [razorCode] [;] +2 0 4 markupTextLiteral [razorCode] [ ] +0 4 6 keyword [razorCode] [public] +0 6 1 markupTextLiteral [razorCode] [ ] +0 1 4 keyword [razorCode] [void] +0 4 1 markupTextLiteral [razorCode] [ ] +0 1 1 method name [razorCode] [M] +0 1 1 punctuation [razorCode] [(] +0 1 1 punctuation [razorCode] [)] +1 0 4 markupTextLiteral [razorCode] [ ] +0 4 1 punctuation [razorCode] [{] +1 0 8 markupTextLiteral [razorCode] [ ] +0 8 14 delegate name [razorCode] [RenderFragment] +0 14 1 markupTextLiteral [razorCode] [ ] +0 1 1 local name [razorCode] [x] +0 1 1 markupTextLiteral [razorCode] [ ] +0 1 1 operator [razorCode] [=] +0 2 1 razorTransition [razorCode] [@] +0 1 1 markupTagDelimiter [] [<] +0 1 3 markupElement [] [div] +0 3 1 markupTagDelimiter [] [>] +0 39 1 markupTagDelimiter [] [<] +0 1 1 markupTagDelimiter [] [/] +0 1 3 markupElement [] [div] +0 3 1 markupTagDelimiter [] [>] +0 1 1 punctuation [razorCode] [;] +1 0 4 markupTextLiteral [razorCode] [ ] +0 4 1 punctuation [razorCode] [}] +1 0 1 razorTransition [] [}] From 7a7998fd6605a4c2079b0fcf7c0ea07852f19228 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 15 Jul 2024 17:06:11 +1000 Subject: [PATCH 3/7] =?UTF-8?q?Fix=20test=20data=20=F0=9F=A4=A6=E2=80=8D?= =?UTF-8?q?=E2=99=82=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TestProjectData.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/TestProjectData.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/TestProjectData.cs index 181b115054e..15100f19680 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/TestProjectData.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/TestProjectData.cs @@ -25,10 +25,10 @@ static TestProjectData() SomeProject = new HostProject(Path.Combine(someProjectPath, "SomeProject.csproj"), someProjectObjPath, RazorConfiguration.Default, "SomeProject"); SomeProjectFile1 = new HostDocument(Path.Combine(someProjectPath, "File1.cshtml"), "File1.cshtml", FileKinds.Legacy); SomeProjectFile2 = new HostDocument(Path.Combine(someProjectPath, "File2.cshtml"), "File2.cshtml", FileKinds.Legacy); - SomeProjectImportFile = new HostDocument(Path.Combine(someProjectPath, "_Imports.cshtml"), "_Imports.cshtml", FileKinds.Legacy); + SomeProjectImportFile = new HostDocument(Path.Combine(someProjectPath, "_ViewImports.cshtml"), "_ViewImports.cshtml", FileKinds.Legacy); SomeProjectNestedFile3 = new HostDocument(Path.Combine(someProjectPath, "Nested", "File3.cshtml"), "Nested\\File3.cshtml", FileKinds.Legacy); SomeProjectNestedFile4 = new HostDocument(Path.Combine(someProjectPath, "Nested", "File4.cshtml"), "Nested\\File4.cshtml", FileKinds.Legacy); - SomeProjectNestedImportFile = new HostDocument(Path.Combine(someProjectPath, "Nested", "_Imports.cshtml"), "Nested\\_Imports.cshtml", FileKinds.Legacy); + SomeProjectNestedImportFile = new HostDocument(Path.Combine(someProjectPath, "Nested", "_ViewImports.cshtml"), "Nested\\_ViewImports.cshtml", FileKinds.Legacy); SomeProjectComponentFile1 = new HostDocument(Path.Combine(someProjectPath, "File1.razor"), "File1.razor", FileKinds.Component); SomeProjectComponentFile2 = new HostDocument(Path.Combine(someProjectPath, "File2.razor"), "File2.razor", FileKinds.Component); SomeProjectComponentImportFile1 = new HostDocument(Path.Combine(someProjectPath, "_Imports.razor"), "_Imports.razor", FileKinds.Component); @@ -42,10 +42,10 @@ static TestProjectData() AnotherProject = new HostProject(Path.Combine(anotherProjectPath, "AnotherProject.csproj"), anotherProjectObjPath, RazorConfiguration.Default, "AnotherProject"); AnotherProjectFile1 = new HostDocument(Path.Combine(anotherProjectPath, "File1.cshtml"), "File1.cshtml", FileKinds.Legacy); AnotherProjectFile2 = new HostDocument(Path.Combine(anotherProjectPath, "File2.cshtml"), "File2.cshtml", FileKinds.Legacy); - AnotherProjectImportFile = new HostDocument(Path.Combine(anotherProjectPath, "_Imports.cshtml"), "_Imports.cshtml", FileKinds.Legacy); + AnotherProjectImportFile = new HostDocument(Path.Combine(anotherProjectPath, "_ViewImports.cshtml"), "_ViewImports.cshtml", FileKinds.Legacy); AnotherProjectNestedFile3 = new HostDocument(Path.Combine(anotherProjectPath, "Nested", "File3.cshtml"), "Nested\\File1.cshtml", FileKinds.Legacy); AnotherProjectNestedFile4 = new HostDocument(Path.Combine(anotherProjectPath, "Nested", "File4.cshtml"), "Nested\\File2.cshtml", FileKinds.Legacy); - AnotherProjectNestedImportFile = new HostDocument(Path.Combine(anotherProjectPath, "Nested", "_Imports.cshtml"), "Nested\\_Imports.cshtml", FileKinds.Legacy); + AnotherProjectNestedImportFile = new HostDocument(Path.Combine(anotherProjectPath, "Nested", "_ViewImports.cshtml"), "Nested\\_ViewImports.cshtml", FileKinds.Legacy); AnotherProjectComponentFile1 = new HostDocument(Path.Combine(anotherProjectPath, "File1.razor"), "File1.razor", FileKinds.Component); AnotherProjectComponentFile2 = new HostDocument(Path.Combine(anotherProjectPath, "File2.razor"), "File2.razor", FileKinds.Component); AnotherProjectNestedComponentFile3 = new HostDocument(Path.Combine(anotherProjectPath, "Nested", "File3.razor"), "Nested\\File1.razor", FileKinds.Component); From a96e5f1e957a102076c3cafd0c76e855b3e7ff78 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 15 Jul 2024 17:18:51 +1000 Subject: [PATCH 4/7] =?UTF-8?q?Fix=20bad=20tests=20that=20relied=20on=20pr?= =?UTF-8?q?eviously=20bad=20test=20data=20=F0=9F=A4=A6=E2=80=8D=E2=99=82?= =?UTF-8?q?=EF=B8=8F=F0=9F=A4=A6=E2=80=8D=E2=99=82=EF=B8=8F=F0=9F=A4=A6?= =?UTF-8?q?=E2=80=8D=E2=99=82=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TestImportProjectFeature.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/TestImportProjectFeature.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/TestImportProjectFeature.cs index ccb8d36bb09..92949eb6d03 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/TestImportProjectFeature.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/TestImportProjectFeature.cs @@ -26,7 +26,7 @@ public IReadOnlyList GetImports(RazorProjectItem projectItem) private void AddHierarchicalImports(RazorProjectItem projectItem, List imports) { // We want items in descending order. FindHierarchicalItems returns items in ascending order. - var importProjectItems = ProjectEngine.FileSystem.FindHierarchicalItems(projectItem.FilePath, "_Imports.cshtml").Reverse(); + var importProjectItems = ProjectEngine.FileSystem.FindHierarchicalItems(projectItem.FilePath, "_ViewImports.cshtml").Reverse(); imports.AddRange(importProjectItems); } } From cbe071b93b0646ff9b074d18f0da55d384a1fa71 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 16 Jul 2024 11:59:04 +1000 Subject: [PATCH 5/7] Remvoe statics from intiailization services --- .../RemoteClientInitializationService.cs | 11 +++++++--- ...emoteClientInitializationServiceFactory.cs | 5 ++++- .../RemoteLanguageServerFeatureOptions.cs | 16 +++++++-------- .../RemoteSemanticTokensLegendService.cs | 20 ++++++++++--------- .../CohostSemanticTokensRangeEndpointTest.cs | 3 ++- .../Cohost/CohostTestBase.cs | 14 ++++++++++--- 6 files changed, 44 insertions(+), 25 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/RemoteClientInitializationService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/RemoteClientInitializationService.cs index d5d316c971e..464d33a2fd7 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/RemoteClientInitializationService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/RemoteClientInitializationService.cs @@ -9,13 +9,18 @@ namespace Microsoft.CodeAnalysis.Remote.Razor; internal sealed class RemoteClientInitializationService( - IRazorServiceBroker serviceBroker) + IRazorServiceBroker serviceBroker, + RemoteLanguageServerFeatureOptions remoteLanguageServerFeatureOptions, + RemoteSemanticTokensLegendService remoteSemanticTokensLegendService) : RazorServiceBase(serviceBroker), IRemoteClientInitializationService { + private readonly RemoteLanguageServerFeatureOptions _remoteLanguageServerFeatureOptions = remoteLanguageServerFeatureOptions; + private readonly RemoteSemanticTokensLegendService _remoteSemanticTokensLegendService = remoteSemanticTokensLegendService; + public ValueTask InitializeAsync(RemoteClientInitializationOptions options, CancellationToken cancellationToken) => RunServiceAsync(ct => { - RemoteLanguageServerFeatureOptions.SetOptions(options); + _remoteLanguageServerFeatureOptions.SetOptions(options); return default; }, cancellationToken); @@ -23,7 +28,7 @@ public ValueTask InitializeAsync(RemoteClientInitializationOptions options, Canc public ValueTask InitializeLSPAsync(RemoteClientLSPInitializationOptions options, CancellationToken cancellationToken) => RunServiceAsync(ct => { - RemoteSemanticTokensLegendService.SetLegend(options.TokenTypes, options.TokenModifiers); + _remoteSemanticTokensLegendService.SetLegend(options.TokenTypes, options.TokenModifiers); return default; }, cancellationToken); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/RemoteClientInitializationServiceFactory.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/RemoteClientInitializationServiceFactory.cs index c1cb47432a3..f8643fbdeab 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/RemoteClientInitializationServiceFactory.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/RemoteClientInitializationServiceFactory.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using Microsoft.CodeAnalysis.Razor.Remote; +using Microsoft.CodeAnalysis.Remote.Razor.SemanticTokens; using Microsoft.VisualStudio.Composition; namespace Microsoft.CodeAnalysis.Remote.Razor; @@ -16,6 +17,8 @@ public RemoteClientInitializationServiceFactory() protected override IRemoteClientInitializationService CreateService(IRazorServiceBroker serviceBroker, ExportProvider exportProvider) { - return new RemoteClientInitializationService(serviceBroker); + var languageServerFeatureOptions = exportProvider.GetExportedValue(); + var semanticTokensLegendService = exportProvider.GetExportedValue(); + return new RemoteClientInitializationService(serviceBroker, languageServerFeatureOptions, semanticTokensLegendService); } } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/RemoteLanguageServerFeatureOptions.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/RemoteLanguageServerFeatureOptions.cs index 37286a8c168..45cdae212de 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/RemoteLanguageServerFeatureOptions.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/RemoteLanguageServerFeatureOptions.cs @@ -10,24 +10,24 @@ namespace Microsoft.CodeAnalysis.Remote.Razor; [Shared] [Export(typeof(LanguageServerFeatureOptions))] +[Export(typeof(RemoteLanguageServerFeatureOptions))] internal class RemoteLanguageServerFeatureOptions : LanguageServerFeatureOptions { - // It's okay to use default here because we expect the options to be set before the first real OOP call - private static RemoteClientInitializationOptions s_options = default; + private RemoteClientInitializationOptions _options = default; - public static void SetOptions(RemoteClientInitializationOptions options) => s_options = options; + public void SetOptions(RemoteClientInitializationOptions options) => _options = options; public override bool SupportsFileManipulation => throw new InvalidOperationException("This option has not been synced to OOP."); - public override string CSharpVirtualDocumentSuffix => s_options.CSharpVirtualDocumentSuffix; + public override string CSharpVirtualDocumentSuffix => _options.CSharpVirtualDocumentSuffix; - public override string HtmlVirtualDocumentSuffix => s_options.HtmlVirtualDocumentSuffix; + public override string HtmlVirtualDocumentSuffix => _options.HtmlVirtualDocumentSuffix; public override bool SingleServerSupport => throw new InvalidOperationException("This option has not been synced to OOP."); public override bool DelegateToCSharpOnDiagnosticPublish => throw new InvalidOperationException("This option has not been synced to OOP."); - public override bool UsePreciseSemanticTokenRanges => s_options.UsePreciseSemanticTokenRanges; + public override bool UsePreciseSemanticTokenRanges => _options.UsePreciseSemanticTokenRanges; public override bool ShowAllCSharpCodeActions => throw new InvalidOperationException("This option has not been synced to OOP."); @@ -35,9 +35,9 @@ internal class RemoteLanguageServerFeatureOptions : LanguageServerFeatureOptions public override bool ReturnCodeActionAndRenamePathsWithPrefixedSlash => throw new InvalidOperationException("This option has not been synced to OOP."); - public override bool IncludeProjectKeyInGeneratedFilePath => s_options.IncludeProjectKeyInGeneratedFilePath; + public override bool IncludeProjectKeyInGeneratedFilePath => _options.IncludeProjectKeyInGeneratedFilePath; - public override bool UseRazorCohostServer => s_options.UseRazorCohostServer; + public override bool UseRazorCohostServer => _options.UseRazorCohostServer; public override bool DisableRazorLanguageServer => throw new InvalidOperationException("This option has not been synced to OOP."); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SemanticTokens/RemoteSemanticTokensLegendService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SemanticTokens/RemoteSemanticTokensLegendService.cs index f5175d0ad08..d5c3b6d2f54 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SemanticTokens/RemoteSemanticTokensLegendService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SemanticTokens/RemoteSemanticTokensLegendService.cs @@ -6,22 +6,24 @@ namespace Microsoft.CodeAnalysis.Remote.Razor.SemanticTokens; -[Export(typeof(ISemanticTokensLegendService)), Shared] +[Shared] +[Export(typeof(ISemanticTokensLegendService))] +[Export(typeof(RemoteSemanticTokensLegendService))] internal sealed class RemoteSemanticTokensLegendService : ISemanticTokensLegendService { - private static SemanticTokenModifiers s_tokenModifiers = null!; - private static SemanticTokenTypes s_tokenTypes = null!; + private SemanticTokenModifiers _tokenModifiers = null!; + private SemanticTokenTypes _tokenTypes = null!; - public SemanticTokenModifiers TokenModifiers => s_tokenModifiers; + public SemanticTokenModifiers TokenModifiers => _tokenModifiers; - public SemanticTokenTypes TokenTypes => s_tokenTypes; + public SemanticTokenTypes TokenTypes => _tokenTypes; - public static void SetLegend(string[] tokenTypes, string[] tokenModifiers) + public void SetLegend(string[] tokenTypes, string[] tokenModifiers) { - if (s_tokenTypes is null) + if (_tokenTypes is null) { - s_tokenTypes = new SemanticTokenTypes(tokenTypes); - s_tokenModifiers = new SemanticTokenModifiers(tokenModifiers); + _tokenTypes = new SemanticTokenTypes(tokenTypes); + _tokenModifiers = new SemanticTokenModifiers(tokenModifiers); } } } diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostSemanticTokensRangeEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostSemanticTokensRangeEndpointTest.cs index 55f80474f43..1a5bcfa4cc6 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostSemanticTokensRangeEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostSemanticTokensRangeEndpointTest.cs @@ -97,7 +97,8 @@ private async Task VerifySemanticTokensAsync(string input, bool colorBackground, var legend = TestRazorSemanticTokensLegendService.Instance; // We need to manually initialize the OOP service so we can get semantic token info later - RemoteSemanticTokensLegendService.SetLegend(legend.TokenTypes.All, legend.TokenModifiers.All); + var legendService = OOPExportProvider.GetExportedValue(); + legendService.SetLegend(legend.TokenTypes.All, legend.TokenModifiers.All); var clientSettingsManager = new ClientSettingsManager([], null, null); clientSettingsManager.Update(ClientAdvancedSettings.Default with { ColorBackground = colorBackground }); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTestBase.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTestBase.cs index 614097f847e..505b3565b3d 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTestBase.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTestBase.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.Razor.Remote; using Microsoft.CodeAnalysis.Remote.Razor; using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Composition; using Xunit.Abstractions; namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost; @@ -21,17 +22,24 @@ public abstract class CohostTestBase(ITestOutputHelper testOutputHelper) : Works { private const string CSharpVirtualDocumentSuffix = ".g.cs"; private IRemoteServiceProvider? _remoteServiceProvider; + private ExportProvider? _exportProvider; private protected IRemoteServiceProvider RemoteServiceProvider => _remoteServiceProvider.AssumeNotNull(); + /// + /// The export provider for Razor OOP services (not Roslyn) + /// + private protected ExportProvider OOPExportProvider => _exportProvider.AssumeNotNull(); + protected override async Task InitializeAsync() { await base.InitializeAsync(); - var exportProvider = AddDisposable(await RemoteMefComposition.CreateExportProviderAsync()); - _remoteServiceProvider = AddDisposable(new TestRemoteServiceProvider(exportProvider)); + _exportProvider = AddDisposable(await RemoteMefComposition.CreateExportProviderAsync()); + _remoteServiceProvider = AddDisposable(new TestRemoteServiceProvider(_exportProvider)); - RemoteLanguageServerFeatureOptions.SetOptions(new() + var featureOptions = OOPExportProvider.GetExportedValue(); + featureOptions.SetOptions(new() { CSharpVirtualDocumentSuffix = CSharpVirtualDocumentSuffix, HtmlVirtualDocumentSuffix = ".g.html", From 734c8a5821a379c1f1da4dd69f774e279e7a2573 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 16 Jul 2024 12:03:38 +1000 Subject: [PATCH 6/7] Test precise semantic tokens too --- .../CohostSemanticTokensRangeEndpointTest.cs | 14 +++++++++----- .../Cohost/CohostTestBase.cs | 7 +++++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostSemanticTokensRangeEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostSemanticTokensRangeEndpointTest.cs index 1a5bcfa4cc6..02d79678d91 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostSemanticTokensRangeEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostSemanticTokensRangeEndpointTest.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Razor.Telemetry; using Microsoft.CodeAnalysis.Razor.Settings; using Microsoft.CodeAnalysis.Razor.Workspaces; +using Microsoft.CodeAnalysis.Remote.Razor; using Microsoft.CodeAnalysis.Remote.Razor.SemanticTokens; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Razor.Settings; @@ -24,7 +25,7 @@ public class CohostSemanticTokensRangeEndpointTest(ITestOutputHelper testOutputH { [Theory] [CombinatorialData] - public async Task Razor(bool colorBackground) + public async Task Razor(bool colorBackground, bool precise) { var input = """ @page "/" @@ -59,12 +60,12 @@ public void M() } """; - await VerifySemanticTokensAsync(input, colorBackground); + await VerifySemanticTokensAsync(input, colorBackground, precise); } [Theory] [CombinatorialData] - public async Task Legacy(bool colorBackground) + public async Task Legacy(bool colorBackground, bool precise) { var input = """ @page "/" @@ -86,10 +87,10 @@ @section MySection { } """; - await VerifySemanticTokensAsync(input, colorBackground, fileKind: FileKinds.Legacy); + await VerifySemanticTokensAsync(input, colorBackground, precise, fileKind: FileKinds.Legacy); } - private async Task VerifySemanticTokensAsync(string input, bool colorBackground, string? fileKind = null, [CallerMemberName] string? testName = null) + private async Task VerifySemanticTokensAsync(string input, bool colorBackground, bool precise, string? fileKind = null, [CallerMemberName] string? testName = null) { var document = CreateProjectAndRazorDocument(input, fileKind); var sourceText = await document.GetTextAsync(DisposalToken); @@ -100,6 +101,9 @@ private async Task VerifySemanticTokensAsync(string input, bool colorBackground, var legendService = OOPExportProvider.GetExportedValue(); legendService.SetLegend(legend.TokenTypes.All, legend.TokenModifiers.All); + var featureOptions = OOPExportProvider.GetExportedValue(); + featureOptions.SetOptions(_clientInitializationOptions with { UsePreciseSemanticTokenRanges = precise }); + var clientSettingsManager = new ClientSettingsManager([], null, null); clientSettingsManager.Update(ClientAdvancedSettings.Default with { ColorBackground = colorBackground }); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTestBase.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTestBase.cs index 505b3565b3d..60dd89e4dc1 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTestBase.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTestBase.cs @@ -23,6 +23,7 @@ public abstract class CohostTestBase(ITestOutputHelper testOutputHelper) : Works private const string CSharpVirtualDocumentSuffix = ".g.cs"; private IRemoteServiceProvider? _remoteServiceProvider; private ExportProvider? _exportProvider; + private protected RemoteClientInitializationOptions _clientInitializationOptions; private protected IRemoteServiceProvider RemoteServiceProvider => _remoteServiceProvider.AssumeNotNull(); @@ -39,14 +40,16 @@ protected override async Task InitializeAsync() _remoteServiceProvider = AddDisposable(new TestRemoteServiceProvider(_exportProvider)); var featureOptions = OOPExportProvider.GetExportedValue(); - featureOptions.SetOptions(new() + _clientInitializationOptions = new() { CSharpVirtualDocumentSuffix = CSharpVirtualDocumentSuffix, HtmlVirtualDocumentSuffix = ".g.html", IncludeProjectKeyInGeneratedFilePath = false, UsePreciseSemanticTokenRanges = false, UseRazorCohostServer = true - }); + }; + + featureOptions.SetOptions(_clientInitializationOptions); } protected TextDocument CreateProjectAndRazorDocument(string contents, string? fileKind = null) From 55fee1ecb73baa230c869dcb9a64cef7d7d7e0d0 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 17 Jul 2024 07:42:52 +1000 Subject: [PATCH 7/7] Post merge updates, and rejig of client initialization options --- .../CohostSemanticTokensRangeEndpointTest.cs | 6 +++--- .../Cohost/CohostTestBase.cs | 17 ++++++++++++----- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostSemanticTokensRangeEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostSemanticTokensRangeEndpointTest.cs index 02d79678d91..760defdd871 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostSemanticTokensRangeEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostSemanticTokensRangeEndpointTest.cs @@ -101,13 +101,13 @@ private async Task VerifySemanticTokensAsync(string input, bool colorBackground, var legendService = OOPExportProvider.GetExportedValue(); legendService.SetLegend(legend.TokenTypes.All, legend.TokenModifiers.All); - var featureOptions = OOPExportProvider.GetExportedValue(); - featureOptions.SetOptions(_clientInitializationOptions with { UsePreciseSemanticTokenRanges = precise }); + // Update the client initialization options to control the precise ranges option + UpdateClientInitializationOptions(c => c with { UsePreciseSemanticTokenRanges = precise }); var clientSettingsManager = new ClientSettingsManager([], null, null); clientSettingsManager.Update(ClientAdvancedSettings.Default with { ColorBackground = colorBackground }); - var endpoint = new CohostSemanticTokensRangeEndpoint(RemoteServiceProvider, clientSettingsManager, legend, NoOpTelemetryReporter.Instance, LoggerFactory); + var endpoint = new CohostSemanticTokensRangeEndpoint(RemoteServiceInvoker, clientSettingsManager, legend, NoOpTelemetryReporter.Instance, LoggerFactory); var span = new LinePositionSpan(new(0, 0), new(sourceText.Lines.Count, 0)); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTestBase.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTestBase.cs index 139a7fa3e5b..47cd7bd476a 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTestBase.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTestBase.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. +using System; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -10,6 +11,7 @@ using Microsoft.AspNetCore.Razor.Test.Common; using Microsoft.AspNetCore.Razor.Test.Common.Workspaces; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Razor.Remote; using Microsoft.CodeAnalysis.Remote.Razor; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Composition; @@ -22,8 +24,8 @@ public abstract class CohostTestBase(ITestOutputHelper testOutputHelper) : Works private const string CSharpVirtualDocumentSuffix = ".g.cs"; private ExportProvider? _exportProvider; private TestRemoteServiceInvoker? _remoteServiceInvoker; + private RemoteClientInitializationOptions _clientInitializationOptions; - private protected RemoteClientInitializationOptions _clientInitializationOptions; private protected TestRemoteServiceInvoker RemoteServiceInvoker => _remoteServiceInvoker.AssumeNotNull(); /// @@ -37,13 +39,12 @@ protected override async Task InitializeAsync() // Create a new isolated MEF composition. // Note that this uses a cached catalog and configuration for performance. - var exportProvider = await RemoteMefComposition.CreateExportProviderAsync(DisposalToken); - AddDisposable(exportProvider); + _exportProvider = await RemoteMefComposition.CreateExportProviderAsync(DisposalToken); + AddDisposable(_exportProvider); - _remoteServiceInvoker = new TestRemoteServiceInvoker(JoinableTaskContext, exportProvider, LoggerFactory); + _remoteServiceInvoker = new TestRemoteServiceInvoker(JoinableTaskContext, _exportProvider, LoggerFactory); AddDisposable(_remoteServiceInvoker); - var featureOptions = OOPExportProvider.GetExportedValue(); _clientInitializationOptions = new() { CSharpVirtualDocumentSuffix = CSharpVirtualDocumentSuffix, @@ -52,7 +53,13 @@ protected override async Task InitializeAsync() UsePreciseSemanticTokenRanges = false, UseRazorCohostServer = true }; + UpdateClientInitializationOptions(c => c); + } + private protected void UpdateClientInitializationOptions(Func mutation) + { + _clientInitializationOptions = mutation(_clientInitializationOptions); + var featureOptions = OOPExportProvider.GetExportedValue(); featureOptions.SetOptions(_clientInitializationOptions); }