39
39
using ICSharpCode . Decompiler . CSharp . Syntax ;
40
40
using Microsoft . CodeAnalysis . Razor . DocumentMapping ;
41
41
using System . Reflection . Metadata . Ecma335 ;
42
+ using Microsoft . VisualStudio . Utilities ;
42
43
43
44
namespace Microsoft . AspNetCore . Razor . LanguageServer . CodeActions ;
44
45
@@ -117,7 +118,7 @@ internal sealed class ExtractToComponentCodeActionResolver(
117
118
} . Uri ;
118
119
119
120
var componentName = Path . GetFileNameWithoutExtension ( componentPath ) ;
120
- var newComponentResult = await GenerateNewComponentAsync ( selectionAnalysis , codeDocument , actionParams . Uri , newComponentUri , documentContext , removeRange , cancellationToken ) . ConfigureAwait ( false ) ;
121
+ var newComponentResult = await GenerateNewComponentAsync ( selectionAnalysis , codeDocument , actionParams . Uri , documentContext , removeRange , cancellationToken ) . ConfigureAwait ( false ) ;
121
122
122
123
if ( newComponentResult is null )
123
124
{
@@ -164,6 +165,7 @@ internal sealed class ExtractToComponentCodeActionResolver(
164
165
DocumentChanges = documentChanges ,
165
166
} ;
166
167
}
168
+
167
169
private static ExtractToComponentCodeActionParams ? DeserializeActionParams ( JsonElement data )
168
170
{
169
171
return data . ValueKind == JsonValueKind . Undefined
@@ -445,13 +447,9 @@ private static HashSet<string> AddComponentDependenciesInRange(SyntaxNode root,
445
447
// Only analyze nodes within the extract span
446
448
foreach ( var node in root . DescendantNodes ( ) . Where ( node => extractSpan . Contains ( node . Span ) ) )
447
449
{
448
- if ( node is MarkupTagHelperElementSyntax )
450
+ if ( node is MarkupTagHelperElementSyntax { TagHelperInfo : { } tagHelperInfo } )
449
451
{
450
- var tagHelperInfo = GetTagHelperInfo ( node ) ;
451
- if ( tagHelperInfo is not null )
452
- {
453
- AddDependenciesFromTagHelperInfo ( tagHelperInfo , ref dependencies ) ;
454
- }
452
+ AddDependenciesFromTagHelperInfo ( tagHelperInfo , dependencies ) ;
455
453
}
456
454
}
457
455
@@ -480,40 +478,24 @@ private static HashSet<string> AddVariableDependenciesInRange(SyntaxNode root, i
480
478
return dependencies ;
481
479
}
482
480
483
- private static TagHelperInfo ? GetTagHelperInfo ( SyntaxNode node )
484
- {
485
- if ( node is MarkupTagHelperElementSyntax markupElement )
486
- {
487
- return markupElement . TagHelperInfo ;
488
- }
489
-
490
- return null ;
491
- }
492
-
493
- private static void AddDependenciesFromTagHelperInfo ( TagHelperInfo tagHelperInfo , ref HashSet < string > dependencies )
481
+ private static void AddDependenciesFromTagHelperInfo ( TagHelperInfo tagHelperInfo , HashSet < string > dependencies )
494
482
{
495
483
foreach ( var descriptor in tagHelperInfo . BindingResult . Descriptors )
496
484
{
497
- if ( descriptor is not null )
485
+ if ( descriptor is null )
498
486
{
499
- foreach ( var metadata in descriptor . Metadata )
500
- {
501
- if ( metadata . Key == TagHelperMetadata . Common . TypeNamespace &&
502
- metadata . Value is not null &&
503
- ! dependencies . Contains ( $ "@using { metadata . Value } ") )
504
- {
505
- dependencies . Add ( $ "@using { metadata . Value } ") ;
506
- }
507
- }
487
+ continue ;
508
488
}
489
+
490
+ var typeNamespace = descriptor . GetTypeNamespace ( ) ;
491
+ dependencies . Add ( typeNamespace ) ;
509
492
}
510
493
}
511
494
512
495
private async Task < NewRazorComponentInfo ? > GenerateNewComponentAsync (
513
496
SelectionAnalysisResult selectionAnalysis ,
514
497
RazorCodeDocument razorCodeDocument ,
515
498
Uri componentUri ,
516
- Uri newComponentUri ,
517
499
DocumentContext documentContext ,
518
500
Range relevantRange ,
519
501
CancellationToken cancellationToken )
@@ -524,31 +506,48 @@ metadata.Value is not null &&
524
506
return null ;
525
507
}
526
508
527
- var dependencies = selectionAnalysis . ComponentDependencies is not null
528
- ? string . Join ( Environment . NewLine , selectionAnalysis . ComponentDependencies )
529
- : string . Empty ;
509
+ var inst = PooledStringBuilder . GetInstance ( ) ;
510
+ var newFileContentBuilder = inst . Builder ;
511
+ if ( selectionAnalysis . ComponentDependencies is not null )
512
+ {
513
+ foreach ( var dependency in selectionAnalysis . ComponentDependencies )
514
+ {
515
+ newFileContentBuilder . AppendLine ( $ "@using { dependency } ") ;
516
+ }
530
517
531
- var extractedContents = contents . GetSubTextString ( new CodeAnalysis . Text . TextSpan ( selectionAnalysis . ExtractStart , selectionAnalysis . ExtractEnd - selectionAnalysis . ExtractStart ) ) . Trim ( ) ;
532
- var newFileContent = $ "{ dependencies } { ( dependencies . Length > 0 ? Environment . NewLine + Environment . NewLine : "" ) } { extractedContents } ";
518
+ if ( newFileContentBuilder . Length > 0 )
519
+ {
520
+ newFileContentBuilder . AppendLine ( ) ;
521
+ }
522
+ }
523
+
524
+ var extractedContents = contents . GetSubTextString (
525
+ new TextSpan ( selectionAnalysis . ExtractStart ,
526
+ selectionAnalysis . ExtractEnd - selectionAnalysis . ExtractStart ) )
527
+ . Trim ( ) ;
528
+
529
+ newFileContentBuilder . Append ( extractedContents ) ;
533
530
534
531
// Get CSharpStatements within component
535
532
var syntaxTree = razorCodeDocument . GetSyntaxTree ( ) ;
536
533
var cSharpCodeBlocks = GetCSharpCodeBlocks ( syntaxTree , selectionAnalysis . ExtractStart , selectionAnalysis . ExtractEnd ) ;
537
534
538
535
var result = new NewRazorComponentInfo
539
536
{
540
- NewContents = newFileContent ,
537
+ NewContents = newFileContentBuilder . ToString ( ) ,
541
538
Methods = [ ]
542
539
} ;
543
540
544
541
// Only make the Roslyn call if there is valid CSharp in the selected code.
545
542
if ( cSharpCodeBlocks . Count == 0 )
546
543
{
544
+ inst . Free ( ) ;
547
545
return result ;
548
546
}
549
547
550
548
if ( ! _documentVersionCache . TryGetDocumentVersion ( documentContext . Snapshot , out var version ) )
551
549
{
550
+ inst . Free ( ) ;
552
551
return result ;
553
552
}
554
553
@@ -586,11 +585,6 @@ metadata.Value is not null &&
586
585
{
587
586
Uri = componentUri
588
587
} ,
589
- NewDocument = new TextDocumentIdentifier
590
- {
591
- Uri = newComponentUri
592
- } ,
593
- NewContents = newFileContent ,
594
588
HostDocumentVersion = version . Value ,
595
589
IntersectingRangesInGeneratedMappings = intersectingGeneratedRanges
596
590
} ;
@@ -610,18 +604,21 @@ metadata.Value is not null &&
610
604
611
605
if ( componentInfo is null )
612
606
{
607
+ inst . Free ( ) ;
613
608
return result ;
614
609
}
615
610
616
611
var codeBlockAtEnd = GetCodeBlockAtEnd ( syntaxTree ) ;
617
612
if ( codeBlockAtEnd is null )
618
613
{
614
+ inst . Free ( ) ;
619
615
return result ;
620
616
}
621
617
622
618
var identifiersInCodeBlock = GetIdentifiersInContext ( codeBlockAtEnd , cSharpCodeBlocks ) ;
623
619
if ( componentInfo . Methods is null )
624
620
{
621
+ inst . Free ( ) ;
625
622
return result ;
626
623
}
627
624
@@ -635,12 +632,13 @@ metadata.Value is not null &&
635
632
636
633
var newFileCodeBlock = GenerateNewFileCodeBlock ( promotedMethods , forwardedFields ) ;
637
634
638
- newFileContent = ReplaceMethodInvocations ( newFileContent , methodsInContext ) ;
639
- newFileContent += newFileCodeBlock ;
635
+ ReplaceMethodInvocations ( newFileContentBuilder , methodsInContext ) ;
636
+ newFileContentBuilder . Append ( newFileCodeBlock ) ;
640
637
641
- result . NewContents = newFileContent ;
638
+ result . NewContents = newFileContentBuilder . ToString ( ) ;
642
639
result . Methods = methodsInContext ;
643
640
641
+ inst . Free ( ) ;
644
642
return result ;
645
643
}
646
644
@@ -852,17 +850,15 @@ private static string GenerateNewFileCodeBlock(string promotedMethods, string ca
852
850
return builder . ToString ( ) ;
853
851
}
854
852
855
- // Method invocations in the new file must be replaced with their respective parameter name. This is simply a case of replacing each string.
856
- private static string ReplaceMethodInvocations ( string newFileContent , HashSet < MethodSymbolicInfo > methods )
853
+ // Method invocations in the new file must be replaced with their respective parameter name.
854
+ private static void ReplaceMethodInvocations ( StringBuilder newFileContentBuilder , HashSet < MethodSymbolicInfo > methods )
857
855
{
858
856
var parameterCount = 0 ;
859
857
foreach ( var method in methods )
860
858
{
861
- newFileContent = newFileContent . Replace ( method . Name , $ "Parameter{ ( parameterCount > 0 ? parameterCount : "" ) } ") ;
859
+ newFileContentBuilder . Replace ( method . Name , $ "Parameter{ ( parameterCount > 0 ? parameterCount : "" ) } ") ;
862
860
parameterCount ++ ;
863
861
}
864
-
865
- return newFileContent ;
866
862
}
867
863
868
864
private static string GenerateComponentNameAndParameters ( HashSet < MethodSymbolicInfo > ? methods , string componentName )
0 commit comments