Skip to content

Commit 17479d4

Browse files
committed
Added method to scan identifiers
1 parent 6e8d2aa commit 17479d4

File tree

3 files changed

+114
-18
lines changed

3 files changed

+114
-18
lines changed

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Models/ExtractToNewComponentCodeActionParams.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,10 @@ internal sealed class ExtractToNewComponentCodeActionParams
2323

2424
[JsonPropertyName("dependencies")]
2525
public required List<string> Dependencies { get; set; }
26+
27+
[JsonPropertyName("usedIdentifiers")]
28+
public required HashSet<string> UsedIdentifiers { get; set; }
29+
30+
[JsonPropertyName("usedMembers")]
31+
public required HashSet<string> UsedMembers { get; set; }
2632
}

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionProvider.cs

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
2-
// Licensed under the MIT license. See License.txt in the project root for license information.
2+
// Licensed under the MIT license. See License.txt in the project divNode for license information.
33

44
using System;
55
using System.Collections.Generic;
@@ -17,6 +17,7 @@
1717
using Microsoft.AspNetCore.Razor.Language.Syntax;
1818
using Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Models;
1919
using Microsoft.AspNetCore.Razor.Threading;
20+
using Microsoft.CodeAnalysis.CSharp.Syntax;
2021
using Microsoft.CodeAnalysis.Razor.Logging;
2122
using Microsoft.CodeAnalysis.Razor.Workspaces;
2223
using Microsoft.CodeAnalysis.Text;
@@ -59,25 +60,30 @@ public Task<ImmutableArray<RazorVSInternalCodeAction>> ProvideAsync(RazorCodeAct
5960
return SpecializedTasks.EmptyImmutableArray<RazorVSInternalCodeAction>();
6061
}
6162

63+
if (endElementNode is null)
64+
{
65+
endElementNode = startElementNode;
66+
}
67+
6268
if (!TryGetNamespace(context.CodeDocument, out var @namespace))
6369
{
6470
return SpecializedTasks.EmptyImmutableArray<RazorVSInternalCodeAction>();
6571
}
6672

67-
var actionParams = CreateInitialActionParams(context, startElementNode, @namespace);
68-
69-
var dependencyScanRoot = FindNearestCommonAncestor(startElementNode, endElementNode);
70-
71-
AddComponentDependenciesInRange(dependencyScanRoot,
72-
actionParams.ExtractStart,
73-
actionParams.ExtractEnd,
74-
actionParams);
73+
var actionParams = CreateInitialActionParams(context, startElementNode, @namespace);
7574

7675
if (IsMultiPointSelection(context.Request.Range))
7776
{
7877
ProcessMultiPointSelection(startElementNode, endElementNode, actionParams);
7978
}
8079

80+
var utilityScanRoot = FindNearestCommonAncestor(startElementNode, endElementNode) ?? startElementNode;
81+
AddComponentDependenciesInRange(utilityScanRoot,
82+
actionParams.ExtractStart,
83+
actionParams.ExtractEnd,
84+
actionParams);
85+
GetUsedIdentifiers(utilityScanRoot, syntaxTree.Root, actionParams);
86+
8187
var resolutionParams = new RazorCodeActionResolutionParams()
8288
{
8389
Action = LanguageServerConstants.CodeActions.ExtractToNewComponentAction,
@@ -106,8 +112,6 @@ private static (MarkupElementSyntax? Start, MarkupElementSyntax? End) GetStartAn
106112

107113
var endElementNode = GetEndElementNode(context, syntaxTree, logger);
108114

109-
endElementNode ??= startElementNode;
110-
111115
return (startElementNode, endElementNode);
112116
}
113117

@@ -156,7 +160,9 @@ private static ExtractToNewComponentCodeActionParams CreateInitialActionParams(R
156160
ExtractStart = startElementNode.Span.Start,
157161
ExtractEnd = startElementNode.Span.End,
158162
Namespace = @namespace,
159-
Dependencies = []
163+
Dependencies = [],
164+
UsedIdentifiers = [],
165+
UsedMembers = [],
160166
};
161167
}
162168

