Skip to content

Commit 3135e69

Browse files
authored
Extract component search engine method to extension (#10272)
2 parents 7ba9c2d + 374a2b9 commit 3135e69

File tree

8 files changed

+211
-221
lines changed

8 files changed

+211
-221
lines changed

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultRazorComponentSearchEngine.cs

Lines changed: 2 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -23,53 +23,6 @@ internal class DefaultRazorComponentSearchEngine(
2323
private readonly IProjectSnapshotManager _projectManager = projectManager;
2424
private readonly ILogger _logger = loggerFactory.GetOrCreateLogger<DefaultRazorComponentSearchEngine>();
2525

26-
public async override Task<TagHelperDescriptor?> TryGetTagHelperDescriptorAsync(IDocumentSnapshot documentSnapshot, CancellationToken cancellationToken)
27-
{
28-
// No point doing anything if its not a component
29-
if (documentSnapshot.FileKind != FileKinds.Component)
30-
{
31-
return null;
32-
}
33-
34-
var razorCodeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false);
35-
if (razorCodeDocument is null)
36-
{
37-
return null;
38-
}
39-
40-
var projects = _projectManager.GetProjects();
41-
42-
foreach (var project in projects)
43-
{
44-
// If the document is an import document, then it can't be a component
45-
if (project.IsImportDocument(documentSnapshot))
46-
{
47-
return null;
48-
}
49-
50-
// If the document isn't in this project, then no point searching for components
51-
// This also avoids the issue of duplicate components
52-
if (!project.DocumentFilePaths.Contains(documentSnapshot.FilePath))
53-
{
54-
return null;
55-
}
56-
57-
// If we got this far, we can check for tag helpers
58-
var tagHelpers = await project.GetTagHelpersAsync(cancellationToken).ConfigureAwait(false);
59-
foreach (var tagHelper in tagHelpers)
60-
{
61-
// Check the typename and namespace match
62-
if (IsPathCandidateForComponent(documentSnapshot, tagHelper.GetTypeNameIdentifier().AsMemory()) &&
63-
ComponentNamespaceMatchesFullyQualifiedName(razorCodeDocument, tagHelper.GetTypeNamespace().AsSpan()))
64-
{
65-
return tagHelper;
66-
}
67-
}
68-
}
69-
70-
return null;
71-
}
72-
7326
/// <summary>Search for a component in a project based on its tag name and fully qualified name.</summary>
7427
/// <remarks>
7528
/// This method makes several assumptions about the nature of components. First, it assumes that a component
@@ -110,7 +63,7 @@ internal class DefaultRazorComponentSearchEngine(
11063
}
11164

11265
// Rule out if not Razor component with correct name
113-
if (!IsPathCandidateForComponent(documentSnapshot, lookupSymbolName))
66+
if (!documentSnapshot.IsPathCandidateForComponent(lookupSymbolName))
11467
{
11568
continue;
11669
}
@@ -122,7 +75,7 @@ internal class DefaultRazorComponentSearchEngine(
12275
}
12376

12477
// Make sure we have the right namespace of the fully qualified name
125-
if (!ComponentNamespaceMatchesFullyQualifiedName(razorCodeDocument, namespaceName.AsSpan()))
78+
if (!razorCodeDocument.ComponentNamespaceMatches(namespaceName))
12679
{
12780
continue;
12881
}
@@ -142,31 +95,4 @@ internal static ReadOnlyMemory<char> RemoveGenericContent(ReadOnlyMemory<char> t
14295
? typeName[..genericSeparatorStart]
14396
: typeName;
14497
}
145-
146-
private static bool IsPathCandidateForComponent(IDocumentSnapshot documentSnapshot, ReadOnlyMemory<char> path)
147-
{
148-
if (documentSnapshot.FileKind != FileKinds.Component)
149-
{
150-
return false;
151-
}
152-
153-
var fileName = Path.GetFileNameWithoutExtension(documentSnapshot.FilePath);
154-
return fileName.AsSpan().Equals(path.Span, FilePathComparison.Instance);
155-
}
156-
157-
private bool ComponentNamespaceMatchesFullyQualifiedName(RazorCodeDocument razorCodeDocument, ReadOnlySpan<char> namespaceName)
158-
{
159-
var namespaceNode = (NamespaceDeclarationIntermediateNode)razorCodeDocument
160-
.GetDocumentIntermediateNode()
161-
.FindDescendantNodes<IntermediateNode>()
162-
.First(n => n is NamespaceDeclarationIntermediateNode);
163-
164-
var namespacesMatch = namespaceNode.Content.AsSpan().Equals(namespaceName, StringComparison.Ordinal);
165-
if (!namespacesMatch)
166-
{
167-
_logger.LogInformation($"Namespace name {namespaceNode.Content} does not match namespace name {namespaceName.ToString()}.");
168-
}
169-
170-
return namespacesMatch;
171-
}
17298
}

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DocumentPresentation/TextDocumentUriPresentationEndpoint.cs

