Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 12 additions & 29 deletions src/Features/Core/Portable/CodeLens/CodeLensReferencesService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
Expand Down Expand Up @@ -224,14 +223,21 @@ public async Task<ImmutableArray<ReferenceLocationDescriptor>> MapReferenceLocat

var descriptor = descriptorAndDocument.Descriptor;
var span = new TextSpan(descriptor.SpanStart, descriptor.SpanLength);
var results = await SpanMappingHelper.TryGetMappedSpanResultAsync(document, [span], cancellationToken).ConfigureAwait(false);
if (results is null)

if (!SpanMappingHelper.CanMapSpans(document))
{
// for normal document, just add one as they are
list.Add(descriptor);
continue;
}

var results = await SpanMappingHelper.TryGetMappedSpanResultAsync(document, [span], cancellationToken).ConfigureAwait(false);
if (results is null)
{
// If the document can map spans, but this span wasn't mapped, drop the result
continue;
}

var mappedSpans = results.GetValueOrDefault();

// external component violated contracts. the mapper should preserve input order/count.
Expand All @@ -246,36 +252,13 @@ public async Task<ImmutableArray<ReferenceLocationDescriptor>> MapReferenceLocat
continue;
}

var excerpter = document.DocumentServiceProvider.GetService<IDocumentExcerptService>();
if (excerpter == null)
if (!DocumentExcerptHelper.CanExcerpt(document))
{
if (document.IsRazorSourceGeneratedDocument())
{
// HACK: Razor doesn't have has a workspace level excerpt service, but if we just return a simple descriptor here,
// the user at least sees something, can navigate, and Razor can improve this later if necessary. Until
// https://github.com/dotnet/roslyn/issues/79699 is fixed this won't get hit anyway.
list.Add(new ReferenceLocationDescriptor(
descriptor.LongDescription,
descriptor.Language,
descriptor.Glyph,
result.Span.Start,
result.Span.Length,
result.LinePositionSpan.Start.Line,
result.LinePositionSpan.Start.Character,
result.FilePath,
descriptor.ReferenceLineText,
descriptor.ReferenceStart,
descriptor.ReferenceLength,
"",
"",
"",
""));
}
continue;
}

var referenceExcerpt = await excerpter.TryExcerptAsync(document, span, ExcerptMode.SingleLine, classificationOptions, cancellationToken).ConfigureAwait(false);
var tooltipExcerpt = await excerpter.TryExcerptAsync(document, span, ExcerptMode.Tooltip, classificationOptions, cancellationToken).ConfigureAwait(false);
var referenceExcerpt = await DocumentExcerptHelper.TryExcerptAsync(document, span, ExcerptMode.SingleLine, classificationOptions, cancellationToken).ConfigureAwait(false);
var tooltipExcerpt = await DocumentExcerptHelper.TryExcerptAsync(document, span, ExcerptMode.Tooltip, classificationOptions, cancellationToken).ConfigureAwait(false);

var (text, start, length) = GetReferenceInfo(referenceExcerpt, descriptor);
var (before1, before2, after1, after2) = GetReferenceTexts(referenceExcerpt, tooltipExcerpt, descriptor);
Expand Down
10 changes: 7 additions & 3 deletions src/Features/Core/Portable/FindUsages/DefinitionItemFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
using Microsoft.CodeAnalysis.Features.RQName;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.FindSymbols.Finders;
using Microsoft.CodeAnalysis.Shared.Collections;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;

