-
Notifications
You must be signed in to change notification settings - Fork 204
#11793 Simplify tag to self-closing code action #11802
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
base: release/dev17.14
Are you sure you want to change the base?
#11793 Simplify tag to self-closing code action #11802
Conversation
@dotnet-policy-service agree |
...odeAnalysis.Razor.Workspaces/CodeActions/Razor/SimplifyTagToSelfClosingCodeActionProvider.cs
Outdated
Show resolved
Hide resolved
attribute is { TypeName: string typeName, IsEditorRequired: true } && | ||
|
||
// It has type of a `RenderFragment` | ||
(typeName == RenderFragmentTypeName || typeName.StartsWith(GenericRenderFragmentTypeName, StringComparison.Ordinal)) && |
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.
Since there is no symbol information available here, if there is a better way to check whether an Attribute is of type of a RenderFragment
, let me know.
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.
IsChildContentProperty()
and IsParameterizedChildContentProperty()
on BoundAttributeDescriptor
may be a better choice, although naming is not clear: they both use metadata populated by the compiler layer, calculated by the IsRenderFragment(IPropertySymbol property)
method under the hood.
Shall I introduce an IsRenderFragmentProperty()
alias on BoundAttributeDescriptor
for IsChildContentProperty()
to make naming clear at least on this layer?
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.
It's not clear to me why we're explicitly checking for RenderFragment anyway. Why are they important? I would have thought the more important thing to check for was ChildContent.
eg, given a component with a random render fragment property "Foo", if its required and not supplied, we can check for diagnostics, if its not required and not supplied then it doesn't matter, and if its not required and supplied, then it will be non-whitespace characters in the tag, so will already be filtered out.
ChildContent though, we need to check for because something this:
<Goo>
</Goo>
That could be a component with no content, or, if there is a ChildContent property, it could be a component with ChildContent set to some whitespace, which we probably want to keep.
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.
why we're explicitly checking for RenderFragment anyway?
Because RenderFragement
typed Parameters (including named ChildContent
if any) are the only ones which can be defined in the Component markup element syntax body (too). Not only named ChildContent
but any other:
<LoadItemView Load="LoadAsync">
<FoundView Context="item">...</FoundView>
<NotFoundView>...</NotFoundView>
<ErrorView>...</ErrorView>
</LoadItemView>
ChildContent
is indeed a special case and distinguished by the language, but I believe having a generalized approach has more value.
That could be a component with no content, or, if there is a ChildContent property, it could be a component with ChildContent set to some whitespace, which we probably want to keep.
Having a Component with an optional RenderFragment? ChildContent
Parameter, designed explicitly to accept some form of whitespace, which is then set by the user as a markup element body syntax (which is subject to formatting, so setting it as an attribute would make more sense) is quite an edge case I believe.
While the majority of the cases is that people have Components with optional/empty or no ChildContent
Parameters, end up with their tags fully closed with an end tag, offering this refactor would bring value. (It could be a follow up task to improve Auto Insert to close applicable Component tags in a self-closing way.)
Even so, this refactoring is not offered as a Diagnostic which would be reported and then a Code Fix for it, but an optional refactor which can be executed at will, because Razor allows both ways to work (without applying this refactor).
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.
In other words: we can only offer simplification, if all editor required RenderFragment
Parameters are already set (either in body, or set as an attribute, or has a binding as an attribute).
If there are still any left (so it is not set as an attribute either), we need to keep open that it may be set in the body (which is usually the case), so we don't offer the simplification yet.
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.
Let's agree to disagree on whether the logic should be here, because you've already written it anyway. I would ask though that this logic be re-written without the nested lambdas and multiple conditions that make it much harder to read (and probably debug). Having a separate method for this logic, that does an old fashioned foreach loop, and has a few guard conditions that just return false, would improve this a lot I think.
src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/SR.resx
Outdated
Show resolved
Hide resolved
148eb97
to
65fc16b
Compare
I fixed the whitespace issues in the resource file, but there is still an error in the build pipeline, possibly the missing translations. Shall I check in the result of |
yup, that should fix. Or if you build in VS it should generate them as well |
65fc16b
to
4fb2a6b
Compare
Thanks for the PR, I'll take a look later this afternoon. Please try not to force push any more now the PR is open though, it makes it much harder for us to keep track of changes as feedback is responded to. For other reviewers: it was my suggestion to target the release branch, as that's the easiest to test with for now. Once everything is signed off, we can change the target to main, and deal with any conflicts and merge. |
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 is really good! Thanks for the contribution.
Just had some thoughts about perhaps simplifying the logic and maybe doing a little less work in tooling, but rather leaning on the compiler a bit more. Curious for your thoughts on it.
...odeAnalysis.Razor.Workspaces/CodeActions/Razor/SimplifyTagToSelfClosingCodeActionProvider.cs
Show resolved
Hide resolved
...odeAnalysis.Razor.Workspaces/CodeActions/Razor/SimplifyTagToSelfClosingCodeActionProvider.cs
Outdated
Show resolved
Hide resolved
...odeAnalysis.Razor.Workspaces/CodeActions/Razor/SimplifyTagToSelfClosingCodeActionProvider.cs
Outdated
Show resolved
Hide resolved
...odeAnalysis.Razor.Workspaces/CodeActions/Razor/SimplifyTagToSelfClosingCodeActionProvider.cs
Outdated
Show resolved
Hide resolved
attribute is { TypeName: string typeName, IsEditorRequired: true } && | ||
|
||
// It has type of a `RenderFragment` | ||
(typeName == RenderFragmentTypeName || typeName.StartsWith(GenericRenderFragmentTypeName, StringComparison.Ordinal)) && |
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.
It's not clear to me why we're explicitly checking for RenderFragment anyway. Why are they important? I would have thought the more important thing to check for was ChildContent.
eg, given a component with a random render fragment property "Foo", if its required and not supplied, we can check for diagnostics, if its not required and not supplied then it doesn't matter, and if its not required and supplied, then it will be non-whitespace characters in the tag, so will already be filtered out.
ChildContent though, we need to check for because something this:
<Goo>
</Goo>
That could be a component with no content, or, if there is a ChildContent property, it could be a component with ChildContent set to some whitespace, which we probably want to keep.
...odeAnalysis.Razor.Workspaces/CodeActions/Razor/SimplifyTagToSelfClosingCodeActionProvider.cs
Outdated
Show resolved
Hide resolved
...VisualStudio.LanguageServices.Razor.Test/Cohost/CodeActions/SimplifyTagToSelfClosingTests.cs
Show resolved
Hide resolved
...odeAnalysis.Razor.Workspaces/CodeActions/Razor/SimplifyTagToSelfClosingCodeActionProvider.cs
Outdated
Show resolved
Hide resolved
- code style - clarify naming on params model - better type check for RenderFragment - recognize binding get/set syntax
...odeAnalysis.Razor.Workspaces/CodeActions/Razor/SimplifyTagToSelfClosingCodeActionProvider.cs
Outdated
Show resolved
Hide resolved
🌴 FYI: I will be traveling in the rest of this week, and while I can read/reply comments, I won't be close to my desktop workstation to be able to make any meaningful changes. Will be back on Monday next week. Feel free to make any changes if needed to proceed with this PR until then. |
Will do. Thanks @Peter-Juhasz ! Love to see both of your contributions. Have safe travels |
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.
Had another look and other than a couple of relatively minor (and most readability) tweaks, this is really good.
No rush on the follow up though :)
...odeAnalysis.Razor.Workspaces/CodeActions/Razor/SimplifyTagToSelfClosingCodeActionProvider.cs
Outdated
Show resolved
Hide resolved
(a.Key.StartsWith("@bind-", StringComparison.Ordinal) && a.Key.AsSpan("@bind-".Length).Equals(attribute.Name, StringComparison.Ordinal)) || | ||
(a.Key.StartsWith("@bind-", StringComparison.Ordinal) && a.Key.EndsWith(":get", StringComparison.Ordinal) && a.Key.AsSpan()["@bind-".Length..^":get".Length].Equals(attribute.Name, StringComparison.Ordinal)) |
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.
My previous comments about this not being necessary were wrong, I think I missed that you were iterating through BoundAttributes and not the actual attribute syntax nodes.
It might still be nice though to move this attribute name parsing to a helper method so it least it only has to be written once. Perhaps a new method in RazorSyntaxFacts
?
So it would be if (!markupElementSyntax.TagHelperInfo!.BindingResult.Attributes.Any(a => attribute.Name == RazorSyntaxFacts.GetAttributeNameForAttribute(a.Key))
or similar?
FYI that @dotnet/razor-compiler also needs to take a look, since this PR adds a method in the Compiler layer. |
...odeAnalysis.Razor.Workspaces/CodeActions/Razor/SimplifyTagToSelfClosingCodeActionProvider.cs
Outdated
Show resolved
Hide resolved
...odeAnalysis.Razor.Workspaces/CodeActions/Razor/SimplifyTagToSelfClosingCodeActionProvider.cs
Outdated
Show resolved
Hide resolved
...odeAnalysis.Razor.Workspaces/CodeActions/Razor/SimplifyTagToSelfClosingCodeActionResolver.cs
Outdated
Show resolved
Hide resolved
...odeAnalysis.Razor.Workspaces/CodeActions/Razor/SimplifyTagToSelfClosingCodeActionProvider.cs
Show resolved
Hide resolved
@@ -211,4 +211,7 @@ | |||
<data name="Unsupported_razor_project_info_version_encountered" xml:space="preserve"> | |||
<value>Unsupported razor project info version encounted.</value> | |||
</data> | |||
<data name="Simplify_Tag_To_SelfClosing_Title" xml:space="preserve"> | |||
<value>Simplify tag to self-closing</value> |
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.
@phil-allen-msft: Does this need any word-smithing?
src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/CodeActions/RemoteServices.cs
Outdated
Show resolved
Hide resolved
...odeAnalysis.Razor.Workspaces/CodeActions/Razor/SimplifyTagToSelfClosingCodeActionResolver.cs
Outdated
Show resolved
Hide resolved
...odeAnalysis.Razor.Workspaces/CodeActions/Razor/SimplifyTagToSelfClosingCodeActionProvider.cs
Outdated
Show resolved
Hide resolved
@DustinCampbell I appreciate your feedback, but I think I need some guidance interpreting the severities. While I totally agree and you are technically correct on each one and don't get me wrong, I'm not against "perfect" code, but suggestions which do not change meaning, like:
Are these suggestions or blockers? I'm happy to fix them all, I'm just curious about the severity when I see findings like these. In any case, are there any analyzers I forgot to enable locally to shorten the feedback loop? Thanks. |
Whitespace formatting would be a compile error if it violated IDE0055, and any suggested whitespace changes where there wasn't a violation are pretty rare, or would have introduced a violation. The issue with that one pattern that Dustin and I both commented on is the latter, and a Roslyn bug that I haven't got around to logging yet.
If there were any that had to be manually enabled, then that would be a bug on us not setting up our dev environment correctly. |
@Peter-Juhasz: I've left feedback that I would leave for any Razor team member submitting a pull request. My goal is to ensure consistency and readability across the code base,, which is admittedly a work-in-progress. 😄 You are free to ignore my comments that you feel are burdensome, especially if you're not planning to push any other changes. However, if you're planning to push more changes, I can't see a reason why you wouldn't make any of my suggested changes. |
Summary of the changes