Lines changed: 6 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,12 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.DocumentPresentation;
2222

2323
internal class TextDocumentUriPresentationEndpoint(
2424
IRazorDocumentMappingService razorDocumentMappingService,
25-
RazorComponentSearchEngine razorComponentSearchEngine,
2625
IClientConnection clientConnection,
2726
IFilePathService filePathService,
2827
IDocumentContextFactory documentContextFactory,
2928
ILoggerFactory loggerFactory)
3029
: AbstractTextDocumentPresentationEndpointBase<UriPresentationParams>(razorDocumentMappingService, clientConnection, filePathService, loggerFactory.GetOrCreateLogger<TextDocumentUriPresentationEndpoint>()), ITextDocumentUriPresentationHandler
3130
{
32-
private readonly RazorComponentSearchEngine _razorComponentSearchEngine = razorComponentSearchEngine ?? throw new ArgumentNullException(nameof(razorComponentSearchEngine));
3331
private readonly IDocumentContextFactory _documentContextFactory = documentContextFactory ?? throw new ArgumentNullException(nameof(documentContextFactory));
3432

3533
public override string EndpointName => CustomMessageNames.RazorUriPresentationEndpoint;
@@ -101,14 +99,14 @@ protected override IRazorPresentationParams CreateRazorRequestParameters(UriPres
10199
{
102100
Uri = request.TextDocument.Uri
103101
},
104-
Edits = new[]
105-
{
102+
Edits =
103+
[
106104
new TextEdit
107105
{
108106
NewText = componentTagText,
109107
Range = request.Range
110108
}
111-
}
109+
]
112110
}
113111
}
114112
};
@@ -127,45 +125,13 @@ protected override IRazorPresentationParams CreateRazorRequestParameters(UriPres
127125

128126
cancellationToken.ThrowIfCancellationRequested();
129127

130-
var descriptor = await _razorComponentSearchEngine.TryGetTagHelperDescriptorAsync(documentContext.Snapshot, cancellationToken).ConfigureAwait(false);
128+
var descriptor = await documentContext.Snapshot.TryGetTagHelperDescriptorAsync(cancellationToken).ConfigureAwait(false);
131129
if (descriptor is null)
132130
{
133-
Logger.LogInformation($"Failed to find tag helper descriptor.");
134-
return null;
135-
}
136-
137-
var typeName = descriptor.GetTypeNameIdentifier();
138-
if (string.IsNullOrWhiteSpace(typeName))
139-
{
140-
Logger.LogWarning($"Found a tag helper, {descriptor.Name}, but it has an empty TypeNameIdentifier.");
131+
Logger.LogInformation($"Failed to find tag helper descriptor for {documentContext.Snapshot.FilePath}.");
141132
return null;
142133
}
143134

144-
// TODO: Add @using statements if required, or fully qualify (GetTypeName())
145-
146-
using var _ = StringBuilderPool.GetPooledObject(out var sb);
147-
148-
sb.Append('<');
149-
sb.Append(typeName);
150-
151-
foreach (var requiredAttribute in descriptor.EditorRequiredAttributes)
152-
{
153-
sb.Append(' ');
154-
sb.Append(requiredAttribute.Name);
155-
sb.Append("=\"\"");
156-
}
157-
158-
if (descriptor.AllowedChildTags.Length > 0)
159-
{
160-
sb.Append("></");
161-
sb.Append(typeName);
162-
sb.Append('>');
163-
}
164-
else
165-
{
166-
sb.Append(" />");
167-
}
168-
169-
return sb.ToString();
135+
return descriptor.TryGetComponentTag();
170136
}
171137
}

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorComponentSearchEngine.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,4 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer;
1010
internal abstract class RazorComponentSearchEngine
1111
{
1212
public abstract Task<IDocumentSnapshot?> TryLocateComponentAsync(TagHelperDescriptor tagHelper);
13-
14-
public abstract Task<TagHelperDescriptor?> TryGetTagHelperDescriptorAsync(IDocumentSnapshot codeDocument, System.Threading.CancellationToken cancellationToken);
1513
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT license. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.IO;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Microsoft.AspNetCore.Razor.Language;
9+
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
10+
11+
namespace Microsoft.CodeAnalysis.Razor.Workspaces;
12+
13+
internal static class IDocumentSnapshotExtensions
14+
{
15+
public static async Task<TagHelperDescriptor?> TryGetTagHelperDescriptorAsync(this IDocumentSnapshot documentSnapshot, CancellationToken cancellationToken)
16+
{
17+
// No point doing anything if its not a component
18+
if (documentSnapshot.FileKind != FileKinds.Component)
19+
{
20+
return null;
21+
}
22+
23+
var razorCodeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false);
24+
if (razorCodeDocument is null)
25+
{
26+
return null;
27+
}
28+
29+
var project = documentSnapshot.Project;
30+
31+
// If the document is an import document, then it can't be a component
32+
if (project.IsImportDocument(documentSnapshot))
33+
{
34+
return null;
35+
}
36+
37+
// If we got this far, we can check for tag helpers
38+
var tagHelpers = await project.GetTagHelpersAsync(cancellationToken).ConfigureAwait(false);
39+
foreach (var tagHelper in tagHelpers)
40+
{
41+
// Check the typename and namespace match
42+
if (documentSnapshot.IsPathCandidateForComponent(tagHelper.GetTypeNameIdentifier().AsMemory()) &&
43+
razorCodeDocument.ComponentNamespaceMatches(tagHelper.GetTypeNamespace()))
44+
{
45+
return tagHelper;
46+
}
47+
}
48+
49+
return null;
50+
}
51+
52+
public static bool IsPathCandidateForComponent(this IDocumentSnapshot documentSnapshot, ReadOnlyMemory<char> path)
53+
{
54+
if (documentSnapshot.FileKind != FileKinds.Component)
55+
{
56+
return false;
57+
}
58+
59+
var fileName = Path.GetFileNameWithoutExtension(documentSnapshot.FilePath);
60+
return fileName.AsSpan().Equals(path.Span, FilePathComparison.Instance);
61+
}
62+
}

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RazorCodeDocumentExtensions.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33

