Skip to content

Commit 5b0fd90

Browse files
authored
Couple of small code actions tweaks (#11630)
A two-for-one PR! I noticed Wrap Attributes wasn't working for self-closing input tags when I was recording a video yesterday, and overnight @AdmiralSnyder reported an annoyance with Extract to Component, and they were both small fixes, so here they are.
2 parents b7956b2 + c23d49b commit 5b0fd90

File tree

7 files changed

+98
-12
lines changed

7 files changed

+98
-12
lines changed

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Threading;
77
using System.Threading.Tasks;
88
using Microsoft.AspNetCore.Razor.Language;
9+
using Microsoft.AspNetCore.Razor.Language.Components;
910
using Microsoft.AspNetCore.Razor.Language.Syntax;
1011
using Microsoft.AspNetCore.Razor.Threading;
1112
using Microsoft.CodeAnalysis.Razor.CodeActions.Models;
@@ -26,6 +27,14 @@ public Task<ImmutableArray<RazorVSInternalCodeAction>> ProvideAsync(RazorCodeAct
2627
return SpecializedTasks.EmptyImmutableArray<RazorVSInternalCodeAction>();
2728
}
2829

30+
if (context.ContainsDiagnostic(ComponentDiagnosticFactory.UnexpectedMarkupElement.Id) &&
31+
!context.HasSelection)
32+
{
33+
// If we are telling the user that a component doesn't exist, and they just have their cursor in the tag, they
34+
// won't get any benefit from extracting a non-existing component to a new component.
35+
return SpecializedTasks.EmptyImmutableArray<RazorVSInternalCodeAction>();
36+
}
37+
2938
if (!FileKinds.IsComponent(context.CodeDocument.FileKind))
3039
{
3140
return SpecializedTasks.EmptyImmutableArray<RazorVSInternalCodeAction>();

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/CodeActions/Razor/GenerateMethodCodeActionProvider.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@
22
// Licensed under the MIT license. See License.txt in the project root for license information.
33

44
using System;
5-
using System.Collections.Generic;
65
using System.Collections.Immutable;
76
using System.Diagnostics.CodeAnalysis;
8-
using System.Linq;
97
using System.Threading;
108
using System.Threading.Tasks;
119
using Microsoft.AspNetCore.Razor;
@@ -14,7 +12,6 @@
1412
using Microsoft.AspNetCore.Razor.Language.Intermediate;
1513
using Microsoft.AspNetCore.Razor.Language.Syntax;
1614
using Microsoft.AspNetCore.Razor.Threading;
17-
using Microsoft.CodeAnalysis;
1815
using Microsoft.CodeAnalysis.Razor.CodeActions.Models;
1916
using SyntaxFacts = Microsoft.CodeAnalysis.CSharp.SyntaxFacts;
2017

@@ -26,8 +23,7 @@ internal class GenerateMethodCodeActionProvider : IRazorCodeActionProvider
2623
{
2724
public Task<ImmutableArray<RazorVSInternalCodeAction>> ProvideAsync(RazorCodeActionContext context, CancellationToken cancellationToken)
2825
{
29-
var nameNotExistDiagnostics = context.Request.Context.Diagnostics.Any(d => d.Code == "CS0103");
30-
if (!nameNotExistDiagnostics)
26+
if (!context.ContainsDiagnostic("CS0103"))
3127
{
3228
return SpecializedTasks.EmptyImmutableArray<RazorVSInternalCodeAction>();
3329
}

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/CodeActions/Razor/WrapAttributesCodeActionProvider.cs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,6 @@ public Task<ImmutableArray<RazorVSInternalCodeAction>> ProvideAsync(RazorCodeAct
3131
}
3232

3333
var owner = syntaxTree.Root.FindNode(TextSpan.FromBounds(context.StartAbsoluteIndex, context.EndAbsoluteIndex));
34-
if (owner is null)
35-
{
36-
return SpecializedTasks.EmptyImmutableArray<RazorVSInternalCodeAction>();
37-
}
38-
3934
var attributes = FindAttributes(owner);
4035
if (attributes.Count == 0)
4136
{
@@ -100,8 +95,24 @@ public Task<ImmutableArray<RazorVSInternalCodeAction>> ProvideAsync(RazorCodeAct
10095
return Task.FromResult<ImmutableArray<RazorVSInternalCodeAction>>([action]);
10196
}
10297

103-
private AspNetCore.Razor.Language.Syntax.SyntaxList<RazorSyntaxNode> FindAttributes(AspNetCore.Razor.Language.Syntax.SyntaxNode owner)
98+
private static AspNetCore.Razor.Language.Syntax.SyntaxList<RazorSyntaxNode> FindAttributes(AspNetCore.Razor.Language.Syntax.SyntaxNode? owner)
10499
{
100+
// Sometimes FindNode will find the start tag, sometimes the element. We always start from the start tag to make searching
101+
// easier, and since we are concerned with attributes, things without start tags wouldn't be applicalbe anyway
102+
if (owner is MarkupElementSyntax element)
103+
{
104+
owner = element.StartTag;
105+
}
106+
else if (owner is MarkupTagHelperElementSyntax tagHelperElement)
107+
{
108+
owner = tagHelperElement.StartTag;
109+
}
110+
111+
if (owner is null)
112+
{
113+
return [];
114+
}
115+
105116
foreach (var node in owner.AncestorsAndSelf())
106117
{
107118
if (node is MarkupStartTagSyntax startTag)

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/CodeActions/RazorCodeActionContext.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,24 @@ internal sealed record class RazorCodeActionContext(
2222
bool SupportsCodeActionResolve)
2323
{
2424
public bool HasSelection => StartAbsoluteIndex != EndAbsoluteIndex;
25+
26+
public bool ContainsDiagnostic(string code)
27+
{
28+
if (Request.Context.Diagnostics is null)
29+
{
30+
return false;
31+
}
32+
33+
foreach (var diagnostic in Request.Context.Diagnostics)
34+
{
35+
if (diagnostic.Code is { } codeSumType &&
36+
codeSumType.TryGetSecond(out var codeString) &&
37+
codeString == code)
38+
{
39+
return true;
40+
}
41+
}
42+
43+
return false;
44+
}
2545
}

src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CodeActions/CohostCodeActionsEndpointTestBase.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,14 @@ private protected async Task VerifyCodeActionAsync(TestCode input, string? expec
8989
}
9090
}
9191

92+
var range = input.HasSpans
93+
? inputText.GetRange(input.Span)
94+
: inputText.GetRange(input.Position, input.Position);
95+
9296
var request = new VSCodeActionParams
9397
{
9498
TextDocument = new VSTextDocumentIdentifier { Uri = document.CreateUri() },
95-
Range = inputText.GetRange(input.Span),
99+
Range = range,
96100
Context = new VSInternalCodeActionContext() { Diagnostics = diagnostics.ToArray() }
97101
};
98102

src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CodeActions/ExtractToComponentTests.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,23 @@ Hello World
3838
</div>
3939
""")]);
4040
}
41+
42+
[Fact]
43+
public async Task DontOfferOnNonExistentComponent()
44+
{
45+
await VerifyCodeActionAsync(
46+
input: """
47+
<div></div>
48+
49+
<div>
50+
Hello World
51+
</div>
52+
53+
<{|RZ10012:Not$$AComponent|} />
54+
55+
<div></div>
56+
""",
57+
expected: null,
58+
codeActionName: WorkspacesSR.ExtractTo_Component_Title);
59+
}
4160
}

src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CodeActions/WrapAttributeTests.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,31 @@ await VerifyCodeActionAsync(
101101
expected: null,
102102
codeActionName: LanguageServerConstants.CodeActions.WrapAttributes);
103103
}
104+
105+
[Fact]
106+
public async Task SelfClosing()
107+
{
108+
await VerifyCodeActionAsync(
109+
input: """
110+
@if (true)
111+
{
112+
<div>
113+
<in[||]put bar="Baz" Zip="Zap" checked @onclick="foo" Pop="Pap" />
114+
</div>
115+
}
116+
""",
117+
expected: """
118+
@if (true)
119+
{
120+
<div>
121+
<input bar="Baz"
122+
Zip="Zap"
123+
checked
124+
@onclick="foo"
125+
Pop="Pap" />
126+
</div>
127+
}
128+
""",
129+
codeActionName: LanguageServerConstants.CodeActions.WrapAttributes);
130+
}
104131
}

0 commit comments

Comments
 (0)