@@ -221,7 +227,7 @@ private static (SyntaxNode? Start, SyntaxNode? End) FindContainingSiblingPair(Sy
221227
{
222228
// Find the lowest common ancestor of both nodes
223229
var nearestCommonAncestor = FindNearestCommonAncestor(startNode, endNode);
224-
if (nearestCommonAncestor == null)
230+
if (nearestCommonAncestor is null)
225231
{
226232
return (null, null);
227233
}
@@ -237,7 +243,7 @@ private static (SyntaxNode? Start, SyntaxNode? End) FindContainingSiblingPair(Sy
237243
{
238244
var childSpan = child.Span;
239245

240-
if (startContainingNode == null && childSpan.Contains(startSpan))
246+
if (startContainingNode is null && childSpan.Contains(startSpan))
241247
{
242248
startContainingNode = child;
243249
if (endContainingNode is not null)
@@ -259,7 +265,7 @@ private static (SyntaxNode? Start, SyntaxNode? End) FindContainingSiblingPair(Sy
259265
{
260266
var current = node1;
261267

262-
while (current.Kind == SyntaxKind.MarkupElement && current is not null)
268+
while (current is MarkupElementSyntax && current is not null)
263269
{
264270
if (current.Span.Contains(node2.Span))
265271
{
@@ -282,7 +288,7 @@ private static void AddComponentDependenciesInRange(SyntaxNode root, int extract
282288
if (IsMarkupTagHelperElement(node, extractSpan))
283289
{
284290
var tagHelperInfo = GetTagHelperInfo(node);
285-
if (tagHelperInfo != null)
291+
if (tagHelperInfo is not null)
286292
{
287293
AddDependenciesFromTagHelperInfo(tagHelperInfo, components, actionParams);
288294
}
@@ -310,7 +316,7 @@ private static void AddDependenciesFromTagHelperInfo(TagHelperInfo tagHelperInfo
310316
{
311317
foreach (var descriptor in tagHelperInfo.BindingResult.Descriptors)
312318
{
313-
if (descriptor != null)
319+
if (descriptor is not null)
314320
{
315321
foreach (var metadata in descriptor.Metadata)
316322
{
@@ -325,4 +331,27 @@ metadata.Value is not null &&
325331
}
326332
}
327333
}
334+
335+
private static void GetUsedIdentifiers(SyntaxNode divNode, SyntaxNode documentRoot, ExtractToNewComponentCodeActionParams actionParams)
336+
{
337+
HashSet<string> identifiersInScope = [];
338+
HashSet<string> identifiersInBlock = [];
339+
340+
341+
foreach (var node in divNode.DescendantNodes().Where(static node => node.Kind is SyntaxKind.Identifier))
342+
{
343+
identifiersInScope.Add(node.GetContent());
344+
}
345+
346+
foreach (var codeBlock in documentRoot.DescendantNodes().Where(static node => node.Kind is SyntaxKind.RazorDirective))
347+
{
348+
foreach (var node in codeBlock.DescendantNodes().Where(static node => node.Kind is SyntaxKind.Identifier))
349+
{
350+
identifiersInBlock.Add(node.GetContent());
351+
}
352+
}
353+
354+
identifiersInBlock.IntersectWith(identifiersInScope);
355+
actionParams.UsedIdentifiers = identifiersInBlock;
356+
}
328357
}

src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,67 @@ public async Task Handle_SinglePointSelection_ReturnsNotEmpty()
118118
Assert.NotEmpty(commandOrCodeActionContainer);
119119
}
120120

121+
[Fact]
122+
public async Task Handle_CodeInsideDiv_ScansCorrect()
123+
{
124+
// Arrange
125+
var documentPath = "c:/Test.cs";
126+
var contents = """
127+
@page "/"
128+
129+
<PageTitle>Home</PageTitle>
130+
131+
<$$div id="codeInside">
132+
@for(int idx = 0; idx < 10; idx++) {
133+
string s = someFunc(idx * myField);
134+
}
135+
</div>
136+
137+
<div id="parent">
138+
<div>
139+
<h1>Div a title</h1>
140+
<p>Div a par</p>
141+
</div>
142+
<div>
143+
<h1>Div b title</h1>
144+
<p>Div b par</p>
145+
</div>
146+
</div>
147+
148+
@code {
149+
public int myField = 7;
150+
151+
public string someFunc(int num) {
152+
return "Hello for number" + num;
153+
}
154+
}
155+
""";
156+
TestFileMarkupParser.GetPosition(contents, out contents, out var cursorPosition);
157+
158+
var request = new VSCodeActionParams()
159+
{
160+
TextDocument = new VSTextDocumentIdentifier { Uri = new Uri(documentPath) },
161+
Range = new Range(),
162+
Context = new VSInternalCodeActionContext()
163+
};
164+
165+
var location = new SourceLocation(cursorPosition, -1, -1);
166+
var context = CreateRazorCodeActionContext(request, location, documentPath, contents);
167+
168+
var provider = new ExtractToNewComponentCodeActionProvider(LoggerFactory);
169+
170+
// Act
171+
var commandOrCodeActionContainer = await provider.ProvideAsync(context, default);
172+
173+
// Assert
174+
Assert.NotEmpty(commandOrCodeActionContainer);
175+
var codeAction = Assert.Single(commandOrCodeActionContainer);
176+
var razorCodeActionResolutionParams = ((JsonElement)codeAction.Data!).Deserialize<RazorCodeActionResolutionParams>();
177+
Assert.NotNull(razorCodeActionResolutionParams);
178+
var actionParams = ((JsonElement)razorCodeActionResolutionParams.Data).Deserialize<ExtractToNewComponentCodeActionParams>();
179+
Assert.NotNull(actionParams);
180+
}
181+
121182
[Fact]
122183
public async Task Handle_MultiPointSelection_ReturnsNotEmpty()
123184
{
@@ -179,8 +240,8 @@ public async Task Handle_MultiPointSelection_WithEndAfterElement_ReturnsCurrentE
179240
// Arrange
180241
var documentPath = "c:/Test.razor";
181242
var contents = """
182-
@namespace MarketApp.Pages.Product.Home
183243
@page "/"
244+
@namespace MarketApp.Pages.Product.Home
184245
185246
namespace MarketApp.Pages.Product.Home
186247

0 commit comments

Comments
 (0)