Skip to content

Commit cf33fd7

Browse files
committed
Include only dependencies included in the selected range
1 parent cba6cf1 commit cf33fd7

File tree

3 files changed

+61
-100
lines changed

3 files changed

+61
-100
lines changed

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

Lines changed: 57 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,17 @@ public Task<ImmutableArray<RazorVSInternalCodeAction>> ProvideAsync(RazorCodeAct
6666

6767
var actionParams = CreateInitialActionParams(context, startElementNode, @namespace);
6868

69-
ProcessSelection(startElementNode, endElementNode, actionParams);
69+
var dependencyScanRoot = FindNearestCommonAncestor(startElementNode, endElementNode);
70+
71+
AddComponentDependenciesInRange(dependencyScanRoot,
72+
actionParams.ExtractStart,
73+
actionParams.ExtractEnd,
74+
ref actionParams);
75+
76+
if (IsMultiPointSelection(context.Request.Range))
77+
{
78+
ProcessMultiPointSelection(startElementNode, endElementNode, actionParams);
79+
}
7080

7181
var resolutionParams = new RazorCodeActionResolutionParams()
7282
{
@@ -94,7 +104,10 @@ private static (MarkupElementSyntax? Start, MarkupElementSyntax? End) GetStartAn
94104
return (null, null);
95105
}
96106

97-
var endElementNode = GetEndElementNode(context, syntaxTree);
107+
var endElementNode = GetEndElementNode(context, syntaxTree, logger);
108+
109+
endElementNode ??= startElementNode;
110+
98111
return (startElementNode, endElementNode);
99112
}
100113

@@ -142,7 +155,8 @@ private static ExtractToNewComponentCodeActionParams CreateInitialActionParams(R
142155
Uri = context.Request.TextDocument.Uri,
143156
ExtractStart = startElementNode.Span.Start,
144157
ExtractEnd = startElementNode.Span.End,
145-
Namespace = @namespace
158+
Namespace = @namespace,
159+
Dependencies = []
146160
};
147161
}
148162

@@ -257,37 +271,58 @@ private static (SyntaxNode? Start, SyntaxNode? End) FindContainingSiblingPair(Sy
257271

258272
return null;
259273
}
260-
private static HashSet<SyntaxNode> IdentifyComponentsInRange(SyntaxNode root, int extractStart, int extractEnd)
274+
275+
private static void AddComponentDependenciesInRange(SyntaxNode root, int extractStart, int extractEnd, ref ExtractToNewComponentCodeActionParams actionParams)
261276
{
262-
var components = new HashSet<SyntaxNode>();
277+
var components = new HashSet<string>();
263278
var extractSpan = new TextSpan(extractStart, extractEnd - extractStart);
264279

265280
foreach (var node in root.DescendantNodes())
266281
{
267-
if (node is MarkupTagHelperElementSyntax markupElement &&
268-
extractSpan.Contains(markupElement.Span))
282+
if (IsMarkupTagHelperElement(node, extractSpan))
269283
{
270-
components.Add(markupElement);
284+
var tagHelperInfo = GetTagHelperInfo(node);
285+
if (tagHelperInfo != null)
286+
{
287+
AddDependenciesFromTagHelperInfo(tagHelperInfo, components, ref actionParams);
288+
}
271289
}
272290
}
291+
}
273292

274-
return components;
293+
private static bool IsMarkupTagHelperElement(SyntaxNode node, TextSpan extractSpan)
294+
{
295+
return node is MarkupTagHelperElementSyntax markupElement &&
296+
extractSpan.Contains(markupElement.Span);
275297
}
276298

277-
private static List<string> GetAllUsingStatements(SyntaxNode root)
299+
private static TagHelperInfo? GetTagHelperInfo(SyntaxNode node)
278300
{
279-
return root.DescendantNodes()
280-
.OfType<CSharpCodeBlockSyntax>()
281-
.SelectMany(cSharpBlock => cSharpBlock.ChildNodes())
282-
.OfType<RazorDirectiveSyntax>()
283-
.Select(directive => directive.ToFullString().TrimStart())
284-
.Where(directiveString => directiveString.StartsWith("@using"))
285-
.Select(directiveString => directiveString.Trim())
286-
.ToList();
301+
if (node is MarkupTagHelperElementSyntax markupElement)
302+
{
303+
return markupElement.TagHelperInfo;
304+
}
305+
306+
return null;
287307
}
288308

289-
//private static bool HasUnsupportedChildren(Language.Syntax.SyntaxNode node)
290-
//{
291-
// return node.DescendantNodes().Any(static n => n is MarkupBlockSyntax or CSharpTransitionSyntax or RazorCommentBlockSyntax);
292-
//}
309+
private static void AddDependenciesFromTagHelperInfo(TagHelperInfo tagHelperInfo, HashSet<string> components, ref ExtractToNewComponentCodeActionParams actionParams)
310+
{
311+
foreach (var descriptor in tagHelperInfo.BindingResult.Descriptors)
312+
{
313+
if (descriptor != null)
314+
{
315+
foreach (var metadata in descriptor.Metadata)
316+
{
317+
if (metadata.Key == TagHelperMetadata.Common.TypeNamespace &&
318+
metadata.Value is not null &&
319+
!components.Contains(metadata.Value))
320+
{
321+
components.Add(metadata.Value);
322+
actionParams.Dependencies.Add($"@using {metadata.Value}");
323+
}
324+
}
325+
}
326+
}
327+
}
293328
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ internal sealed class ExtractToNewComponentCodeActionResolver(
9898
newComponentContent += Environment.NewLine + Environment.NewLine; // Ensure there's a newline after the dependencies if any exist.
9999
}
100100