44
using System;
55
using System.Diagnostics;
6+
using System.Linq;
67
using Microsoft.AspNetCore.Razor.Language;
8+
using Microsoft.AspNetCore.Razor.Language.Intermediate;
79
using Microsoft.CodeAnalysis.Razor.Protocol;
810
using Microsoft.CodeAnalysis.Text;
911

@@ -131,4 +133,14 @@ public static bool TryGetMinimalCSharpRange(this RazorCodeDocument codeDocument,
131133
csharpRange = default;
132134
return false;
133135
}
136+
137+
public static bool ComponentNamespaceMatches(this RazorCodeDocument razorCodeDocument, string fullyQualifiedNamespace)
138+
{
139+
var namespaceNode = (NamespaceDeclarationIntermediateNode)razorCodeDocument
140+
.GetDocumentIntermediateNode()
141+
.FindDescendantNodes<IntermediateNode>()
142+
.First(n => n is NamespaceDeclarationIntermediateNode);
143+
144+
return namespaceNode.Content == fullyQualifiedNamespace;
145+
}
134146
}

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/TagHelperDescriptorExtensions.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using Microsoft.AspNetCore.Razor.Language;
6+
using Microsoft.AspNetCore.Razor.PooledObjects;
67

78
namespace Microsoft.CodeAnalysis.Razor.Workspaces;
89

@@ -13,4 +14,40 @@ public static bool IsAttributeDescriptor(this TagHelperDescriptor d)
1314
return d.Metadata.TryGetValue(TagHelperMetadata.Common.ClassifyAttributesOnly, out var value) ||
1415
string.Equals(value, bool.TrueString, StringComparison.OrdinalIgnoreCase);
1516
}
17+
18+
public static string? TryGetComponentTag(this TagHelperDescriptor descriptor)
19+
{
20+
var typeName = descriptor.GetTypeNameIdentifier();
21+
if (string.IsNullOrWhiteSpace(typeName))
22+
{
23+
return null;
24+
}
25+
26+
// TODO: Add @using statements if required, or fully qualify (GetTypeName())
27+
28+
using var _ = StringBuilderPool.GetPooledObject(out var sb);
29+
30+
sb.Append('<');
31+
sb.Append(typeName);
32+
33+
foreach (var requiredAttribute in descriptor.EditorRequiredAttributes)
34+
{
35+
sb.Append(' ');
36+
sb.Append(requiredAttribute.Name);
37+
sb.Append("=\"\"");
38+
}
39+
40+
if (descriptor.AllowedChildTags.Length > 0)
41+
{
42+
sb.Append("></");
43+
sb.Append(typeName);
44+
sb.Append('>');
45+
}
46+
else
47+
{
48+
sb.Append(" />");
49+
}
50+
51+
return sb.ToString();
52+
}
1653
}

0 commit comments

Comments
 (0)