Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ internal sealed class DefaultCSharpCodeActionProvider : ICSharpCodeActionProvide
RazorPredefinedCodeRefactoringProviderNames.GenerateDefaultConstructors,
RazorPredefinedCodeRefactoringProviderNames.GenerateConstructorFromMembers,
RazorPredefinedCodeRefactoringProviderNames.UseExpressionBody,
RazorPredefinedCodeRefactoringProviderNames.IntroduceVariable,
RazorPredefinedCodeFixProviderNames.ImplementAbstractClass,
RazorPredefinedCodeFixProviderNames.ImplementInterface,
RazorPredefinedCodeFixProviderNames.RemoveUnusedVariable,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public static RazorVSInternalCodeAction WrapResolvableCodeAction(

if (!isOnAllowList)
{
razorCodeAction.Title = "(Exp) " + razorCodeAction.Title;
razorCodeAction.Title = $"(Exp) {razorCodeAction.Title} ({razorCodeAction.Name})";
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unrelated, but when experimenting with code actions, its handy to know what Roslyn calls the action so we know what to add to the allow list.

}

if (razorCodeAction.Children != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Microsoft.AspNetCore.Razor.LanguageServer.EndpointContracts;
using Microsoft.AspNetCore.Razor.LanguageServer.Extensions;
using Microsoft.AspNetCore.Razor.LanguageServer.Formatting;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
using Microsoft.CodeAnalysis.Razor.Workspaces.Extensions;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;
Expand Down Expand Up @@ -59,10 +60,104 @@ public Goo()

""";

await ValidateCodeActionAsync(input, "Generate Default Constructors Code Action Provider", expected);
await ValidateCodeActionAsync(input, expected, RazorPredefinedCodeRefactoringProviderNames.GenerateDefaultConstructors);
}

private async Task ValidateCodeActionAsync(string input, string codeAction, string expected)
[Fact]
public async Task Handle_IntroduceLocal()
{
var input = """
@using System.Linq

<div></div>

@functions
{
void M(string[] args)
{
if ([|args.First()|].Length > 0)
{
}
if (args.First().Length > 0)
{
}
}
}

""";

var expected = """
@using System.Linq

<div></div>

@functions
{
void M(string[] args)
{
string v = args.First();
if (v.Length > 0)
{
}
if (args.First().Length > 0)
{
}
}
}

""";

await ValidateCodeActionAsync(input, expected, RazorPredefinedCodeRefactoringProviderNames.IntroduceVariable);
}

[Fact]
public async Task Handle_IntroduceLocal_All()
{
var input = """
@using System.Linq

<div></div>

@functions
{
void M(string[] args)
{
if ([|args.First()|].Length > 0)
{
}
if (args.First().Length > 0)
{
}
}
}

""";

var expected = """
@using System.Linq

<div></div>

@functions
{
void M(string[] args)
{
string v = args.First();
if (v.Length > 0)
{
}
if (v.Length > 0)
{
}
}
}

""";

await ValidateCodeActionAsync(input, expected, RazorPredefinedCodeRefactoringProviderNames.IntroduceVariable, childActionIndex: 1);
}

private async Task ValidateCodeActionAsync(string input, string expected, string codeAction, int childActionIndex = 0)
{
TestFileMarkupParser.GetSpan(input, out input, out var textSpan);

Expand Down Expand Up @@ -108,7 +203,12 @@ private async Task ValidateCodeActionAsync(string input, string codeAction, stri
Assert.NotNull(result);
Assert.NotEmpty(result);

var codeActionToRun = (RazorVSInternalCodeAction)result.Single(e => ((RazorVSInternalCodeAction)e.Value!).Name == codeAction);
var codeActionToRun = (VSInternalCodeAction)result.Single(e => ((RazorVSInternalCodeAction)e.Value!).Name == codeAction);

if (codeActionToRun.Children?.Length > 0)
{
codeActionToRun = codeActionToRun.Children[childActionIndex];
}

var formattingService = await TestRazorFormattingService.CreateWithFullSupportAsync();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,112 @@ await TestServices.Editor.WaitForTextChangeAsync("""

""", ControlledHangMitigatingCancellationToken);
}

[IdeFact]
public async Task CSharpCodeActionsTests_IntroduceLocal()
{
// Open the file
await TestServices.SolutionExplorer.OpenFileAsync(RazorProjectConstants.BlazorProjectName, RazorProjectConstants.CounterRazorFile, ControlledHangMitigatingCancellationToken);
await TestServices.Editor.SetTextAsync("""
@code {
void M(string[] args)
{
if (args.First().Length == 0)
{
}

if (args.First().Length == 0)
{
}
}
}
""", ControlledHangMitigatingCancellationToken);

await TestServices.Editor.PlaceCaretAsync("args.First()", charsOffset: 0, occurrence: 1, extendSelection: false, selectBlock: false, ControlledHangMitigatingCancellationToken);

// Act
var codeActions = await TestServices.Editor.InvokeCodeActionListAsync(ControlledHangMitigatingCancellationToken);

// Assert
var introduceLocal = codeActions.FirstOrDefault(a => a.Actions.Single().DisplayText.Equals("Introduce local"));
Assert.NotNull(introduceLocal);

var codeAction = introduceLocal.Actions.First();

Assert.True(codeAction.HasActionSets);

codeAction = (await codeAction.GetActionSetsAsync(ControlledHangMitigatingCancellationToken)).First().Actions.First();

await TestServices.Editor.InvokeCodeActionAsync(codeAction, ControlledHangMitigatingCancellationToken);

await TestServices.Editor.WaitForTextChangeAsync("""
@code {
void M(string[] args)
{
string v = args.First();
if (v.Length == 0)
{
}

if (args.First().Length == 0)
{
}
}
}
""", ControlledHangMitigatingCancellationToken);
}

[IdeFact]
public async Task CSharpCodeActionsTests_IntroduceLocal_All()
{
// Open the file
await TestServices.SolutionExplorer.OpenFileAsync(RazorProjectConstants.BlazorProjectName, RazorProjectConstants.CounterRazorFile, ControlledHangMitigatingCancellationToken);
await TestServices.Editor.SetTextAsync("""
@code {
void M(string[] args)
{
if (args.First().Length == 0)
{
}

if (args.First().Length == 0)
{
}
}
}
""", ControlledHangMitigatingCancellationToken);

await TestServices.Editor.PlaceCaretAsync("args.First()", charsOffset: 0, occurrence: 1, extendSelection: false, selectBlock: false, ControlledHangMitigatingCancellationToken);

// Act
var codeActions = await TestServices.Editor.InvokeCodeActionListAsync(ControlledHangMitigatingCancellationToken);

// Assert
var introduceLocal = codeActions.FirstOrDefault(a => a.Actions.Single().DisplayText.Equals("Introduce local"));
Assert.NotNull(introduceLocal);

var codeAction = introduceLocal.Actions.First();

Assert.True(codeAction.HasActionSets);

codeAction = (await codeAction.GetActionSetsAsync(ControlledHangMitigatingCancellationToken)).First().Actions.Skip(1).First();

await TestServices.Editor.InvokeCodeActionAsync(codeAction, ControlledHangMitigatingCancellationToken);

await TestServices.Editor.WaitForTextChangeAsync("""
@code {
void M(string[] args)
{
string v = args.First();
if (v.Length == 0)
{
}

if (v.Length == 0)
{
}
}
}
""", ControlledHangMitigatingCancellationToken);
}
}