-
Notifications
You must be signed in to change notification settings - Fork 193
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Extract To Component: Code handling capabilities #10760
base: features/extract-to-component
Are you sure you want to change the base?
Extract To Component: Code handling capabilities #10760
Conversation
### Summary of the changes Addition to feature wherein razor component dependencies are now identified, and their corresponding `@usings` are now also extracted into the new document. ## **_Important!_** Notes: Some changes in this PR are further rectified in #10760, such as: `AddComponentDependenciesInRange` does not take actionParams anymore. In general, most (if not all) of the internal methods that take `actionParams` as a parameter were moved to the resolver, where each method returns a collection of appropiate objects.
From merge
…ribute promotion functionality, Resolver now handles event handlers and data binding
Integrated previous PR feedback into this branch.
edbb1b4
to
5b76f23
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stopped at ExtractToComponentCodeAction. There seems to be some duplicate work going on to walk the syntax tree that can be reduced.
return context is not null && | ||
context.SupportsFileCreation && | ||
FileKinds.IsComponent(context.CodeDocument.GetFileKind()) && | ||
context.CodeDocument.GetSyntaxTree()?.Root is not null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: RazorSyntaxTree.Root
can't be null, so just need to check if the syntax tree is null
@@ -24,65 +27,35 @@ internal sealed class ExtractToComponentCodeActionProvider(ILoggerFactory logger | |||
|
|||
public Task<ImmutableArray<RazorVSInternalCodeAction>> ProvideAsync(RazorCodeActionContext context, CancellationToken cancellationToken) | |||
{ | |||
if (context is null) | |||
if (!IsValidContext(context)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
much cleaner checking!
{ | ||
return null; | ||
} | ||
var tryMakeMarkupElement = owner.FirstAncestorOrSelf<MarkupElementSyntax>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're walking the tree twice. I would suggest doing
var (startTag, endTag) = GetStartAndEndTag(owner);
if (startTag is null || endTag is null)
{
return false;
}
return startTag.Span.Contains(context.Location.AbsoluteIndex) || endTag.Span.Contains(context.Location.AbsoluteIndex);
static (MarkupTagHelperStartTagSyntax? startTag, MarkupTagHelperStartTagSyntax? endTag) GetStartAndEndTag(owner)
{
var potentialElement = owner.FirstAncestor<MarkupSyntaxNode>(e => e is MarkupElementSyntax || MarkupTagHelperElementSyntax);
return potentialElement switch
{
MarkupElementSyntax markupElement => (markupElement.StartTag, markupElement .EndTag),
MarkupTagHelperElementSyntax tagHelper => (tagHelper.StartTag, tagHelper.EndTag),
_ => (null, null);
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did try doing this before, but MarkupElementSyntax
is inconvertible to MarkupTagHelperElementSyntax
, along with their start and end tags. This is why I'm doing two separate variables for it. I think I can walk up the tree once to get them both, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't trying to convert between the two, it's just getting the start/end tags. Although the return type should probably be (MarkupSyntaxNode? startTag, MarkupSyntaxNode? endTag)
but that's what I get for coding on GitHub :P
} | ||
// Note: If we don't find a valid pair, we keep the original extraction range | ||
var diagnostics = markupElement.GetDiagnostics(); | ||
return !diagnostics.Any(d => d.Severity == RazorDiagnosticSeverity.Error); | ||
} | ||
|
||
private static bool TryGetNamespace(RazorCodeDocument codeDocument, [NotNullWhen(returnValue: true)] out string? @namespace) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this can be removed and the extension method can be used directly
@@ -23,50 +23,93 @@ | |||
using Microsoft.CodeAnalysis.Razor.Protocol; | |||
using Microsoft.VisualStudio.LanguageServer.Protocol; | |||
using Newtonsoft.Json.Linq; | |||
using static System.Net.Mime.MediaTypeNames; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
order usings
var codeDocument = await documentContext.GetCodeDocumentAsync(cancellationToken).ConfigureAwait(false); | ||
var syntaxTree = codeDocument.GetSyntaxTree(); | ||
|
||
if (codeDocument.IsUnsupported()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems like something that should go into the provider
{ | ||
return null; | ||
} | ||
|
||
var componentDocument = await documentContext.GetCodeDocumentAsync(cancellationToken).ConfigureAwait(false); | ||
if (componentDocument.IsUnsupported()) | ||
if (!FileKinds.IsComponent(codeDocument.GetFileKind())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should always be true based on the provider check
|
||
private static ExtractToComponentCodeActionParams? DeserializeActionParams(JsonElement data) | ||
{ | ||
return data.ValueKind == JsonValueKind.Undefined |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this ever possible? Seems like an exception
|
||
private static (MarkupSyntaxNode? Start, MarkupSyntaxNode? End) GetStartAndEndElements(SourceText sourceText, RazorSyntaxTree syntaxTree, ExtractToComponentCodeActionParams actionParams) | ||
{ | ||
if (syntaxTree is null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to the signature this shouldn't be null ever
return (null, null); | ||
} | ||
|
||
var owner = syntaxTree.Root.FindInnermostNode(actionParams.AbsoluteIndex, includeWhitespace: true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're doing this twice. Why can't we provide the correct way to get the start and end in the provider? We already have to check the nodes there anyways
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be an improvement if I pass in the spans of the start and element nodes to the resolver and use FindNode()
on those spans?
If not, I think the way to go would be to move all of the TryAnalyzeSelection()
stuff to the provider.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would say any analysis of validation of selection should always go in the provider, and the action of determining work beyond that is in the resolver. Keep in mind failure in a resolver means that an action was shown and just fails to apply for some reason and isn't as obvious to a user.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep. Working on moving selection validation to Provider again
Intended previous commit message: Telemetry was added to provider in previous commit, and cleaned up usings. This commit: Integrated PR Feedback for IsInsideMarkupTag
…ntaxTree to scan for using directives
I was able to extract component on
overall the feature works fine in many cases I tried that would be great too also, I noticed if you try to extract on @inject WeatherApiClient WeatherApi the extract component code action does not appear. I wonder what it would take to enable that NOTE (update)
|
…reduce duplicate work
Summary of the changes
Added base support for code in Razor elements. Specifically, the feature now automatically parametrizes methods when applicable, and extracts any appropriate fields.
Also includes refactoring of previously done work, which shifts complex responsibilities to the code action resolver (i.e. the provider was doing things like scanning through the syntax tree for the dependencies. These and others were moved.)
Note: Added work includes an API call to a roslyn endpoint which has not been pushed to as of writing.