Skip to content

Commit e42ee5f

Browse files
committed
Modified forwared constant fields
1 parent 41f0ce1 commit e42ee5f

File tree

2 files changed

+55
-85
lines changed

2 files changed

+55
-85
lines changed

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

Lines changed: 46 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
using Microsoft.VisualStudio.Text;
3939
using ICSharpCode.Decompiler.CSharp.Syntax;
4040
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
41+
using System.Reflection.Metadata.Ecma335;
4142

4243
namespace Microsoft.AspNetCore.Razor.LanguageServer.CodeActions;
4344

@@ -323,13 +324,14 @@ private static bool TryProcessSelection(MarkupSyntaxNode startElementNode, Marku
323324
// This conditional handles cases where the user's selection spans across different levels of the DOM.
324325
// For example:
325326
// <div>
326-
// <span>
327-
// Selected text starts here<p>Some text</p>
327+
// {|result:<span>
328+
// {|selection:<p>Some text</p>
328329
// </span>
329330
// <span>
330331
// <p>More text</p>
331332
// </span>
332-
// Selected text ends here <span></span>
333+
// <span>|}|}
334+
// </span>
333335
// </div>
334336
// In this case, we need to find the smallest set of complete elements that covers the entire selection.
335337
if (startElementNode != endElementNode)
@@ -551,7 +553,6 @@ metadata.Value is not null &&
551553
}
552554

553555
var sourceMappings = razorCodeDocument.GetCSharpDocument().SourceMappings;
554-
555556
var sourceMappingRanges = sourceMappings.Select(m =>
556557
(
557558
new Range
@@ -564,20 +565,17 @@ metadata.Value is not null &&
564565

565566
var relevantTextSpan = relevantRange.ToTextSpan(razorCodeDocument.Source.Text);
566567
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.
567570
var intersectingGeneratedRanges = intersectingGeneratedSpans.Select(m =>
568571
(
569572
new Range
570573
{
571-
Start = new Position(m.LineIndex, m.CharacterIndex),
574+
Start = new Position(m.LineIndex, m.EndCharacterIndex),
572575
End = new Position(m.LineIndex, m.CharacterIndex)
573576
}
574577
)).ToArray();
575578

576-
if (!TryMapToClosestGeneratedDocumentRange(razorCodeDocument.GetCSharpDocument(), selectionAnalysis.ExtractStart, selectionAnalysis.ExtractEnd, out var selectionMappedRange))
577-
{
578-
return result;
579-
}
580-
581579
var parameters = new GetSymbolicInfoParams()
582580
{
583581
Project = new TextDocumentIdentifier
@@ -594,8 +592,7 @@ metadata.Value is not null &&
594592
},
595593
NewContents = newFileContent,
596594
HostDocumentVersion = version.Value,
597-
MappedRange = selectionMappedRange,
598-
IntersectingSpansInGeneratedMappings = intersectingGeneratedRanges
595+
IntersectingRangesInGeneratedMappings = intersectingGeneratedRanges
599596
};
600597

601598
SymbolicInfo? componentInfo;
@@ -611,7 +608,6 @@ metadata.Value is not null &&
611608
throw new InvalidOperationException("Failed to send request to RazorComponentInfoEndpoint", ex);
612609
}
613610

614-
// Check if client connection call was successful
615611
if (componentInfo is null)
616612
{
617613
return result;
@@ -624,7 +620,6 @@ metadata.Value is not null &&
624620
}
625621

