Skip to content

Commit 83fa931

Browse files
authored
Enable extract refactorings in LSP (#76718)
Options services are added to the LanguageServer project so that the extract refactorings will run. The options use defaults and do not require user interaction. This is part of the O# parity work and the defaults match those used by O#. Once inserted, this will resolve dotnet/vscode-csharp#6430
2 parents 09a5602 + ee3f33b commit 83fa931

24 files changed

+800
-38
lines changed

src/Features/Core/Portable/CodeRefactorings/ExtractMethod/AbstractExtractMethodCodeRefactoringProvider.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ namespace Microsoft.CodeAnalysis.CodeRefactorings.ExtractMethod;
2525
[method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
2626
internal sealed class ExtractMethodCodeRefactoringProvider() : CodeRefactoringProvider
2727
{
28+
internal override CodeRefactoringKind Kind => CodeRefactoringKind.Extract;
29+
2830
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
2931
{
3032
// Don't bother if there isn't a selection

src/Features/Core/Portable/ExtractClass/AbstractExtractClassRefactoringProvider.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ internal abstract class AbstractExtractClassRefactoringProvider(IExtractClassOpt
2222
protected abstract Task<ImmutableArray<SyntaxNode>> GetSelectedNodesAsync(CodeRefactoringContext context);
2323
protected abstract Task<SyntaxNode?> GetSelectedClassDeclarationAsync(CodeRefactoringContext context);
2424

25+
internal override CodeRefactoringKind Kind => CodeRefactoringKind.Extract;
26+
2527
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
2628
{
2729
var solution = context.Document.Project.Solution;

src/Features/Core/Portable/ExtractInterface/ExtractInterfaceCodeRefactoringProvider.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public ExtractInterfaceCodeRefactoringProvider()
2323
{
2424
}
2525

26+
internal override CodeRefactoringKind Kind => CodeRefactoringKind.Extract;
27+
2628
public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
2729
{
2830
var (document, textSpan, cancellationToken) = context;

src/Features/Core/Portable/InlineMethod/AbstractInlineMethodRefactoringProvider.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ protected AbstractInlineMethodRefactoringProvider(
7979
_semanticFactsService = semanticFactsService;
8080
}
8181

82+
internal override CodeRefactoringKind Kind => CodeRefactoringKind.Inline;
83+
8284
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
8385
{
8486
var (document, _, cancellationToken) = context;

src/Features/Core/Portable/InlineTemporary/AbstractInlineTemporaryCodeRefactoringProvider.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ internal abstract class AbstractInlineTemporaryCodeRefactoringProvider<
1919
where TIdentifierNameSyntax : SyntaxNode
2020
where TVariableDeclaratorSyntax : SyntaxNode
2121
{
22+
internal override CodeRefactoringKind Kind => CodeRefactoringKind.Inline;
23+
2224
protected static async Task<ImmutableArray<TIdentifierNameSyntax>> GetReferenceLocationsAsync(
2325
Document document,
2426
TVariableDeclaratorSyntax variableDeclarator,

src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Microsoft.CodeAnalysis.LanguageServer.UnitTests.csproj

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,41 @@
1717

1818
<ItemGroup>
1919
<ProjectReference Include="..\..\Compilers\Test\Core\Microsoft.CodeAnalysis.Test.Utilities.csproj" />
20-
<ProjectReference Include="..\Microsoft.CodeAnalysis.LanguageServer\Microsoft.CodeAnalysis.LanguageServer.csproj"/>
20+
<ProjectReference Include="..\Microsoft.CodeAnalysis.LanguageServer\Microsoft.CodeAnalysis.LanguageServer.csproj" />
21+
<ProjectReference Include="..\Protocol\Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj" />
2122

22-
<ProjectReference Include="..\..\VisualStudio\DevKit\Impl\Microsoft.VisualStudio.LanguageServices.DevKit.csproj"
23-
ReferenceOutputAssembly="false"
24-
Private="false" />
23+
<ProjectReference Include="..\..\VisualStudio\DevKit\Impl\Microsoft.VisualStudio.LanguageServices.DevKit.csproj" ReferenceOutputAssembly="false" Private="false" />
2524
</ItemGroup>
2625

26+
<ItemGroup>
27+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit" />
28+
</ItemGroup>
29+
30+
<!--
31+
Copy files contained in the project to a RoslynLSP subdirectory to emulate deployment of the language server.
32+
-->
33+
<Target Name="_CopyLanguageServerFiles" AfterTargets="ResolveProjectReferences">
34+
<MSBuild Projects="..\Microsoft.CodeAnalysis.LanguageServer\Microsoft.CodeAnalysis.LanguageServer.csproj" Targets="GetTargetPath">
35+
<Output TaskParameter="TargetOutputs" ItemName="_LanguageServerFilePath" />
36+
</MSBuild>
37+
38+
<PropertyGroup>
39+
<_LanguageServerOutputPath>$([System.IO.Path]::GetDirectoryName(%(_LanguageServerFilePath.Identity)))</_LanguageServerOutputPath>
40+
</PropertyGroup>
41+
42+
<ItemGroup Condition="'$(_LanguageServerOutputPath)' != ''">
43+
<_LanguageServerFile Include="$(_LanguageServerOutputPath)\**\*.*" />
44+
<_LanguageServerFile Remove="$(_LanguageServerOutputPath)\**\*.mef-composition" />
45+
<Content Include="@(_LanguageServerFile)" Link="RoslynLSP\%(RecursiveDir)\%(_LanguageServerFile.Filename)%(_LanguageServerFile.Extension)" CopyToOutputDirectory="PreserveNewest" />
46+
</ItemGroup>
47+
</Target>
48+
2749
<!--
2850
Copy files contained in the NPM package to a DevKit subdirectory to emulate deployment of the DevKit extension in VS Code.
2951
-->
3052
<Target Name="_CopyDevKitExtensionFiles" AfterTargets="ResolveProjectReferences">
31-
<MSBuild Projects="..\..\VisualStudio\DevKit\Impl\Microsoft.VisualStudio.LanguageServices.DevKit.csproj"
32-
Targets="GetPackInputs">
33-
<Output TaskParameter="TargetOutputs" ItemName="_DevKitExtensionFile"/>
53+
<MSBuild Projects="..\..\VisualStudio\DevKit\Impl\Microsoft.VisualStudio.LanguageServices.DevKit.csproj" Targets="GetPackInputs">
54+
<Output TaskParameter="TargetOutputs" ItemName="_DevKitExtensionFile" />
3455
</MSBuild>
3556

3657
<ItemGroup>
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Diagnostics.CodeAnalysis;
6+
using Microsoft.CodeAnalysis;
7+
using Roslyn.Test.Utilities;
8+
using Xunit.Abstractions;
9+
using LSP = Roslyn.LanguageServer.Protocol;
10+
11+
namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.Services;
12+
13+
public class ExtractRefactoringTests(ITestOutputHelper testOutputHelper) : AbstractLanguageServerClientTests(testOutputHelper)
14+
{
15+
[Theory]
16+
[CombinatorialData]
17+
public async Task TestExtractBaseClass(bool includeDevKitComponents)
18+
{
19+
var markup =
20+
"""
21+
class {|caret:A|}
22+
{
23+
public void M()
24+
{
25+
}
26+
}
27+
""";
28+
var expected =
29+
"""
30+
internal class NewBaseType
31+
{
32+
public void M()
33+
{
34+
}
35+
}
36+
37+
class A : NewBaseType
38+
{
39+
}
40+
""";
41+
42+
await using var testLspServer = await CreateCSharpLanguageServerAsync(markup, includeDevKitComponents);
43+
var caretLocation = testLspServer.GetLocations("caret").Single();
44+
45+
await TestCodeActionAsync(testLspServer, caretLocation, "Extract base class...", expected);
46+
}
47+
48+
[Theory]
49+
[CombinatorialData]
50+
public async Task TestExtractInterface(bool includeDevKitComponents)
51+
{
52+
var markup =
53+
"""
54+
class {|caret:A|}
55+
{
56+
public void M()
57+
{
58+
}
59+
}
60+
""";
61+
var expected =
62+
"""
63+
interface IA
64+
{
65+
void M();
66+
}
67+
68+
class A : IA
69+
{
70+
public void M()
71+
{
72+
}
73+
}
74+
""";
75+
76+
await using var testLspServer = await CreateCSharpLanguageServerAsync(markup, includeDevKitComponents);
77+
var caretLocation = testLspServer.GetLocations("caret").Single();
78+
79+
await TestCodeActionAsync(testLspServer, caretLocation, "Extract interface...", expected);
80+
}
81+
82+
private static async Task TestCodeActionAsync(
83+
TestLspClient testLspClient,
84+
LSP.Location caretLocation,
85+
string codeActionTitle,
86+
[StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string expected)
87+
{
88+
var codeActionResults = await testLspClient.RunGetCodeActionsAsync(CreateCodeActionParams(caretLocation));
89+
90+
var unresolvedCodeAction = Assert.Single(codeActionResults, codeAction => codeAction.Title == codeActionTitle);
91+
92+
var resolvedCodeAction = await testLspClient.RunGetCodeActionResolveAsync(unresolvedCodeAction);
93+
94+
testLspClient.ApplyWorkspaceEdit(resolvedCodeAction.Edit);
95+
96+
var updatedCode = testLspClient.GetDocumentText(caretLocation.Uri);
97+
98+
AssertEx.Equal(expected, updatedCode);
99+
}
100+
}

0 commit comments

Comments
 (0)