diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlayHints/InlayHintService.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlayHints/InlayHintService.cs index 60b405b5faa..1e93b44046e 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlayHints/InlayHintService.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlayHints/InlayHintService.cs @@ -29,6 +29,16 @@ internal sealed class InlayHintService(IDocumentMappingService documentMappingSe var span = range.ToLinePositionSpan(); + cancellationToken.ThrowIfCancellationRequested(); + + // Sometimes the client sends us a request that doesn't match the file contents. Could be a bug with old requests + // not being cancelled, but no harm in being defensive + if (!codeDocument.Source.Text.TryGetAbsoluteIndex(span.Start, out var startIndex) || + !codeDocument.Source.Text.TryGetAbsoluteIndex(span.End, out var endIndex)) + { + return null; + } + // We are given a range by the client, but our mapping only succeeds if the start and end of the range can both be mapped // to C#. Since that doesn't logically match what we want from inlay hints, we instead get the minimum range of mappable // C# to get hints for. We'll filter that later, to remove the sections that can't be mapped back. diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/InlayHints/RemoteInlayHintService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/InlayHints/RemoteInlayHintService.cs index 1dc9c7fcef4..ff8d90753a7 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/InlayHints/RemoteInlayHintService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/InlayHints/RemoteInlayHintService.cs @@ -41,6 +41,16 @@ protected override IRemoteInlayHintService CreateService(in ServiceArgs args) var span = inlayHintParams.Range.ToLinePositionSpan(); + cancellationToken.ThrowIfCancellationRequested(); + + // Sometimes the client sends us a request that doesn't match the file contents. Could be a bug with old requests + // not being cancelled, but no harm in being defensive + if (!codeDocument.Source.Text.TryGetAbsoluteIndex(span.Start, out var startIndex) || + !codeDocument.Source.Text.TryGetAbsoluteIndex(span.End, out var endIndex)) + { + return null; + } + // We are given a range by the client, but our mapping only succeeds if the start and end of the range can both be mapped // to C#. Since that doesn't logically match what we want from inlay hints, we instead get the minimum range of mappable // C# to get hints for. We'll filter that later, to remove the sections that can't be mapped back. diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/InlayHints/InlayHintEndpointTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/InlayHints/InlayHintEndpointTest.cs index 9b964c0898a..285635a557a 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/InlayHints/InlayHintEndpointTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/InlayHints/InlayHintEndpointTest.cs @@ -73,8 +73,8 @@ public Task InlayHints_ComponentAttributes() """, toolTipMap: new Dictionary - { - }, + { + }, output: """
@@ -85,6 +85,42 @@ public Task InlayHints_ComponentAttributes() """); + [Theory] + [InlineData(0, 0, 0, 20)] + [InlineData(0, 0, 2, 0)] + [InlineData(2, 0, 4, 0)] + public async Task InlayHints_InvalidRange(int startLine, int starChar, int endLine, int endChar) + { + var input = """ +
+ """; + var razorFilePath = "C:/path/to/file.razor"; + var codeDocument = CreateCodeDocument(input, filePath: razorFilePath); + + var languageServer = await CreateLanguageServerAsync(codeDocument, razorFilePath); + + var service = new InlayHintService(DocumentMappingService); + + var endpoint = new InlayHintEndpoint(service, languageServer); + + var request = new InlayHintParams() + { + TextDocument = new VSTextDocumentIdentifier + { + Uri = new Uri(razorFilePath) + }, + Range = VsLspFactory.CreateRange(startLine, starChar, endLine, endChar) + }; + Assert.True(DocumentContextFactory.TryCreate(request.TextDocument, out var documentContext)); + var requestContext = CreateRazorRequestContext(documentContext); + + // Act + var hints = await endpoint.HandleRequestAsync(request, requestContext, DisposalToken); + + // Assert + Assert.Null(hints); + } + private async Task VerifyInlayHintsAsync(string input, Dictionary toolTipMap, string output) { TestFileMarkupParser.GetSpans(input, out input, out ImmutableDictionary> spansDict); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostInlayHintEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostInlayHintEndpointTest.cs index 05c9b4486ec..1b81b87ef39 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostInlayHintEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostInlayHintEndpointTest.cs @@ -128,6 +128,30 @@ public Task InlayHints_ComponentAttributes() """); + [Theory] + [InlineData(0, 0, 0, 20)] + [InlineData(0, 0, 2, 0)] + [InlineData(2, 0, 4, 0)] + public async Task InlayHints_InvalidRange(int startLine, int starChar, int endLine, int endChar) + { + var input = """ +
+ """; + var document = await CreateProjectAndRazorDocumentAsync(input); + var endpoint = new CohostInlayHintEndpoint(RemoteServiceInvoker); + + var request = new InlayHintParams() + { + TextDocument = new TextDocumentIdentifier() { Uri = document.CreateUri() }, + Range = RoslynLspFactory.CreateRange(startLine, starChar, endLine, endChar) + }; + + var hints = await endpoint.GetTestAccessor().HandleRequestAsync(request, document, displayAllOverride: false, DisposalToken); + + // Assert + Assert.Null(hints); + } + private async Task VerifyInlayHintsAsync(string input, Dictionary toolTipMap, string output, bool displayAllOverride = false) { TestFileMarkupParser.GetSpans(input, out input, out ImmutableDictionary> spansDict);