Skip to content

Commit cd677c7

Browse files
committed
Adjustments to addUsingDirectives and adjusted for relative using namespaces
1 parent 1c239a1 commit cd677c7

File tree

1 file changed

+33
-13
lines changed

1 file changed

+33
-13
lines changed

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

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using Microsoft.AspNetCore.Razor.Language.Components;
1616
using Microsoft.AspNetCore.Razor.Language.Extensions;
1717
using Microsoft.AspNetCore.Razor.Language.Intermediate;
18+
using Microsoft.AspNetCore.Razor.Language.Legacy;
1819
using Microsoft.AspNetCore.Razor.Language.Syntax;
1920
using Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Models;
2021
using Microsoft.AspNetCore.Razor.PooledObjects;
@@ -79,16 +80,14 @@ public Task<ImmutableArray<RazorVSInternalCodeAction>> ProvideAsync(RazorCodeAct
7980

8081
var utilityScanRoot = FindNearestCommonAncestor(startElementNode, endElementNode) ?? startElementNode;
8182

82-
// The new component usings are going to be a subset of the usings in the source razor file
83-
var usingsInFile = syntaxTree.Root.DescendantNodes()
84-
.OfType<CSharpStatementLiteralSyntax>()
85-
.Where(literal => literal.SerializedValue.Contains("using"))
86-
.Select(literal => literal.SerializedValue)
87-
.Aggregate(new StringBuilder(), (sb, line) => sb.AppendLine(line))
88-
.ToString();
83+
// The new component usings are going to be a subset of the usings in the source razor file.
84+
var usingStrings = syntaxTree.Root.DescendantNodes().Where(node => node.IsUsingDirective(out var _)).Select(node => node.ToFullString().TrimEnd());
85+
86+
// Get only the namespace after the "using" keyword.
87+
var usingNamespaceStrings = usingStrings.Select(usingString => usingString.Substring("using ".Length));
8988

9089
AddUsingDirectivesInRange(utilityScanRoot,
91-
usingsInFile,
90+
usingNamespaceStrings,
9291
actionParams.ExtractStart,
9392
actionParams.ExtractEnd,
9493
actionParams);
@@ -288,7 +287,7 @@ MarkupTagHelperAttributeSyntax or
288287
return null;
289288
}
290289

291-
private static void AddUsingDirectivesInRange(SyntaxNode root, string usingsInFile, int extractStart, int extractEnd, ExtractToComponentCodeActionParams actionParams)
290+
private static void AddUsingDirectivesInRange(SyntaxNode root, IEnumerable<string> usingsInSourceRazor, int extractStart, int extractEnd, ExtractToComponentCodeActionParams actionParams)
292291
{
293292
var components = new HashSet<string>();
294293
var extractSpan = new TextSpan(extractStart, extractEnd - extractStart);
@@ -297,12 +296,12 @@ private static void AddUsingDirectivesInRange(SyntaxNode root, string usingsInFi
297296
{
298297
if (node is MarkupTagHelperElementSyntax { TagHelperInfo: { } tagHelperInfo })
299298
{
300-
AddUsingFromTagHelperInfo(tagHelperInfo, components, usingsInFile, actionParams);
299+
AddUsingFromTagHelperInfo(tagHelperInfo, components, usingsInSourceRazor, actionParams);
301300
}
302301
}
303302
}
304303

305-
private static void AddUsingFromTagHelperInfo(TagHelperInfo tagHelperInfo, HashSet<string> components, string usingsInImportsFile, ExtractToComponentCodeActionParams actionParams)
304+
private static void AddUsingFromTagHelperInfo(TagHelperInfo tagHelperInfo, HashSet<string> components, IEnumerable<string> usingsInSourceRazor, ExtractToComponentCodeActionParams actionParams)
306305
{
307306
foreach (var descriptor in tagHelperInfo.BindingResult.Descriptors)
308307
{
@@ -312,9 +311,30 @@ private static void AddUsingFromTagHelperInfo(TagHelperInfo tagHelperInfo, HashS
312311
}
313312

314313
var typeNamespace = descriptor.GetTypeNamespace();
315-
if (components.Add(typeNamespace) && usingsInImportsFile.Contains(typeNamespace))
314+
315+
// Since the using directive at the top of the file may be relative and not absolute,
316+
// we need to generate all possible partial namespaces from `typeNamespace`.
317+
318+
// Potentially, the alternative could be to ask if the using namespace at the top is a substring of `typeNamespace`.
319+
// The only potential edge case is if there are very similar namespaces where one
320+
// is a substring of the other, but they're actually different (e.g., "My.App" and "My.Apple").
321+
322+
// Generate all possible partial namespaces from `typeNamespace`, from least to most specific
323+
// (assuming that the user writes absolute `using` namespaces most of the time)
324+
325+
// This is a bit inefficient because at most 'k' string operations are performed (k = parts in the namespace),
326+
// for each potential using directive.
327+
328+
var parts = typeNamespace.Split('.');
329+
for (var i = 0; i < parts.Length; i++)
316330
{
317-
actionParams.usingDirectives.Add($"@using {typeNamespace}");
331+
var partialNamespace = string.Join(".", parts.Skip(i));
332+
333+
if (components.Add(partialNamespace) && usingsInSourceRazor.Contains(partialNamespace))
334+
{
335+
actionParams.usingDirectives.Add($"@using {partialNamespace}");
336+
break;
337+
}
318338
}
319339
}
320340
}

0 commit comments

Comments
 (0)