101-
newComponentContent = newComponentContent + text.GetSubTextString(new CodeAnalysis.Text.TextSpan(actionParams.ExtractStart, actionParams.ExtractEnd - actionParams.ExtractStart)).Trim();
101+
newComponentContent += text.GetSubTextString(new CodeAnalysis.Text.TextSpan(actionParams.ExtractStart, actionParams.ExtractEnd - actionParams.ExtractStart)).Trim();
102102

103103
var start = componentDocument.Source.Text.Lines.GetLinePosition(actionParams.ExtractStart);
104104
var end = componentDocument.Source.Text.Lines.GetLinePosition(actionParams.ExtractEnd);

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

Lines changed: 3 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ public async Task Handle_MultiPointSelection_ReturnsNotEmpty()
154154

155155
var location = new SourceLocation(cursorPosition, -1, -1);
156156
var context = CreateRazorCodeActionContext(request, location, documentPath, contents);
157+
AddMultiPointSelectionToContext(ref context, selectionSpan);
157158

158159
var lineSpan = context.SourceText.GetLinePositionSpan(selectionSpan);
159160
request.Range = VsLspFactory.CreateRange(lineSpan);
@@ -230,7 +231,7 @@ @namespace MarketApp.Pages.Product.Home
230231
}
231232

232233
[Fact]
233-
public async Task Handle_InProperMarkup_ReturnsNull()
234+
public async Task Handle_InProperMarkup_ReturnsEmpty()
234235
{
235236
// Arrange
236237
var documentPath = "c:/Test.razor";
@@ -272,82 +273,7 @@ public async Task Handle_InProperMarkup_ReturnsNull()
272273
var commandOrCodeActionContainer = await provider.ProvideAsync(context, default);
273274

274275
// Assert
275-
Assert.Null(commandOrCodeActionContainer);
276-
}
277-
278-
[Theory]
279-
[InlineData("""
280-
<div id="parent">
281-
[|<div>
282-
<h1>Div a title</h1>
283-
<p>Div a par</p>
284-
</div>|]
285-
<div>
286-
<h1>Div b title</h1>
287-
<p>Div b par</p>
288-
</div>
289-
</div>
290-
""")]
291-
[InlineData("""
292-
<div id="parent">
293-
<div>
294-
<h1>Div a title</h1>
295-
[|<p>Div a par</p>|]
296-
</div>
297-
<div>
298-
<h1>Div b title</h1>
299-
<p>Div b par</p>
300-
</div>
301-
</div>
302-
""")]
303-
[InlineData("""
304-
<div id="parent">
305-
<div>
306-
<h1>Div a title</h1>
307-
[|<p>Div a par</p>
308-
</div>
309-
<div>
310-
<h1>Div b title</h1>
311-
<p>Div b par</p>|]
312-
</div>
313-
</div>
314-
""")]
315-
public async Task Handle_ValidElementSelection_ReturnsNotNull(string markupElementSelection)
316-
{
317-
// Arrange
318-
var documentPath = "c:/Test.razor";
319-
var contents = $$"""
320-
page "/"
321-
322-
<PageTitle>Home</PageTitle>
323-
324-
{{markupElementSelection}}
325-
326-
<h1>Hello, world!</h1>
327-
328-
Welcome to your new app.
329-
""";
330-
331-
TestFileMarkupParser.GetPositionAndSpans(
332-
contents, out contents, out int cursorPosition, out ImmutableArray<TextSpan> spans);
333-
334-
var request = new VSCodeActionParams()
335-
{
336-
TextDocument = new VSTextDocumentIdentifier { Uri = new Uri(documentPath) },
337-
Range = new Range(),
338-
Context = new VSInternalCodeActionContext()
339-
};
340-
341-
var location = new SourceLocation(cursorPosition, -1, -1);
342-
var context = CreateRazorCodeActionContext(request, location, documentPath, contents, supportsFileCreation: true);
343-
344-
var provider = new ExtractToNewComponentCodeActionProvider(LoggerFactory);
345-
346-
// Act
347-
var commandOrCodeActionContainer = await provider.ProvideAsync(context, default);
348-
349-
// Assert
350-
Assert.NotNull(commandOrCodeActionContainer);
276+
Assert.Empty(commandOrCodeActionContainer);
351277
}
352278

353279
private static RazorCodeActionContext CreateRazorCodeActionContext(VSCodeActionParams request, SourceLocation location, string filePath, string text, bool supportsFileCreation = true)

0 commit comments

Comments
 (0)