15
15
using Microsoft . AspNetCore . Razor . Language . Components ;
16
16
using Microsoft . AspNetCore . Razor . Language . Extensions ;
17
17
using Microsoft . AspNetCore . Razor . Language . Intermediate ;
18
+ using Microsoft . AspNetCore . Razor . Language . Legacy ;
18
19
using Microsoft . AspNetCore . Razor . Language . Syntax ;
19
20
using Microsoft . AspNetCore . Razor . LanguageServer . CodeActions . Models ;
20
21
using Microsoft . AspNetCore . Razor . PooledObjects ;
@@ -79,16 +80,14 @@ public Task<ImmutableArray<RazorVSInternalCodeAction>> ProvideAsync(RazorCodeAct
79
80
80
81
var utilityScanRoot = FindNearestCommonAncestor ( startElementNode , endElementNode ) ?? startElementNode ;
81
82
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 ) ) ;
89
88
90
89
AddUsingDirectivesInRange ( utilityScanRoot ,
91
- usingsInFile ,
90
+ usingNamespaceStrings ,
92
91
actionParams . ExtractStart ,
93
92
actionParams . ExtractEnd ,
94
93
actionParams ) ;
@@ -288,7 +287,7 @@ MarkupTagHelperAttributeSyntax or
288
287
return null ;
289
288
}
290
289
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 )
292
291
{
293
292
var components = new HashSet < string > ( ) ;
294
293
var extractSpan = new TextSpan ( extractStart , extractEnd - extractStart ) ;
@@ -297,12 +296,12 @@ private static void AddUsingDirectivesInRange(SyntaxNode root, string usingsInFi
297
296
{
298
297
if ( node is MarkupTagHelperElementSyntax { TagHelperInfo : { } tagHelperInfo } )
299
298
{
300
- AddUsingFromTagHelperInfo ( tagHelperInfo , components , usingsInFile , actionParams ) ;
299
+ AddUsingFromTagHelperInfo ( tagHelperInfo , components , usingsInSourceRazor , actionParams ) ;
301
300
}
302
301
}
303
302
}
304
303
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 )
306
305
{
307
306
foreach ( var descriptor in tagHelperInfo . BindingResult . Descriptors )
308
307
{
@@ -312,9 +311,30 @@ private static void AddUsingFromTagHelperInfo(TagHelperInfo tagHelperInfo, HashS
312
311
}
313
312
314
313
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 ++ )
316
330
{
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
+ }
318
338
}
319
339
}
320
340
}
0 commit comments