Expand Down Expand Up @@ -244,8 +244,8 @@ private static async Task<ImmutableArray<DocumentSpan>> GetSourceLocationsAsync(
foreach (var location in locations)
{
if (location.IsInSource &&
(includeHiddenLocations || location.IsVisibleSourceLocation()) &&
solution.GetDocument(location.SourceTree) is { } document)
solution.GetDocument(location.SourceTree) is { } document &&
(includeHiddenLocations || document.IsRazorSourceGeneratedDocument() || location.IsVisibleSourceLocation()))
{
var isGeneratedCode = await document.IsGeneratedCodeAsync(cancellationToken).ConfigureAwait(false);
source.Add(new DocumentSpan(document, location.SourceSpan, isGeneratedCode));
Expand Down Expand Up @@ -300,6 +300,10 @@ private static ImmutableDictionary<string, string> GetProperties(ISymbol definit
{
var location = referenceLocation.Location;

// Razor has a mapping service that can map from hidden locations, or will drop results if it wants to,
// so hidden locations aren't a problem, and are actually desirable.
includeHiddenLocations |= referenceLocation.Document.IsRazorSourceGeneratedDocument();

Debug.Assert(location.IsInSource);
if (!location.IsVisibleSourceLocation() &&
!includeHiddenLocations)
Expand Down
55 changes: 29 additions & 26 deletions src/LanguageServer/Protocol/Extensions/ProtocolConversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -486,37 +486,40 @@ public static LSP.Range TextSpanToRange(TextSpan textSpan, SourceText text)
{
Debug.Assert(document.FilePath != null);

var result = document is Document d
? await SpanMappingHelper.TryGetMappedSpanResultAsync(d, [textSpan], cancellationToken).ConfigureAwait(false)
: null;
if (result == null)
return await ConvertTextSpanToLocationAsync(document, textSpan, isStale, cancellationToken).ConfigureAwait(false);
if (document is Document d &&
SpanMappingHelper.CanMapSpans(d))
{
var result = await SpanMappingHelper.TryGetMappedSpanResultAsync(d, [textSpan], cancellationToken).ConfigureAwait(false);
if (result is not [{ IsDefault: false } mappedSpan])
{
// Couldn't map the span, but mapping is supported, so the mapper must not want to show include this result
return null;
}

var mappedSpan = result.Value.Single();
if (mappedSpan.IsDefault)
return await ConvertTextSpanToLocationAsync(document, textSpan, isStale, cancellationToken).ConfigureAwait(false);
DocumentUri? uri = null;
try
{
if (PathUtilities.IsAbsolute(mappedSpan.FilePath))
uri = CreateAbsoluteDocumentUri(mappedSpan.FilePath);
}
catch (UriFormatException)
{
}

DocumentUri? uri = null;
try
{
if (PathUtilities.IsAbsolute(mappedSpan.FilePath))
uri = CreateAbsoluteDocumentUri(mappedSpan.FilePath);
}
catch (UriFormatException)
{
}
if (uri == null)
{
context?.TraceWarning($"Could not convert '{mappedSpan.FilePath}' to uri");
return null;
}

if (uri == null)
{
context?.TraceWarning($"Could not convert '{mappedSpan.FilePath}' to uri");
return null;
return new LSP.Location
{
DocumentUri = uri,
Range = MappedSpanResultToRange(mappedSpan)
};
}

return new LSP.Location
{
DocumentUri = uri,
Range = MappedSpanResultToRange(mappedSpan)
};
return await ConvertTextSpanToLocationAsync(document, textSpan, isStale, cancellationToken).ConfigureAwait(false);

static async Task<LSP.Location> ConvertTextSpanToLocationAsync(
TextDocument document,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,45 @@ public void M()

var results = await RunFindAllReferencesAsync(testLspServer, testLspServer.GetLocations("caret").First());
Assert.Equal(2, results.Length);
Assert.True(results[1].Location.DocumentUri.GetRequiredParsedUri().LocalPath.EndsWith("generated_file.cs"));
Assert.True(results.Any(r => r.Location.DocumentUri.GetRequiredParsedUri().LocalPath.EndsWith("generated_file.cs")));

var service = Assert.IsType<TestSourceGeneratedDocumentSpanMappingService>(workspace.Services.GetService<ISourceGeneratedDocumentSpanMappingService>());
Assert.True(service.DidMapSpans);
}

[Theory, CombinatorialData]
public async Task TestFindReferencesAsync_WithRazorSourceGeneratedFile_HiddenSpan(bool mutatingLspWorkspace)
{
var generatedMarkup = """
public class B
{
#line hidden
public void {|reference:M|}()
{
}
}
""";
await using var testLspServer = await CreateTestLspServerAsync("""
public class A
{
public void M()
{
new B().{|caret:M|}();
}
}
""", mutatingLspWorkspace, CapabilitiesWithVSExtensions);

TestFileMarkupParser.GetSpans(generatedMarkup, out var generatedCode, out ImmutableDictionary<string, ImmutableArray<TextSpan>> spans);
var generatedSourceText = SourceText.From(generatedCode);

var razorGenerator = new Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator((c) => c.AddSource("generated_file.cs", generatedCode));
var workspace = testLspServer.TestWorkspace;
var project = workspace.CurrentSolution.Projects.First().AddAnalyzerReference(new TestGeneratorReference(razorGenerator));
workspace.TryApplyChanges(project.Solution);

var results = await RunFindAllReferencesAsync(testLspServer, testLspServer.GetLocations("caret").First());
Assert.Equal(2, results.Length);
Assert.True(results.Any(r => r.Location.DocumentUri.GetRequiredParsedUri().LocalPath.EndsWith("generated_file.cs")));

var service = Assert.IsType<TestSourceGeneratedDocumentSpanMappingService>(workspace.Services.GetService<ISourceGeneratedDocumentSpanMappingService>());
Assert.True(service.DidMapSpans);
Expand Down
12 changes: 10 additions & 2 deletions src/LanguageServer/ProtocolUnitTests/Rename/RenameTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,11 @@ void M2()
workspace.TryApplyChanges(project.Solution);
var generatedDocument = (await project.GetSourceGeneratedDocumentsAsync()).First();

var renameLocation = await ProtocolConversions.TextSpanToLocationAsync(generatedDocument, spans["caret"].First(), isStale: false, CancellationToken.None);
var renameLocation = new LSP.Location()
{
DocumentUri = generatedDocument.GetURI(),
Range = ProtocolConversions.TextSpanToRange(spans["caret"].First(), generatedSourceText)
};
var renameValue = "RENAME";
var expectedEdits = testLspServer.GetLocations("renamed").Select(location => new LSP.TextEdit() { NewText = renameValue, Range = location.Range });
var expectedGeneratedEdits = spans["renamed"].Select(span => new LSP.TextEdit() { NewText = renameValue, Range = ProtocolConversions.TextSpanToRange(span, generatedSourceText) });
Expand Down Expand Up @@ -360,7 +364,11 @@ public void M()
workspace.TryApplyChanges(project.Solution);
var generatedDocument = (await project.GetSourceGeneratedDocumentsAsync()).First();

var renameLocation = await ProtocolConversions.TextSpanToLocationAsync(generatedDocument, spans["caret"].First(), isStale: false, CancellationToken.None);
var renameLocation = new LSP.Location()
{
DocumentUri = generatedDocument.GetURI(),
Range = ProtocolConversions.TextSpanToRange(spans["caret"].First(), generatedSourceText)
};
var renameValue = "RENAME";
var expectedGeneratedEdits = spans["renamed"].Select(span => new LSP.TextEdit() { NewText = renameValue, Range = ProtocolConversions.TextSpanToRange(span, generatedSourceText) });

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests;

[ExportWorkspaceService(typeof(ISourceGeneratedDocumentSpanMappingService))]
[Shared]
[PartNotDiscoverable]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal class TestSourceGeneratedDocumentSpanMappingService() : ISourceGeneratedDocumentSpanMappingService
Expand All @@ -25,21 +27,23 @@ internal class TestSourceGeneratedDocumentSpanMappingService() : ISourceGenerate

public bool CanMapSpans(SourceGeneratedDocument sourceGeneratedDocument)
{
throw new NotImplementedException();
return sourceGeneratedDocument.IsRazorSourceGeneratedDocument();
}

public Task<ImmutableArray<MappedTextChange>> GetMappedTextChangesAsync(SourceGeneratedDocument oldDocument, SourceGeneratedDocument newDocument, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}

public Task<ImmutableArray<MappedSpanResult>> MapSpansAsync(SourceGeneratedDocument document, ImmutableArray<TextSpan> spans, CancellationToken cancellationToken)
public async Task<ImmutableArray<MappedSpanResult>> MapSpansAsync(SourceGeneratedDocument document, ImmutableArray<TextSpan> spans, CancellationToken cancellationToken)
{
if (document.IsRazorSourceGeneratedDocument())
{
var sourceText = await document.GetTextAsync(cancellationToken);
DidMapSpans = true;
return spans.SelectAsArray(s => new MappedSpanResult(document.FilePath, sourceText.Lines.GetLinePositionSpan(s), s));
}

return Task.FromResult(ImmutableArray<MappedSpanResult>.Empty);
return [];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Text;

namespace Microsoft.CodeAnalysis.ExternalAccess.Razor;

internal interface IRazorSourceGeneratedDocumentExcerptService
{
Task<RazorExcerptResult?> TryExcerptAsync(SourceGeneratedDocument document, TextSpan span, RazorExcerptMode razorMode, RazorClassificationOptionsWrapper options, CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Runtime.Serialization;
using Microsoft.CodeAnalysis.Classification;

namespace Microsoft.CodeAnalysis.ExternalAccess.Razor
{
[DataContract]
internal readonly struct RazorClassificationOptionsWrapper
{
public static RazorClassificationOptionsWrapper Default = new(ClassificationOptions.Default);

[DataMember(Order = 0)]
internal readonly ClassificationOptions UnderlyingObject;

public RazorClassificationOptionsWrapper(ClassificationOptions underlyingObject)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@ internal readonly struct RazorMappedSpanResult

public RazorMappedSpanResult(string filePath, LinePositionSpan linePositionSpan, TextSpan span)
{
if (string.IsNullOrEmpty(filePath))
{
throw new ArgumentException(nameof(filePath));
}

FilePath = filePath;
LinePositionSpan = linePositionSpan;
Span = span;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Text;

namespace Microsoft.CodeAnalysis.ExternalAccess.Razor;

[ExportWorkspaceService(typeof(ISourceGeneratedDocumentExcerptService)), Shared]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed class RazorSourceGeneratedDocumentExcerptServiceWrapper(
[Import(AllowDefault = true)] IRazorSourceGeneratedDocumentExcerptService? implementation) : ISourceGeneratedDocumentExcerptService
{
private readonly IRazorSourceGeneratedDocumentExcerptService? _implementation = implementation;

public bool CanExcerpt(SourceGeneratedDocument document)
{
return _implementation is not null && document.IsRazorSourceGeneratedDocument();
}

public async Task<ExcerptResult?> TryExcerptAsync(SourceGeneratedDocument document, TextSpan span, ExcerptMode mode, ClassificationOptions classificationOptions, CancellationToken cancellationToken)
{
if (_implementation is null || !document.IsRazorSourceGeneratedDocument())
{
return null;
}

var razorMode = mode switch
{
ExcerptMode.SingleLine => RazorExcerptMode.SingleLine,
ExcerptMode.Tooltip => RazorExcerptMode.Tooltip,
_ => throw ExceptionUtilities.UnexpectedValue(mode),
};

var options = new RazorClassificationOptionsWrapper(classificationOptions);
var result = await _implementation.TryExcerptAsync(document, span, razorMode, options, cancellationToken).ConfigureAwait(false);

if (result is null)
return null;

var razorExcerpt = result.Value;
return new ExcerptResult(razorExcerpt.Content, razorExcerpt.MappedSpan, razorExcerpt.ClassifiedSpans, razorExcerpt.Document, razorExcerpt.Span);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ public async Task<ImmutableArray<MappedSpanResult>> MapSpansAsync(SourceGenerate
}

var mappedSpans = await _implementation.MapSpansAsync(document, spans, cancellationToken).ConfigureAwait(false);
if (mappedSpans.Length != spans.Length)
if (mappedSpans.IsDefault ||
mappedSpans.Length != spans.Length)
{
return [];
}
Expand Down
Loading
Loading