38
38
using Microsoft . VisualStudio . Text ;
39
39
using ICSharpCode . Decompiler . CSharp . Syntax ;
40
40
using Microsoft . CodeAnalysis . Razor . DocumentMapping ;
41
+ using System . Reflection . Metadata . Ecma335 ;
41
42
42
43
namespace Microsoft . AspNetCore . Razor . LanguageServer . CodeActions ;
43
44
@@ -323,13 +324,14 @@ private static bool TryProcessSelection(MarkupSyntaxNode startElementNode, Marku
323
324
// This conditional handles cases where the user's selection spans across different levels of the DOM.
324
325
// For example:
325
326
// <div>
326
- // <span>
327
- // Selected text starts here <p>Some text</p>
327
+ // {|result: <span>
328
+ // {|selection: <p>Some text</p>
328
329
// </span>
329
330
// <span>
330
331
// <p>More text</p>
331
332
// </span>
332
- // Selected text ends here <span></span>
333
+ // <span>|}|}
334
+ // </span>
333
335
// </div>
334
336
// In this case, we need to find the smallest set of complete elements that covers the entire selection.
335
337
if ( startElementNode != endElementNode )
@@ -551,7 +553,6 @@ metadata.Value is not null &&
551
553
}
552
554
553
555
var sourceMappings = razorCodeDocument . GetCSharpDocument ( ) . SourceMappings ;
554
-
555
556
var sourceMappingRanges = sourceMappings . Select ( m =>
556
557
(
557
558
new Range
@@ -564,20 +565,17 @@ metadata.Value is not null &&
564
565
565
566
var relevantTextSpan = relevantRange . ToTextSpan ( razorCodeDocument . Source . Text ) ;
566
567
var intersectingGeneratedSpans = sourceMappingRanges . Where ( m => relevantRange . IntersectsOrTouches ( m . Item1 ) ) . Select ( m => m . GeneratedSpan ) . ToArray ( ) ;
568
+
569
+ // I'm not sure why, but for some reason the endCharacterIndex is lower than the CharacterIndex so they must be swapped.
567
570
var intersectingGeneratedRanges = intersectingGeneratedSpans . Select ( m =>
568
571
(
569
572
new Range
570
573
{
571
- Start = new Position ( m . LineIndex , m . CharacterIndex ) ,
574
+ Start = new Position ( m . LineIndex , m . EndCharacterIndex ) ,
572
575
End = new Position ( m . LineIndex , m . CharacterIndex )
573
576
}
574
577
) ) . ToArray ( ) ;
575
578
576
- if ( ! TryMapToClosestGeneratedDocumentRange ( razorCodeDocument . GetCSharpDocument ( ) , selectionAnalysis . ExtractStart , selectionAnalysis . ExtractEnd , out var selectionMappedRange ) )
577
- {
578
- return result ;
579
- }
580
-
581
579
var parameters = new GetSymbolicInfoParams ( )
582
580
{
583
581
Project = new TextDocumentIdentifier
@@ -594,8 +592,7 @@ metadata.Value is not null &&
594
592
} ,
595
593
NewContents = newFileContent ,
596
594
HostDocumentVersion = version . Value ,
597
- MappedRange = selectionMappedRange ,
598
- IntersectingSpansInGeneratedMappings = intersectingGeneratedRanges
595
+ IntersectingRangesInGeneratedMappings = intersectingGeneratedRanges
599
596
} ;
600
597
601
598
SymbolicInfo ? componentInfo ;
@@ -611,7 +608,6 @@ metadata.Value is not null &&
611
608
throw new InvalidOperationException ( "Failed to send request to RazorComponentInfoEndpoint" , ex ) ;
612
609
}
613
610
614
- // Check if client connection call was successful
615
611
if ( componentInfo is null )
616
612
{
617
613
return result ;
@@ -624,7 +620,6 @@ metadata.Value is not null &&
624
620
}
625
621
626
622
var identifiersInCodeBlock = GetIdentifiersInContext ( codeBlockAtEnd , cSharpCodeBlocks ) ;
627
-
628
623
if ( componentInfo . Methods is null )
629
624
{
630
625
return result ;
@@ -635,8 +630,9 @@ metadata.Value is not null &&
635
630
var methodsInContext = GetMethodsInContext ( componentInfo , methodStringsInContext ) ;
636
631
var promotedMethods = GeneratePromotedMethods ( methodsInContext ) ;
637
632
638
- var fieldsInContext = GetFieldsInContext ( componentInfo , identifiersInCodeBlock ) ;
639
- var forwardedFields = GenerateForwardedConstantFields ( codeBlockAtEnd , fieldsInContext ) ;
633
+ var fieldsInContext = GetFieldsInContext ( componentInfo . Fields , identifiersInCodeBlock ) ;
634
+ var forwardedFields = GenerateForwardedConstantFields ( fieldsInContext , Path . GetFileName ( razorCodeDocument . Source . FilePath ) ) ;
635
+
640
636
var newFileCodeBlock = GenerateNewFileCodeBlock ( promotedMethods , forwardedFields ) ;
641
637
642
638
newFileContent = ReplaceMethodInvocations ( newFileContent , methodsInContext ) ;
@@ -669,44 +665,6 @@ private static List<CSharpCodeBlockSyntax> GetCSharpCodeBlocks(RazorSyntaxTree s
669
665
return cSharpCodeBlocks ;
670
666
}
671
667
672
- private static bool TryMapToClosestGeneratedDocumentRange ( RazorCSharpDocument generatedDocument , int start , int end , out Range mappedRange )
673
- {
674
- var sourceMappings = generatedDocument . SourceMappings ;
675
-
676
- var closestStartSourceMap = sourceMappings . OrderBy ( m => Math . Abs ( m . OriginalSpan . AbsoluteIndex - start ) ) . First ( ) ;
677
- var closestEndSourceMap = sourceMappings . OrderBy ( m => Math . Abs ( m . OriginalSpan . AbsoluteIndex - end ) ) . First ( ) ;
678
-
679
- var generatedStart = closestStartSourceMap . GeneratedSpan ;
680
- var generatedEnd = closestEndSourceMap . GeneratedSpan ;
681
-
682
- var generatedStartLinePosition = GetGeneratedPosition ( generatedDocument , generatedStart . AbsoluteIndex ) ;
683
- var generatedEndLinePosition = GetGeneratedPosition ( generatedDocument , generatedEnd . AbsoluteIndex ) ;
684
-
685
- mappedRange = new Range
686
- {
687
- Start = new Position ( generatedStartLinePosition . Line , generatedStartLinePosition . Character ) ,
688
- End = new Position ( generatedEndLinePosition . Line , generatedEndLinePosition . Character )
689
- } ;
690
-
691
- LinePosition GetGeneratedPosition ( IRazorGeneratedDocument generatedDocument , int generatedIndex )
692
- {
693
- var generatedSource = GetGeneratedSourceText ( generatedDocument ) ;
694
- return generatedSource . Lines . GetLinePosition ( generatedIndex ) ;
695
- }
696
-
697
- SourceText GetGeneratedSourceText ( IRazorGeneratedDocument generatedDocument )
698
- {
699
- if ( generatedDocument . CodeDocument is not { } codeDocument )
700
- {
701
- throw new InvalidOperationException ( "Cannot use document mapping service on a generated document that has a null CodeDocument." ) ;
702
- }
703
-
704
- return codeDocument . GetGeneratedSourceText ( generatedDocument ) ;
705
- }
706
-
707
- return true ;
708
- }
709
-
710
668
// Get identifiers in code block to union with the identifiers in the extracted code
711
669
private static HashSet < string > GetIdentifiersInContext ( SyntaxNode codeBlockAtEnd , List < CSharpCodeBlockSyntax > previousCodeBlocks )
712
670
{
@@ -745,9 +703,9 @@ private static HashSet<string> GetIdentifiersInContext(SyntaxNode codeBlockAtEnd
745
703
return identifiersInLastCodeBlock ;
746
704
}
747
705
748
- private static HashSet < MethodInRazorInfo > GetMethodsInContext ( SymbolicInfo componentInfo , IEnumerable < string > methodStringsInContext )
706
+ private static HashSet < MethodSymbolicInfo > GetMethodsInContext ( SymbolicInfo componentInfo , IEnumerable < string > methodStringsInContext )
749
707
{
750
- var methodsInContext = new HashSet < MethodInRazorInfo > ( ) ;
708
+ var methodsInContext = new HashSet < MethodSymbolicInfo > ( ) ;
751
709
if ( componentInfo . Methods is null )
752
710
{
753
711
return methodsInContext ;
@@ -782,7 +740,7 @@ private static HashSet<MethodInRazorInfo> GetMethodsInContext(SymbolicInfo compo
782
740
// Create a series of [Parameter] attributes for extracted methods.
783
741
// Void return functions are promoted to Action<T> delegates.
784
742
// All other functions should be Func<T... TResult> delegates.
785
- private static string GeneratePromotedMethods ( HashSet < MethodInRazorInfo > methods )
743
+ private static string GeneratePromotedMethods ( HashSet < MethodSymbolicInfo > methods )
786
744
{
787
745
var builder = new StringBuilder ( ) ;
788
746
var parameterCount = 0 ;
@@ -838,34 +796,48 @@ private static string GeneratePromotedMethods(HashSet<MethodInRazorInfo> methods
838
796
return builder . ToString ( ) ;
839
797
}
840
798
841
- private static string GenerateForwardedConstantFields ( SyntaxNode codeBlockAtEnd , HashSet < string > relevantFields )
799
+ private static HashSet < FieldSymbolicInfo > GetFieldsInContext ( FieldSymbolicInfo [ ] fields , HashSet < string > identifiersInCodeBlock )
842
800
{
843
- var builder = new StringBuilder ( ) ;
801
+ if ( fields is null )
802
+ {
803
+ return [ ] ;
804
+ }
844
805
845
- var codeBlockString = codeBlockAtEnd . ToFullString ( ) ;
806
+ var fieldsInContext = new HashSet < FieldSymbolicInfo > ( ) ;
846
807
847
- var lines = codeBlockString . Split ( '\n ' ) ;
848
- foreach ( var line in lines )
808
+ foreach ( var fieldInfo in fields )
849
809
{
850
- if ( relevantFields . Any ( field => line . Contains ( field ) ) )
810
+ if ( identifiersInCodeBlock . Contains ( fieldInfo . Name ) )
851
811
{
852
- builder . AppendLine ( line . Trim ( ) ) ;
812
+ fieldsInContext . Add ( fieldInfo ) ;
853
813
}
854
814
}
855
815
856
- return builder . ToString ( ) ;
816
+ return fieldsInContext ;
857
817
}
858
818
859
- // GetFieldsInContext(componentInfo, identifiersInCodeBlock)
860
- private static HashSet < string > GetFieldsInContext ( SymbolicInfo componentInfo , HashSet < string > identifiersInCodeBlock )
819
+ private static string GenerateForwardedConstantFields ( HashSet < FieldSymbolicInfo > relevantFields , string ? sourceDocumentFileName )
861
820
{
862
- if ( componentInfo . Fields is null )
821
+ var builder = new StringBuilder ( ) ;
822
+ var fieldCount = 0 ;
823
+ var totalFields = relevantFields . Count ;
824
+
825
+ foreach ( var field in relevantFields )
863
826
{
864
- return [ ] ;
827
+ if ( field . IsValueType || field . Type == "string" )
828
+ {
829
+ builder . AppendLine ( $ "// Warning: Field '{ field . Name } ' was passed by value and may not be referenced correctly. Please check its usage in the original document: '{ sourceDocumentFileName } '.") ;
830
+ }
831
+
832
+ builder . AppendLine ( $ "public { field . Type } { field . Name } ") ;
833
+
834
+ if ( fieldCount < totalFields - 1 )
835
+ {
836
+ builder . AppendLine ( ) ;
837
+ }
865
838
}
866
839
867
- var identifiersInFile = componentInfo . Fields . Select ( field => field . Name ) . ToHashSet ( ) ;
868
- return identifiersInFile . Intersect ( identifiersInCodeBlock ) . ToHashSet ( ) ;
840
+ return builder . ToString ( ) ;
869
841
}
870
842
871
843
private static string GenerateNewFileCodeBlock ( string promotedMethods , string carryoverFields )
@@ -881,7 +853,7 @@ private static string GenerateNewFileCodeBlock(string promotedMethods, string ca
881
853
}
882
854
883
855
// Method invocations in the new file must be replaced with their respective parameter name. This is simply a case of replacing each string.
884
- private static string ReplaceMethodInvocations ( string newFileContent , HashSet < MethodInRazorInfo > methods )
856
+ private static string ReplaceMethodInvocations ( string newFileContent , HashSet < MethodSymbolicInfo > methods )
885
857
{
886
858
var parameterCount = 0 ;
887
859
foreach ( var method in methods )
@@ -893,7 +865,7 @@ private static string ReplaceMethodInvocations(string newFileContent, HashSet<Me
893
865
return newFileContent ;
894
866
}
895
867
896
- private static string GenerateComponentNameAndParameters ( HashSet < MethodInRazorInfo > ? methods , string componentName )
868
+ private static string GenerateComponentNameAndParameters ( HashSet < MethodSymbolicInfo > ? methods , string componentName )
897
869
{
898
870
var builder = new StringBuilder ( ) ;
899
871
builder . Append ( componentName + " " ) ;
@@ -918,6 +890,6 @@ private static string GenerateComponentNameAndParameters(HashSet<MethodInRazorIn
918
890
internal sealed record NewRazorComponentInfo
919
891
{
920
892
public required string NewContents { get ; set ; }
921
- public required HashSet < MethodInRazorInfo > ? Methods { get ; set ; }
893
+ public required HashSet < MethodSymbolicInfo > ? Methods { get ; set ; }
922
894
}
923
895
}
0 commit comments