Skip to content

Commit 68fdf30

Browse files
committed
Adapted for roslyn endpoint rename, and added tests
1 parent 6929f9e commit 68fdf30

File tree

9 files changed

+295
-482
lines changed

9 files changed

+295
-482
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public Task<ImmutableArray<RazorVSInternalCodeAction>> ProvideAsync(RazorCodeAct
6262

6363
var resolutionParams = new RazorCodeActionResolutionParams()
6464
{
65-
Action = LanguageServerConstants.CodeActions.ExtractToNewComponentAction,
65+
Action = LanguageServerConstants.CodeActions.ExtractToComponentAction,
6666
Language = LanguageServerConstants.CodeActions.Languages.Razor,
6767
Data = actionParams,
6868
};

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

Lines changed: 128 additions & 38 deletions
Large diffs are not rendered by default.
Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5-
using System.Collections.Generic;
65
using System.Runtime.Serialization;
76
using System.Text.Json.Serialization;
87
using Microsoft.VisualStudio.LanguageServer.Protocol;
98

109
namespace Microsoft.CodeAnalysis.Razor.Protocol.CodeActions;
1110

12-
internal sealed record RazorComponentInfoParams
11+
[DataContract]
12+
internal record GetSymbolicInfoParams
1313
{
1414
[DataMember(Name = "document")]
1515
[JsonPropertyName("document")]
@@ -30,25 +30,33 @@ internal sealed record RazorComponentInfoParams
3030
[DataMember(Name = "newContents")]
3131
[JsonPropertyName("newContents")]
3232
public required string NewContents { get; set; }
33+
34+
[DataMember(Name = "mappedRange")]
35+
[JsonPropertyName("mappedRange")]
36+
public required Range MappedRange { get; set; }
37+
38+
[DataMember(Name = "intersectingSpansInGeneratedRange")]
39+
[JsonPropertyName("intersectingSpansInGeneratedRange")]
40+
41+
public required Range[] IntersectingSpansInGeneratedMappings { get; set; }
3342
}
3443

35-
// Not sure where to put these two records
36-
internal sealed record RazorComponentInfo
44+
internal sealed record SymbolicInfo
3745
{
38-
public required List<MethodInsideRazorElementInfo> Methods { get; set; }
39-
public required List<SymbolInsideRazorElementInfo> Fields { get; set; }
46+
public required MethodInRazorInfo[] Methods { get; set; }
47+
public required SymbolInRazorInfo[] Fields { get; set; }
4048
}
4149

42-
internal sealed record MethodInsideRazorElementInfo
50+
internal sealed record MethodInRazorInfo
4351
{
4452
public required string Name { get; set; }
4553

4654
public required string ReturnType { get; set; }
4755

48-
public required List<string> ParameterTypes { get; set; }
56+
public required string[] ParameterTypes { get; set; }
4957
}
5058

51-
internal sealed record SymbolInsideRazorElementInfo
59+
internal sealed record SymbolInRazorInfo
5260
{
5361
public required string Name { get; set; }
5462
public required string Type { get; set; }

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/CustomMessageNames.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace Microsoft.CodeAnalysis.Razor.Protocol;
1313
internal static class CustomMessageNames
1414
{
1515
// VS Windows only
16-
public const string RazorComponentInfoEndpointName = "razor/razorComponentInfo";
16+
public const string RazorGetSymbolicInfoEndpointName = "razor/getSymbolicInfo";
1717
public const string RazorInlineCompletionEndpoint = "razor/inlineCompletion";
1818
public const string RazorValidateBreakpointRangeName = "razor/validateBreakpointRange";
1919
public const string RazorOnAutoInsertEndpointName = "razor/onAutoInsert";

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/LanguageServerConstants.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public static class CodeActions
3737

3838
public const string ExtractToCodeBehindAction = "ExtractToCodeBehind";
3939

40-
public const string ExtractToNewComponentAction = "ExtractToNewComponent";
40+
public const string ExtractToComponentAction = "ExtractToComponent";
4141

4242
public const string CreateComponentFromTag = "CreateComponentFromTag";
4343

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Endpoints;
1313

1414
internal partial class RazorCustomMessageTarget
1515
{
16-
[JsonRpcMethod(CustomMessageNames.RazorComponentInfoEndpointName, UseSingleObjectParameterDeserialization = true)]
17-
public async Task<RazorComponentInfo?> RazorComponentInfoAsync(RazorComponentInfoParams request, CancellationToken cancellationToken)
16+
[JsonRpcMethod(CustomMessageNames.RazorGetSymbolicInfoEndpointName, UseSingleObjectParameterDeserialization = true)]
17+
public async Task<SymbolicInfo?> RazorGetSymbolicInfoAsync(GetSymbolicInfoParams request, CancellationToken cancellationToken)
1818
{
1919
var (synchronized, virtualDocumentSnapshot) = await TrySynchronizeVirtualDocumentAsync<CSharpVirtualDocumentSnapshot>(request.HostDocumentVersion, request.Document, cancellationToken);
2020
if (!synchronized || virtualDocumentSnapshot is null)
@@ -23,14 +23,12 @@ internal partial class RazorCustomMessageTarget
2323
}
2424

2525
request.Document.Uri = virtualDocumentSnapshot.Uri;
26+
ReinvokeResponse<SymbolicInfo?> response;
2627

27-
ReinvokeResponse<RazorComponentInfo?> response;
28-
29-
// This endpoint is special because it deals with a file that doesn't exist yet, so there is no document syncing necessary!
3028
try
3129
{
32-
response = await _requestInvoker.ReinvokeRequestOnServerAsync<RazorComponentInfoParams, RazorComponentInfo?>(
33-
RazorLSPConstants.RoslynRazorComponentInfoEndpointName,
30+
response = await _requestInvoker.ReinvokeRequestOnServerAsync<GetSymbolicInfoParams, SymbolicInfo?>(
31+
RazorLSPConstants.RoslynGetSymbolicInfoEndpointName,
3432
RazorLSPConstants.RazorCSharpLanguageServerName,
3533
request,
3634
cancellationToken).ConfigureAwait(false);

src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/RazorLSPConstants.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ internal static class RazorLSPConstants
2727

2828
public const string RoslynFormatNewFileEndpointName = "roslyn/formatNewFile";
2929

30-
public const string RoslynRazorComponentInfoEndpointName = "roslyn/razorComponentInfo";
30+
public const string RoslynGetSymbolicInfoEndpointName = "roslyn/getSymbolicInfo";
3131

3232
public const string RoslynSemanticTokenRangesEndpointName = "roslyn/semanticTokenRanges";
3333
}

src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CodeActionEndToEndTest.NetFx.cs

Lines changed: 141 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -9,33 +9,35 @@
99
using System.Linq;
1010
using System.Threading;
1111
using System.Threading.Tasks;
12-
using Castle.Core.Logging;
1312
using Microsoft.AspNetCore.Razor.Language;
1413
using Microsoft.AspNetCore.Razor.Language.Components;
1514
using Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Models;
1615
using Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Razor;
1716
using Microsoft.AspNetCore.Razor.LanguageServer.EndpointContracts;
1817
using Microsoft.AspNetCore.Razor.LanguageServer.Formatting;
1918
using Microsoft.AspNetCore.Razor.LanguageServer.Hosting;
20-
using Microsoft.AspNetCore.Razor.LanguageServer.Test;
2119
using Microsoft.AspNetCore.Razor.ProjectSystem;
20+
using Microsoft.AspNetCore.Razor.Test.Common;
2221
using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer;
2322
using Microsoft.AspNetCore.Razor.Test.Common.ProjectSystem;
2423
using Microsoft.AspNetCore.Razor.Test.Common.Workspaces;
2524
using Microsoft.AspNetCore.Razor.Utilities;
2625
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
2726
using Microsoft.CodeAnalysis.Razor;
27+
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
2828
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
2929
using Microsoft.CodeAnalysis.Razor.Protocol;
3030
using Microsoft.CodeAnalysis.Razor.Protocol.CodeActions;
3131
using Microsoft.CodeAnalysis.Razor.Workspaces;
3232
using Microsoft.CodeAnalysis.Testing;
3333
using Microsoft.CodeAnalysis.Text;
34+
using Microsoft.VisualStudio.Copilot.Internal;
3435
using Microsoft.VisualStudio.LanguageServer.Protocol;
36+
using Moq;
3537
using Roslyn.Test.Utilities;
3638
using Xunit;
3739
using Xunit.Abstractions;
38-
using static Microsoft.AspNetCore.Razor.LanguageServer.Formatting.FormattingLanguageServerTestBase;
40+
using Xunit.Sdk;
3941

4042
namespace Microsoft.AspNetCore.Razor.LanguageServer.CodeActions;
4143

@@ -64,26 +66,27 @@ private GenerateMethodCodeActionResolver[] CreateRazorCodeActionResolvers(
6466
razorFormattingService)
6567
];
6668

67-
// TODO: Make this func
68-
//private ExtractToComponentCodeActionResolver[] CreateExtractComponentCodeActionResolvers(string filePath, RazorCodeDocument codeDocument)
69-
//{
70-
// var emptyDocumentContextFactory = new TestDocumentContextFactory();
71-
// var languageServer = new Test.TestLanguageServer(new Dictionary<string, Func<object?, Task<object>>>()
72-
// {
73-
// [CustomMessageNames.RazorFormatNewFileEndpointName] = c => Task.FromResult<object>(null!),
74-
// });
75-
// var razorLSPOptionsMonitor = TestRazorLSPOptionsMonitor.Create();
76-
// var testFormattingService = await TestRazorFormattingService.CreateWithFullSupportAsync(LoggerFactory);
77-
//return [
78-
// new ExtractToComponentCodeActionResolver(
79-
// new GenerateMethodResolverDocumentContextFactory(filePath, codeDocument),
80-
// razorLSPOptionsMonitor,
81-
// TestLanguageServerFeatureOptions.Instance,
82-
// languageServer,
83-
// testFormattingService,
84-
// new DocumentVersionCacheTest())
85-
// ];
86-
//}
69+
private ExtractToComponentCodeActionResolver[] CreateExtractComponentCodeActionResolver(
70+
string filePath,
71+
RazorCodeDocument codeDocument,
72+
IRazorFormattingService razorFormattingService,
73+
IClientConnection clientConnection,
74+
RazorLSPOptionsMonitor? optionsMonitor = null)
75+
{
76+
var projectManager = new StrictMock<IDocumentVersionCache>();
77+
int? version = 1;
78+
projectManager.Setup(x => x.TryGetDocumentVersion(It.IsAny<IDocumentSnapshot>(), out version)).Returns(true);
79+
80+
return [
81+
new ExtractToComponentCodeActionResolver(
82+
new GenerateMethodResolverDocumentContextFactory(filePath, codeDocument),
83+
optionsMonitor ?? TestRazorLSPOptionsMonitor.Create(),
84+
TestLanguageServerFeatureOptions.Instance,
85+
clientConnection,
86+
razorFormattingService,
87+
projectManager.Object)
88+
];
89+
}
8790

8891
#region CSharp CodeAction Tests
8992

@@ -1032,7 +1035,7 @@ await ValidateCodeActionAsync(input,
10321035
}
10331036

10341037
[Fact]
1035-
public async Task Handle_ExtractComponent()
1038+
public async Task Handle_ExtractComponent_SingleElement_ReturnsResult()
10361039
{
10371040
var input = """
10381041
<[||]div id="a">
@@ -1053,12 +1056,115 @@ public async Task Handle_ExtractComponent()
10531056
</div>
10541057
""";
10551058

1056-
//await ValidateExtractComponentCodeActionAsync(
1057-
// input,
1058-
// expectedRazorComponent,
1059-
// ExtractToComponentTitle,
1060-
// razorCodeActionProviders: [new ExtractToComponentCodeActionProvider(LoggerFactory)],
1061-
// codeActionResolversCreator: CreateExtractComponentCodeActionResolvers);
1059+
await ValidateExtractComponentCodeActionAsync(
1060+
input,
1061+
expectedRazorComponent,
1062+
ExtractToComponentTitle,
1063+
razorCodeActionProviders: [new ExtractToComponentCodeActionProvider(LoggerFactory)],
1064+
codeActionResolversCreator: CreateExtractComponentCodeActionResolver);
1065+
}
1066+
1067+
[Fact]
1068+
public async Task Handle_ExtractComponent_SiblingElement_ReturnsResult()
1069+
{
1070+
var input = """
1071+
<[|div id="a">
1072+
<h1>Div a title</h1>
1073+
<Book Title="To Kill a Mockingbird" Author="Harper Lee" Year="Long ago" />
1074+
<p>Div a par</p>
1075+
</div>
1076+
<div id="b">
1077+
<Movie Title="Aftersun" Director="Charlotte Wells" Year="2022" />
1078+
</div|]>
1079+
""";
1080+
1081+
var expectedRazorComponent = """
1082+
<div id="a">
1083+
<h1>Div a title</h1>
1084+
<Book Title="To Kill a Mockingbird" Author="Harper Lee" Year="Long ago" />
1085+
<p>Div a par</p>
1086+
</div>
1087+
<div id="b">
1088+
<Movie Title="Aftersun" Director="Charlotte Wells" Year="2022" />
1089+
</div>
1090+
""";
1091+
1092+
await ValidateExtractComponentCodeActionAsync(
1093+
input,
1094+
expectedRazorComponent,
1095+
ExtractToComponentTitle,
1096+
razorCodeActionProviders: [new ExtractToComponentCodeActionProvider(LoggerFactory)],
1097+
codeActionResolversCreator: CreateExtractComponentCodeActionResolver);
1098+
}
1099+
1100+
[Fact]
1101+
public async Task Handle_ExtractComponent_StartNodeContainsEndNode_ReturnsResult()
1102+
{
1103+
var input = """
1104+
<[|div id="parent">
1105+
<div>
1106+
<div>
1107+
<div>
1108+
<p>Deeply nested par</p|]>
1109+
</div>
1110+
</div>
1111+
</div>
1112+
</div>
1113+
""";
1114+
1115+
var expectedRazorComponent = """
1116+
<div id="parent">
1117+
<div>
1118+
<div>
1119+
<div>
1120+
<p>Deeply nested par</p>
1121+
</div>
1122+
</div>
1123+
</div>
1124+
</div>
1125+
""";
1126+
1127+
await ValidateExtractComponentCodeActionAsync(
1128+
input,
1129+
expectedRazorComponent,
1130+
ExtractToComponentTitle,
1131+
razorCodeActionProviders: [new ExtractToComponentCodeActionProvider(LoggerFactory)],
1132+
codeActionResolversCreator: CreateExtractComponentCodeActionResolver);
1133+
}
1134+
1135+
[Fact]
1136+
public async Task Handle_ExtractComponent_EndNodeContainsStartNode_ReturnsResult()
1137+
{
1138+
var input = """
1139+
<div id="parent">
1140+
<div>
1141+
<div>
1142+
<div>
1143+
<[|p>Deeply nested par</p>
1144+
</div>
1145+
</div>
1146+
</div>
1147+
</div|]>
1148+
""";
1149+
1150+
var expectedRazorComponent = """
1151+
<div id="parent">
1152+
<div>
1153+
<div>
1154+
<div>
1155+
<p>Deeply nested par</p>
1156+
</div>
1157+
</div>
1158+
</div>
1159+
</div>
1160+
""";
1161+
1162+
await ValidateExtractComponentCodeActionAsync(
1163+
input,
1164+
expectedRazorComponent,
1165+
ExtractToComponentTitle,
1166+
razorCodeActionProviders: [new ExtractToComponentCodeActionProvider(LoggerFactory)],
1167+
codeActionResolversCreator: CreateExtractComponentCodeActionResolver);
10621168
}
10631169

10641170
#endregion
@@ -1210,13 +1316,13 @@ private async Task ValidateExtractComponentCodeActionAsync(
12101316
string codeAction,
12111317
int childActionIndex = 0,
12121318
IRazorCodeActionProvider[]? razorCodeActionProviders = null,
1213-
Func<string, RazorCodeDocument, IRazorCodeActionResolver[]>? codeActionResolversCreator = null,
1319+
Func<string, RazorCodeDocument, IRazorFormattingService, IClientConnection, RazorLSPOptionsMonitor?, IRazorCodeActionResolver[]>? codeActionResolversCreator = null,
12141320
RazorLSPOptionsMonitor? optionsMonitor = null,
12151321
Diagnostic[]? diagnostics = null)
12161322
{
12171323
TestFileMarkupParser.GetSpan(input, out input, out var textSpan);
12181324

1219-
var razorFilePath = "C:/path/test.razor";
1325+
var razorFilePath = "C:/path/Test.razor";
12201326
var componentFilePath = "C:/path/Component.razor";
12211327
var codeDocument = CreateCodeDocument(input, filePath: razorFilePath);
12221328
var sourceText = codeDocument.GetSourceText();
@@ -1250,17 +1356,11 @@ private async Task ValidateExtractComponentCodeActionAsync(
12501356
codeActionToRun,
12511357
requestContext,
12521358
languageServer,
1253-
codeActionResolversCreator?.Invoke(razorFilePath, codeDocument) ?? []);
1254-
1255-
var edits = new List<TextChange>();
1359+
codeActionResolversCreator?.Invoke(razorFilePath, codeDocument, formattingService, languageServer, arg5: null) ?? []);
12561360

1257-
// Only get changes made in the new component file
1258-
foreach (var change in changes.Where(e => e.TextDocument.Uri.AbsolutePath == componentFilePath))
1259-
{
1260-
edits.AddRange(change.Edits.Select(e => e.ToTextChange(sourceText)));
1261-
}
1361+
var edits = changes.Where(change => change.TextDocument.Uri.AbsolutePath == componentFilePath).Single();
1362+
var actual = edits.Edits.Select(edit => edit.NewText).Single();
12621363

1263-
var actual = sourceText.WithChanges(edits).ToString();
12641364
AssertEx.EqualOrDiff(expected, actual);
12651365
}
12661366

0 commit comments

Comments
 (0)