Allow LSP and cohosting to provide specialized methods to get a syntax tree for a C# document

This commit is contained in:
David Wengier 2024-08-20 17:06:26 +10:00
Родитель e16582150b
Коммит efceb90f2d
5 изменённых файлов: 50 добавлений и 7 удалений

Просмотреть файл

@ -5,10 +5,15 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
using Microsoft.CodeAnalysis.Razor.GoToDefinition;
using Microsoft.CodeAnalysis.Razor.Logging;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.CodeAnalysis.Razor.Workspaces;
namespace Microsoft.AspNetCore.Razor.LanguageServer.Definition;
@ -19,4 +24,9 @@ internal sealed class RazorComponentDefinitionService(
ILoggerFactory loggerFactory)
: AbstractRazorComponentDefinitionService(componentSearchEngine, documentMappingService, loggerFactory.GetOrCreateLogger<RazorComponentDefinitionService>())
{
protected override ValueTask<SyntaxTree> GetCSharpSyntaxTreeAsync(IDocumentSnapshot documentSnapshot, RazorCodeDocument codeDocument, CancellationToken cancellationToken)
{
var csharpText = codeDocument.GetCSharpSourceText();
return new(CSharpSyntaxTree.ParseText(csharpText, cancellationToken: cancellationToken));
}
}

Просмотреть файл

@ -70,9 +70,10 @@ internal abstract class AbstractRazorComponentDefinitionService(
_logger.LogInformation($"Attempting to get definition from an attribute directly.");
var originCodeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false);
var syntaxTree = await GetCSharpSyntaxTreeAsync(documentSnapshot, originCodeDocument, cancellationToken).ConfigureAwait(false);
var range = await RazorComponentDefinitionHelpers
.TryGetPropertyRangeAsync(originCodeDocument, attributeDescriptor.GetPropertyName(), _documentMappingService, _logger, cancellationToken)
.TryGetPropertyRangeAsync(originCodeDocument, syntaxTree, attributeDescriptor.GetPropertyName(), _documentMappingService, _logger, cancellationToken)
.ConfigureAwait(false);
if (range is not null)
@ -87,4 +88,6 @@ internal abstract class AbstractRazorComponentDefinitionService(
// at least then press F7 to go there.
return VsLspFactory.DefaultRange;
}
protected abstract ValueTask<SyntaxTree> GetCSharpSyntaxTreeAsync(IDocumentSnapshot documentSnapshot, RazorCodeDocument codeDocument, CancellationToken cancellationToken);
}

Просмотреть файл

@ -8,7 +8,6 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Syntax;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
using Microsoft.CodeAnalysis.Razor.Logging;
@ -131,12 +130,13 @@ internal static class RazorComponentDefinitionHelpers
public static async Task<LspRange?> TryGetPropertyRangeAsync(
RazorCodeDocument codeDocument,
SyntaxTree csharpSyntaxTree,
string propertyName,
IDocumentMappingService documentMappingService,
ILogger logger,
CancellationToken cancellationToken)
{
// Parse the C# file and find the property that matches the name.
// Process the C# tree and find the property that matches the name.
// We don't worry about parameter attributes here for two main reasons:
// 1. We don't have symbolic information, so the best we could do would be checking for any
// attribute named Parameter, regardless of which namespace. It also means we would have
@ -147,9 +147,8 @@ internal static class RazorComponentDefinitionHelpers
// tag helper attribute. If they don't have the [Parameter] attribute then the Razor compiler
// will error, but allowing them to Go To Def on that property regardless, actually helps
// them fix the error.
var csharpText = codeDocument.GetCSharpSourceText();
var syntaxTree = CSharpSyntaxTree.ParseText(csharpText, cancellationToken: cancellationToken);
var root = await syntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
var root = await csharpSyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
// Since we know how the compiler generates the C# source we can be a little specific here, and avoid
// long tree walks. If the compiler ever changes how they generate their code, the tests for this will break
@ -169,6 +168,7 @@ internal static class RazorComponentDefinitionHelpers
return null;
}
var csharpText = codeDocument.GetCSharpSourceText();
var range = csharpText.GetRange(property.Identifier.Span);
if (documentMappingService.TryMapToHostDocumentRange(codeDocument.GetCSharpDocument(), range, out var originalRange))
{

Просмотреть файл

@ -2,10 +2,17 @@
// Licensed under the MIT license. See License.txt in the project root for license information.
using System.Composition;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
using Microsoft.CodeAnalysis.Razor.GoToDefinition;
using Microsoft.CodeAnalysis.Razor.Logging;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.CodeAnalysis.Razor.Workspaces;
using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem;
namespace Microsoft.CodeAnalysis.Remote.Razor.GoToDefinition;
@ -14,7 +21,25 @@ namespace Microsoft.CodeAnalysis.Remote.Razor.GoToDefinition;
internal sealed class RazorComponentDefinitionService(
IRazorComponentSearchEngine componentSearchEngine,
IDocumentMappingService documentMappingService,
IFilePathService filePathService,
ILoggerFactory loggerFactory)
: AbstractRazorComponentDefinitionService(componentSearchEngine, documentMappingService, loggerFactory.GetOrCreateLogger<RazorComponentDefinitionService>())
{
private readonly IFilePathService _filePathService = filePathService;
protected override async ValueTask<SyntaxTree> GetCSharpSyntaxTreeAsync(IDocumentSnapshot documentSnapshot, RazorCodeDocument codeDocument, CancellationToken cancellationToken)
{
Debug.Assert(documentSnapshot is RemoteDocumentSnapshot, "This method only works on document snapshots created in the OOP process");
var remoteSnapshot = (RemoteDocumentSnapshot)documentSnapshot;
var document = await remoteSnapshot.GetGeneratedDocumentAsync(_filePathService).ConfigureAwait(false);
if (document.TryGetSyntaxTree(out var syntaxTree))
{
return syntaxTree;
}
var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
return tree.AssumeNotNull();
}
}

Просмотреть файл

@ -1,11 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.LanguageServer.Completion;
using Microsoft.AspNetCore.Razor.Test.Common;
using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Razor.GoToDefinition;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.VisualStudio.LanguageServer.Protocol;
@ -392,7 +394,10 @@ public class RazorComponentDefinitionHelpersTest(ITestOutputHelper testOutput) :
var documentMappingService = new LspDocumentMappingService(FilePathService, new TestDocumentContextFactory(), LoggerFactory);
var range = await RazorComponentDefinitionHelpers.TryGetPropertyRangeAsync(codeDocument, propertyName, documentMappingService, Logger, DisposalToken);
var csharpText = codeDocument.GetCSharpSourceText();
var syntaxTree = CSharpSyntaxTree.ParseText(csharpText);
var range = await RazorComponentDefinitionHelpers.TryGetPropertyRangeAsync(codeDocument, syntaxTree, propertyName, documentMappingService, Logger, DisposalToken);
Assert.NotNull(range);
Assert.Equal(expectedRange, range);
}