Skip to content

Commit

Permalink
Support component rename from an end tag (#10762)
Browse files Browse the repository at this point in the history
Fixes #10717
  • Loading branch information
davidwengier authored Aug 20, 2024
2 parents 0777649 + a8c3c36 commit e165821
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Threading;
Expand Down Expand Up @@ -250,23 +251,7 @@ private static async Task<ImmutableArray<TagHelperDescriptor>> GetOriginTagHelpe
return default;
}

var node = owner.FirstAncestorOrSelf<RazorSyntaxNode>(n => n.Kind == RazorSyntaxKind.MarkupTagHelperStartTag);
if (node is not MarkupTagHelperStartTagSyntax tagHelperStartTag)
{
return default;
}

// Ensure the rename action was invoked on the component name
// instead of a component parameter. This serves as an issue
// mitigation till `textDocument/prepareRename` is supported
// and we can ensure renames aren't triggered in unsupported
// contexts. (https://github.com/dotnet/aspnetcore/issues/26407)
if (!tagHelperStartTag.Name.FullSpan.IntersectsWith(absoluteIndex))
{
return default;
}

if (tagHelperStartTag.Parent is not MarkupTagHelperElementSyntax { TagHelperInfo.BindingResult: var binding })
if (!TryGetTagHelperBinding(owner, absoluteIndex, out var binding))
{
return default;
}
Expand All @@ -288,6 +273,43 @@ private static async Task<ImmutableArray<TagHelperDescriptor>> GetOriginTagHelpe
return [primaryTagHelper, associatedTagHelper];
}

private static bool TryGetTagHelperBinding(RazorSyntaxNode owner, int absoluteIndex, [NotNullWhen(true)] out TagHelperBinding? binding)
{
// End tags are easy, because there is only one possible binding result
if (owner is MarkupTagHelperEndTagSyntax { Parent: MarkupTagHelperElementSyntax { TagHelperInfo.BindingResult: var endTagBindingResult } })
{
binding = endTagBindingResult;
return true;
}

// A rename of a start tag could have an "owner" of one of its attributes, so we do a bit more checking
// to support this case
var node = owner.FirstAncestorOrSelf<RazorSyntaxNode>(n => n.Kind == RazorSyntaxKind.MarkupTagHelperStartTag);
if (node is not MarkupTagHelperStartTagSyntax tagHelperStartTag)
{
binding = null;
return false;
}

// Ensure the rename action was invoked on the component name instead of a component parameter. This serves as an issue
// mitigation till `textDocument/prepareRename` is supported and we can ensure renames aren't triggered in unsupported
// contexts. (https://github.com/dotnet/razor/issues/4285)
if (!tagHelperStartTag.Name.FullSpan.IntersectsWith(absoluteIndex))
{
binding = null;
return false;
}

if (tagHelperStartTag is { Parent: MarkupTagHelperElementSyntax { TagHelperInfo.BindingResult: var startTagBindingResult } })
{
binding = startTagBindingResult;
return true;
}

binding = null;
return false;
}

private static TagHelperDescriptor? FindAssociatedTagHelper(TagHelperDescriptor tagHelper, ImmutableArray<TagHelperDescriptor> tagHelpers)
{
var typeName = tagHelper.GetTypeName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,29 @@ public async Task Handle_Rename_OnComponentName_ReturnsResult()
Assert.NotNull(result);
}

[Fact]
public async Task Handle_Rename_OnComponentEndTag_ReturnsResult()
{
// Arrange
var (endpoint, documentContextFactory) = await CreateEndpointAndDocumentContextFactoryAsync();
var uri = PathUtilities.GetUri(s_componentWithParamFilePath);
var request = new RenameParams
{
TextDocument = new() { Uri = uri },
Position = VsLspFactory.CreatePosition(1, 36),
NewName = "Test2"
};

Assert.True(documentContextFactory.TryCreateForOpenDocument(uri, out var documentContext));
var requestContext = CreateRazorRequestContext(documentContext);

// Act
var result = await endpoint.HandleRequestAsync(request, requestContext, DisposalToken);

// Assert
Assert.NotNull(result);
}

[Fact]
public async Task Handle_Rename_OnComponentNameTrailingEdge_ReturnsResult()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,62 +112,62 @@ The end.
""",
renames: [("Component.razor", "DifferentName.razor")]);

[Theory(Skip = "https://github.com/dotnet/razor/issues/10717")]
[Theory]
[InlineData("$$Component")]
[InlineData("Com$$ponent")]
[InlineData("Component$$")]
public Task Component_EndTag(string endTag)
=> VerifyRenamesAsync(
input: $"""
This is a Razor document.
<Component />
=> VerifyRenamesAsync(
input: $"""
This is a Razor document.
<div>
<Component />
<Component>
</Component>
<div>
<Component />
<Component>
</{endTag}>
</Component>
<div>
<Component />
<Component>
</{endTag}>
</div>
</div>
</div>
The end.
""",
additionalFiles: [
// The source generator isn't hooked up to our test project, so we have to manually "compile" the razor file
(File("Component.cs"), """
namespace SomeProject;
The end.
""",
additionalFiles: [
// The source generator isn't hooked up to our test project, so we have to manually "compile" the razor file
(File("Component.cs"), """
namespace SomeProject;
public class Component : Microsoft.AspNetCore.Components.ComponentBase
{
}
"""),
// The above will make the component exist, but the .razor file needs to exist too for Uri presentation
(File("Component.razor"), "")
],
newName: "DifferentName",
expected: """
This is a Razor document.
public class Component : Microsoft.AspNetCore.Components.ComponentBase
{
}
"""),
// The above will make the component exist, but the .razor file needs to exist too for Uri presentation
(File("Component.razor"), "")
],
newName: "DifferentName",
expected: """
This is a Razor document.
<DifferentName />
<div>
<DifferentName />
<DifferentName>
</DifferentName>
<div>
<DifferentName />
<DifferentName>
</DifferentName>
<div>
<DifferentName />
<DifferentName>
</DifferentName>
</div>
</div>
</div>
The end.
""",
renames: [("Component.razor", "DifferentName.razor")]);
The end.
""",
renames: [("Component.razor", "DifferentName.razor")]);

[Fact]
public Task Mvc()
Expand Down

0 comments on commit e165821

Please sign in to comment.