Support component rename from an end tag (#10762)

Fixes https://github.com/dotnet/razor/issues/10717
This commit is contained in:
David Wengier 2024-08-20 10:29:53 +10:00 коммит произвёл GitHub
Родитель 077764960f a8c3c3626a
Коммит e16582150b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
3 изменённых файлов: 98 добавлений и 53 удалений

Просмотреть файл

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Threading;
@ -250,23 +251,7 @@ internal class RenameService(
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;
}
@ -288,6 +273,43 @@ internal class RenameService(
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();

Просмотреть файл

@ -265,6 +265,29 @@ public class RenameEndpointTest(ITestOutputHelper testOutput) : LanguageServerTe
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()
{

Просмотреть файл

@ -112,62 +112,62 @@ public class CohostRenameEndpointTest(ITestOutputHelper testOutputHelper) : Coho
""",
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.
=> VerifyRenamesAsync(
input: $"""
This is a Razor document.
<Component />
<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()