626622
var identifiersInCodeBlock = GetIdentifiersInContext(codeBlockAtEnd, cSharpCodeBlocks);
627-
628623
if (componentInfo.Methods is null)
629624
{
630625
return result;
@@ -635,8 +630,9 @@ metadata.Value is not null &&
635630
var methodsInContext = GetMethodsInContext(componentInfo, methodStringsInContext);
636631
var promotedMethods = GeneratePromotedMethods(methodsInContext);
637632

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+
640636
var newFileCodeBlock = GenerateNewFileCodeBlock(promotedMethods, forwardedFields);
641637

642638
newFileContent = ReplaceMethodInvocations(newFileContent, methodsInContext);
@@ -669,44 +665,6 @@ private static List<CSharpCodeBlockSyntax> GetCSharpCodeBlocks(RazorSyntaxTree s
669665
return cSharpCodeBlocks;
670666
}
671667

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-
710668
// Get identifiers in code block to union with the identifiers in the extracted code
711669
private static HashSet<string> GetIdentifiersInContext(SyntaxNode codeBlockAtEnd, List<CSharpCodeBlockSyntax> previousCodeBlocks)
712670
{
@@ -745,9 +703,9 @@ private static HashSet<string> GetIdentifiersInContext(SyntaxNode codeBlockAtEnd
745703
return identifiersInLastCodeBlock;
746704
}
747705

748-
private static HashSet<MethodInRazorInfo> GetMethodsInContext(SymbolicInfo componentInfo, IEnumerable<string> methodStringsInContext)
706+
private static HashSet<MethodSymbolicInfo> GetMethodsInContext(SymbolicInfo componentInfo, IEnumerable<string> methodStringsInContext)
749707
{
750-
var methodsInContext = new HashSet<MethodInRazorInfo>();
708+
var methodsInContext = new HashSet<MethodSymbolicInfo>();
751709
if (componentInfo.Methods is null)
752710
{
753711
return methodsInContext;
@@ -782,7 +740,7 @@ private static HashSet<MethodInRazorInfo> GetMethodsInContext(SymbolicInfo compo
782740
// Create a series of [Parameter] attributes for extracted methods.
783741
// Void return functions are promoted to Action<T> delegates.
784742
// 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)
786744
{
787745
var builder = new StringBuilder();
788746
var parameterCount = 0;
@@ -838,34 +796,48 @@ private static string GeneratePromotedMethods(HashSet<MethodInRazorInfo> methods
838796
return builder.ToString();
839797
}
840798

841-
private static string GenerateForwardedConstantFields(SyntaxNode codeBlockAtEnd, HashSet<string> relevantFields)
799+
private static HashSet<FieldSymbolicInfo> GetFieldsInContext(FieldSymbolicInfo[] fields, HashSet<string> identifiersInCodeBlock)
842800
{
843-
var builder = new StringBuilder();
801+
if (fields is null)
802+
{
803+
return [];
804+
}
844805

845-
var codeBlockString = codeBlockAtEnd.ToFullString();
806+
var fieldsInContext = new HashSet<FieldSymbolicInfo>();
846807

847-
var lines = codeBlockString.Split('\n');
848-
foreach (var line in lines)
808+
foreach (var fieldInfo in fields)
849809
{
850-
if (relevantFields.Any(field => line.Contains(field)))
810+
if (identifiersInCodeBlock.Contains(fieldInfo.Name))
851811
{
852-
builder.AppendLine(line.Trim());
812+
fieldsInContext.Add(fieldInfo);
853813
}
854814
}
855815

856-
return builder.ToString();
816+
return fieldsInContext;
857817
}
858818

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)
861820
{
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)
863826
{
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+
}
865838
}
866839

867-
var identifiersInFile = componentInfo.Fields.Select(field => field.Name).ToHashSet();
868-
return identifiersInFile.Intersect(identifiersInCodeBlock).ToHashSet();
840+
return builder.ToString();
869841
}
870842

871843
private static string GenerateNewFileCodeBlock(string promotedMethods, string carryoverFields)
@@ -881,7 +853,7 @@ private static string GenerateNewFileCodeBlock(string promotedMethods, string ca
881853
}
882854

883855
// 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)
885857
{
886858
var parameterCount = 0;
887859
foreach (var method in methods)
@@ -893,7 +865,7 @@ private static string ReplaceMethodInvocations(string newFileContent, HashSet<Me
893865
return newFileContent;
894866
}
895867

896-
private static string GenerateComponentNameAndParameters(HashSet<MethodInRazorInfo>? methods, string componentName)
868+
private static string GenerateComponentNameAndParameters(HashSet<MethodSymbolicInfo>? methods, string componentName)
897869
{
898870
var builder = new StringBuilder();
899871
builder.Append(componentName + " ");
@@ -918,6 +890,6 @@ private static string GenerateComponentNameAndParameters(HashSet<MethodInRazorIn
918890
internal sealed record NewRazorComponentInfo
919891
{
920892
public required string NewContents { get; set; }
921-
public required HashSet<MethodInRazorInfo>? Methods { get; set; }
893+
public required HashSet<MethodSymbolicInfo>? Methods { get; set; }
922894
}
923895
}

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/CodeActions/GetSymbolicInfoParams.cs

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,19 @@ internal record GetSymbolicInfoParams
3131
[JsonPropertyName("newContents")]
3232
public required string NewContents { get; set; }
3333

34-
[DataMember(Name = "mappedRange")]
35-
[JsonPropertyName("mappedRange")]
36-
public required Range MappedRange { get; set; }
34+
[DataMember(Name = "intersectingRangesInGeneratedMappings")]
35+
[JsonPropertyName("intersectingRangesInGeneratedMappings")]
3736

38-
[DataMember(Name = "intersectingSpansInGeneratedRange")]
39-
[JsonPropertyName("intersectingSpansInGeneratedRange")]
40-
41-
public required Range[] IntersectingSpansInGeneratedMappings { get; set; }
37+
public required Range[] IntersectingRangesInGeneratedMappings { get; set; }
4238
}
4339

4440
internal sealed record SymbolicInfo
4541
{
46-
public required MethodInRazorInfo[] Methods { get; set; }
47-
public required SymbolInRazorInfo[] Fields { get; set; }
42+
public required MethodSymbolicInfo[] Methods { get; set; }
43+
public required FieldSymbolicInfo[] Fields { get; set; }
4844
}
4945

50-
internal sealed record MethodInRazorInfo
46+
internal sealed record MethodSymbolicInfo
5147
{
5248
public required string Name { get; set; }
5349

@@ -56,8 +52,10 @@ internal sealed record MethodInRazorInfo
5652
public required string[] ParameterTypes { get; set; }
5753
}
5854

59-
internal sealed record SymbolInRazorInfo
55+
internal sealed record FieldSymbolicInfo
6056
{
6157
public required string Name { get; set; }
6258
public required string Type { get; set; }
59+
public required bool IsValueType { get; set; }
60+
public required bool IsWrittenTo { get; set; }
6361
}

0 commit comments

Comments
 (0)