Commit 0d4d79b
authored
Add CodeAction to simplify fully-qualified component tags (#12379)
## Implementation Complete: Add CodeAction to Simplify Fully-Qualified
Component
✅ **All implementation tasks completed successfully**
### What was implemented:
- [x] Created `SimplifyFullyQualifiedComponentCodeActionProvider` to
detect fully-qualified component tags
- [x] Created `SimplifyFullyQualifiedComponentCodeActionParams` model
for the action parameters
- [x] Created `SimplifyFullyQualifiedComponentCodeActionResolver` to
perform simplification and add using directives
- [x] Registered provider and resolver in both LSP and OOP service
collections
- [x] Added `SimplifyFullyQualifiedComponent` constant to
LanguageServerConstants
- [x] Added factory method in RazorCodeActionFactory
- [x] Added localized string resource "Simplify fully qualified
component"
- [x] Created 15 comprehensive tests covering various scenarios
- [x] All tests pass (15/15 SimplifyFullyQualifiedComponent + 110/110
CodeActions suite)
- [x] No regressions in existing code actions
- [x] Addressed all PR feedback
### Recent changes:
- Fixed negative tests to not pass `codeActionName` parameter when
`expected` is null
- Removed null-conditional operator as TagHelperInfo.BindingResult
cannot be null per recent framework changes
- Added test `DoNotOfferOnLegacyRazorFile` to verify code action not
offered on .cshtml files
- Added test `DoNotOfferInCSharpCode` to verify code action not offered
in C# code blocks
- Fixed comment to correctly state 3 elements max (start tag, end tag,
using directive)
- Changed to use `TryGetSyntaxRoot()` instead of `GetSyntaxTree()` for
cleaner code
- Added comment explaining no capacity needed for PooledArrayBuilder
- Fixed constructor consistency by adding parentheses to WorkspaceEdit
- Added test to verify code action doesn't trigger when cursor is inside
tag content
- Changed to only trigger on `MarkupTagHelperStartTagSyntax` instead of
`MarkupTagHelperElementSyntax`
- Fixed `FindInnermostNode` to use `includeWhitespace: true`
- Changed diagnostic range checking to use `TryGetAbsoluteIndex`
- Improved using directive detection to use `GetRequiredTagHelpers()`
- Simplified resolver implementation by removing unnecessary
PooledArrayBuilder
- Added test cases for self-closing tag without space and multiline
components
### Features:
✅ Simplifies fully-qualified component tags (e.g.,
`Microsoft.AspNetCore.Components.Authorization.AuthorizeRouteView`)
✅ Adds `@using` directive only when needed
✅ Intelligently detects when namespace is already in scope
✅ Works with both self-closing tags and tags with content
✅ Handles multiline components with attributes
✅ Only offers when cursor is on the start tag, not on element content
✅ Only offers in Component (.razor) files, not Legacy (.cshtml) files
✅ Only offers in HTML context, not in C# code blocks
✅ Safely handles invalid diagnostic ranges from LSP clients
✅ Does not offer when diagnostics are present on the start tag
✅ Does not offer for simple (non-qualified) component names
### Test Coverage:
1. ✅ No existing using directive
2. ✅ With existing using directive
3. ✅ With start and end tags
4. ✅ With attributes
5. ✅ Do not offer on simple component
6. ✅ Do not offer on HTML tag
7. ✅ Do not offer when diagnostic present
8. ✅ Nested namespace support
9. ✅ Multiple occurrences (only selected one simplified)
10. ✅ Self-closing without space
11. ✅ Multiline with attributes spread across multiple lines
12. ✅ Namespace already in scope via imports
13. ✅ Do not offer when cursor is inside tag content
14. ✅ Do not offer on Legacy (.cshtml) files
15. ✅ Do not offer in C# code blocks
Fixes #4522
<!-- START COPILOT CODING AGENT SUFFIX -->
<details>
<summary>Original prompt</summary>
>
> ----
>
> *This section details on the original issue you should resolve*
>
> <issue_title>Add CodeAction to simplify fully-qualified Component
</issue_title>
> <issue_description>## Summarization
> There should be a CodeAction available to simplify the Component name
for a fully qualified component tag, either when there is already an
`@using` directive in the file, or if there isn't the code action should
insert one.
>
> eg given:
> ```
> <Microsoft.AspNetCore.Components.Authorization.AuthorizeRouteView />
> ```
>
> There should be a code action to "Simplify component tag" which
results in the following document:
> ```
> @using Microsoft.AspNetCore.Components.Authorization
> <AuthorizeRouteView />
> ```
>
> If the using already exists, it should be left alone. eg given:
>
> ```
> @using Microsoft.AspNetCore.Components.Authorization
> <Microsoft.AspNetCore.Components.Authorization.AuthorizeRouteView />
> ```
>
> Then the "Simplify component tag" code action should result in the
following document:
> ```
> @using Microsoft.AspNetCore.Components.Authorization
> <AuthorizeRouteView />
> ```
>
> If there are any diagnostics present on the start tag then we
shouldn't offer.
> </issue_description>
>
> <agent_instructions>Please place tests in a new file in
https://github.com/dotnet/razor/tree/main/src/Razor/test/Microsoft.VisualStudioCode.RazorExtension.Test/Endpoints/Shared/CodeActions</agent_instructions>
>
> ## Comments on the Issue (you are @copilot in this section)
>
> <comments>
> </comments>
>
</details>
Fixes #4522
<!-- START COPILOT CODING AGENT TIPS -->
---
💡 You can make Copilot smarter by setting up custom instructions,
customizing its development environment and configuring Model Context
Protocol (MCP) servers. Learn more [Copilot coding agent
tips](https://gh.io/copilot-coding-agent-tips) in the docs.File tree
23 files changed
+783
-8
lines changed- src/Razor
- src
- Microsoft.AspNetCore.Razor.LanguageServer/Extensions
- Microsoft.CodeAnalysis.Razor.Workspaces
- CodeActions
- Models
- Razor
- Protocol
- Resources
- xlf
- Microsoft.CodeAnalysis.Remote.Razor/CodeActions
23 files changed
+783
-8
lines changedsrc/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Extensions/IServiceCollectionExtensions.cs
Lines changed: 2 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
162 | 162 | | |
163 | 163 | | |
164 | 164 | | |
| 165 | + | |
| 166 | + | |
165 | 167 | | |
166 | 168 | | |
167 | 169 | | |
| |||
Lines changed: 27 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
Lines changed: 14 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| 24 | + | |
24 | 25 | | |
25 | 26 | | |
26 | 27 | | |
| |||
196 | 197 | | |
197 | 198 | | |
198 | 199 | | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
199 | 213 | | |
Lines changed: 152 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
Lines changed: 111 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
Lines changed: 2 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
59 | 59 | | |
60 | 60 | | |
61 | 61 | | |
| 62 | + | |
| 63 | + | |
62 | 64 | | |
63 | 65 | | |
64 | 66 | | |
| |||
Lines changed: 3 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
214 | 214 | | |
215 | 215 | | |
216 | 216 | | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
217 | 220 | | |
218 | 221 | | |
219 | 222 | | |
| |||
Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
0 commit comments