From 0471dbff4f087e1fa2bbb884b57fecd27020cb59 Mon Sep 17 00:00:00 2001 From: marcarro Date: Mon, 8 Jul 2024 15:42:42 -0700 Subject: [PATCH 01/19] Arranging selection logic From merge --- .../Razor/ExtractToNewComponentCodeActionProviderTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs index 185f34b0b70..4a37cec92e2 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs @@ -178,6 +178,7 @@ public async Task Handle_MultiPointSelection_WithEndAfterElement_ReturnsCurrentE // Arrange var documentPath = "c:/Test.razor"; var contents = """ + @namespace MarketApp.Pages.Product.Home @page "/" Home From 1b6e03587944f584f77dfc71e1db5f2286b877d1 Mon Sep 17 00:00:00 2001 From: marcarro Date: Mon, 15 Jul 2024 10:04:42 -0700 Subject: [PATCH 02/19] Completed and corrected selected range extraction functionality FROM MERGE --- ...actToNewComponentCodeActionProviderTest.cs | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs index 4a37cec92e2..b9e1ffac657 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs @@ -229,6 +229,127 @@ @namespace MarketApp.Pages.Product.Home Assert.Equal(selectionSpan.End, actionParams.ExtractEnd); } + [Fact] + public async Task Handle_InProperMarkup_ReturnsNull() + { + // Arrange + var documentPath = "c:/Test.razor"; + var contents = """ + page "/" + + Home + +
+
+

Div a title

+

Div $$a par

+
+
+

Div b title

+

Div b par

+
+
Hello, world! + + Welcome to your new app. + """; + TestFileMarkupParser.GetPosition(contents, out contents, out var cursorPosition); + + var request = new VSCodeActionParams() + { + TextDocument = new VSTextDocumentIdentifier { Uri = new Uri(documentPath) }, + Range = new Range(), + Context = new VSInternalCodeActionContext() + }; + + var location = new SourceLocation(cursorPosition, -1, -1); + var context = CreateRazorCodeActionContext(request, location, documentPath, contents); + + var provider = new ExtractToNewComponentCodeActionProvider(LoggerFactory); + + // Act + var commandOrCodeActionContainer = await provider.ProvideAsync(context, default); + + // Assert + Assert.Null(commandOrCodeActionContainer); + } + + [Theory] + [InlineData(""" +
+ [|
+

Div a title

+

Div a par

+
|] +
+

Div b title

+

Div b par

+
+
+ """)] + [InlineData(""" +
+
+

Div a title

+ [|

Div a par

|] +
+
+

Div b title

+

Div b par

+
+
+ """)] + [InlineData(""" +
+
+

Div a title

+ [|

Div a par

+
+
+

Div b title

+

Div b par

|] +
+
+ """)] + public async Task Handle_ValidElementSelection_ReturnsNotNull(string markupElementSelection) + { + // Arrange + var documentPath = "c:/Test.razor"; + var contents = $$""" + page "/" + + Home + + {{markupElementSelection}} + +

Hello, world!

+ + Welcome to your new app. + """; + + TestFileMarkupParser.GetPositionAndSpans( + contents, out contents, out int cursorPosition, out ImmutableArray spans); + + var request = new VSCodeActionParams() + { + TextDocument = new VSTextDocumentIdentifier { Uri = new Uri(documentPath) }, + Range = new Range(), + Context = new VSInternalCodeActionContext() + }; + + var location = new SourceLocation(cursorPosition, -1, -1); + var context = CreateRazorCodeActionContext(request, location, documentPath, contents, supportsFileCreation: true); + + var provider = new ExtractToNewComponentCodeActionProvider(LoggerFactory); + + // Act + var commandOrCodeActionContainer = await provider.ProvideAsync(context, default); + + // Assert + Assert.NotNull(commandOrCodeActionContainer); + } + private static RazorCodeActionContext CreateRazorCodeActionContext(VSCodeActionParams request, SourceLocation location, string filePath, string text, bool supportsFileCreation = true) => CreateRazorCodeActionContext(request, location, filePath, text, relativePath: filePath, supportsFileCreation: supportsFileCreation); From cba6cf1d0f50f9cdea29d87e9f010352b273d230 Mon Sep 17 00:00:00 2001 From: marcarro Date: Thu, 18 Jul 2024 13:05:38 -0700 Subject: [PATCH 03/19] Base component dependency functionality and fixed range selection bug FROM MERGE --- .../ExtractToNewComponentCodeActionParams.cs | 7 ++++ ...ExtractToNewComponentCodeActionProvider.cs | 33 +++++++++++++++++++ ...ExtractToNewComponentCodeActionResolver.cs | 14 ++++++-- 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Models/ExtractToNewComponentCodeActionParams.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Models/ExtractToNewComponentCodeActionParams.cs index 9c30b64aeed..4ed31edc459 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Models/ExtractToNewComponentCodeActionParams.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Models/ExtractToNewComponentCodeActionParams.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Text.Json.Serialization; namespace Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Models; @@ -10,10 +11,16 @@ internal sealed class ExtractToNewComponentCodeActionParams { [JsonPropertyName("uri")] public required Uri Uri { get; set; } + [JsonPropertyName("extractStart")] public int ExtractStart { get; set; } + [JsonPropertyName("extractEnd")] public int ExtractEnd { get; set; } + [JsonPropertyName("namespace")] public required string Namespace { get; set; } + + [JsonPropertyName("dependencies")] + public required List Dependencies { get; set; } } diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionProvider.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionProvider.cs index 6ae53437ba7..41b5d9ee0cf 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionProvider.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionProvider.cs @@ -257,4 +257,37 @@ private static (SyntaxNode? Start, SyntaxNode? End) FindContainingSiblingPair(Sy return null; } + private static HashSet IdentifyComponentsInRange(SyntaxNode root, int extractStart, int extractEnd) + { + var components = new HashSet(); + var extractSpan = new TextSpan(extractStart, extractEnd - extractStart); + + foreach (var node in root.DescendantNodes()) + { + if (node is MarkupTagHelperElementSyntax markupElement && + extractSpan.Contains(markupElement.Span)) + { + components.Add(markupElement); + } + } + + return components; + } + + private static List GetAllUsingStatements(SyntaxNode root) + { + return root.DescendantNodes() + .OfType() + .SelectMany(cSharpBlock => cSharpBlock.ChildNodes()) + .OfType() + .Select(directive => directive.ToFullString().TrimStart()) + .Where(directiveString => directiveString.StartsWith("@using")) + .Select(directiveString => directiveString.Trim()) + .ToList(); + } + + //private static bool HasUnsupportedChildren(Language.Syntax.SyntaxNode node) + //{ + // return node.DescendantNodes().Any(static n => n is MarkupBlockSyntax or CSharpTransitionSyntax or RazorCommentBlockSyntax); + //} } diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionResolver.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionResolver.cs index 7cc6dfce792..3197689ce7e 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionResolver.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionResolver.cs @@ -90,7 +90,15 @@ internal sealed class ExtractToNewComponentCodeActionResolver( } var componentName = Path.GetFileNameWithoutExtension(componentPath); - var newComponentContent = text.GetSubTextString(new TextSpan(actionParams.ExtractStart, actionParams.ExtractEnd - actionParams.ExtractStart)).Trim(); + var newComponentContent = string.Empty; + + newComponentContent += string.Join(Environment.NewLine, actionParams.Dependencies); + if (actionParams.Dependencies.Count > 0) + { + newComponentContent += Environment.NewLine + Environment.NewLine; // Ensure there's a newline after the dependencies if any exist. + } + + newComponentContent = newComponentContent + text.GetSubTextString(new CodeAnalysis.Text.TextSpan(actionParams.ExtractStart, actionParams.ExtractEnd - actionParams.ExtractStart)).Trim(); var start = componentDocument.Source.Text.Lines.GetLinePosition(actionParams.ExtractStart); var end = componentDocument.Source.Text.Lines.GetLinePosition(actionParams.ExtractEnd); @@ -100,7 +108,9 @@ internal sealed class ExtractToNewComponentCodeActionResolver( End = new Position(end.Line, end.Character) }; - var componentDocumentIdentifier = new OptionalVersionedTextDocumentIdentifier { Uri = actionParams.Uri }; + + + var componentDocumentIdentifier = new OptionalVersionedTextDocumentIdentifier { Uri = actionParams.Uri }; var newComponentDocumentIdentifier = new OptionalVersionedTextDocumentIdentifier { Uri = newComponentUri }; var documentChanges = new SumType[] From cf33fd7c164865e9a731b927461e3b5597a04d10 Mon Sep 17 00:00:00 2001 From: marcarro Date: Fri, 19 Jul 2024 10:11:41 -0700 Subject: [PATCH 04/19] Include only dependencies included in the selected range --- ...ExtractToNewComponentCodeActionProvider.cs | 79 +++++++++++++----- ...ExtractToNewComponentCodeActionResolver.cs | 2 +- ...actToNewComponentCodeActionProviderTest.cs | 80 +------------------ 3 files changed, 61 insertions(+), 100 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionProvider.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionProvider.cs index 41b5d9ee0cf..925067c64aa 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionProvider.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionProvider.cs @@ -66,7 +66,17 @@ public Task> ProvideAsync(RazorCodeAct var actionParams = CreateInitialActionParams(context, startElementNode, @namespace); - ProcessSelection(startElementNode, endElementNode, actionParams); + var dependencyScanRoot = FindNearestCommonAncestor(startElementNode, endElementNode); + + AddComponentDependenciesInRange(dependencyScanRoot, + actionParams.ExtractStart, + actionParams.ExtractEnd, + ref actionParams); + + if (IsMultiPointSelection(context.Request.Range)) + { + ProcessMultiPointSelection(startElementNode, endElementNode, actionParams); + } var resolutionParams = new RazorCodeActionResolutionParams() { @@ -94,7 +104,10 @@ private static (MarkupElementSyntax? Start, MarkupElementSyntax? End) GetStartAn return (null, null); } - var endElementNode = GetEndElementNode(context, syntaxTree); + var endElementNode = GetEndElementNode(context, syntaxTree, logger); + + endElementNode ??= startElementNode; + return (startElementNode, endElementNode); } @@ -142,7 +155,8 @@ private static ExtractToNewComponentCodeActionParams CreateInitialActionParams(R Uri = context.Request.TextDocument.Uri, ExtractStart = startElementNode.Span.Start, ExtractEnd = startElementNode.Span.End, - Namespace = @namespace + Namespace = @namespace, + Dependencies = [] }; } @@ -257,37 +271,58 @@ private static (SyntaxNode? Start, SyntaxNode? End) FindContainingSiblingPair(Sy return null; } - private static HashSet IdentifyComponentsInRange(SyntaxNode root, int extractStart, int extractEnd) + + private static void AddComponentDependenciesInRange(SyntaxNode root, int extractStart, int extractEnd, ref ExtractToNewComponentCodeActionParams actionParams) { - var components = new HashSet(); + var components = new HashSet(); var extractSpan = new TextSpan(extractStart, extractEnd - extractStart); foreach (var node in root.DescendantNodes()) { - if (node is MarkupTagHelperElementSyntax markupElement && - extractSpan.Contains(markupElement.Span)) + if (IsMarkupTagHelperElement(node, extractSpan)) { - components.Add(markupElement); + var tagHelperInfo = GetTagHelperInfo(node); + if (tagHelperInfo != null) + { + AddDependenciesFromTagHelperInfo(tagHelperInfo, components, ref actionParams); + } } } + } - return components; + private static bool IsMarkupTagHelperElement(SyntaxNode node, TextSpan extractSpan) + { + return node is MarkupTagHelperElementSyntax markupElement && + extractSpan.Contains(markupElement.Span); } - private static List GetAllUsingStatements(SyntaxNode root) + private static TagHelperInfo? GetTagHelperInfo(SyntaxNode node) { - return root.DescendantNodes() - .OfType() - .SelectMany(cSharpBlock => cSharpBlock.ChildNodes()) - .OfType() - .Select(directive => directive.ToFullString().TrimStart()) - .Where(directiveString => directiveString.StartsWith("@using")) - .Select(directiveString => directiveString.Trim()) - .ToList(); + if (node is MarkupTagHelperElementSyntax markupElement) + { + return markupElement.TagHelperInfo; + } + + return null; } - //private static bool HasUnsupportedChildren(Language.Syntax.SyntaxNode node) - //{ - // return node.DescendantNodes().Any(static n => n is MarkupBlockSyntax or CSharpTransitionSyntax or RazorCommentBlockSyntax); - //} + private static void AddDependenciesFromTagHelperInfo(TagHelperInfo tagHelperInfo, HashSet components, ref ExtractToNewComponentCodeActionParams actionParams) + { + foreach (var descriptor in tagHelperInfo.BindingResult.Descriptors) + { + if (descriptor != null) + { + foreach (var metadata in descriptor.Metadata) + { + if (metadata.Key == TagHelperMetadata.Common.TypeNamespace && + metadata.Value is not null && + !components.Contains(metadata.Value)) + { + components.Add(metadata.Value); + actionParams.Dependencies.Add($"@using {metadata.Value}"); + } + } + } + } + } } diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionResolver.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionResolver.cs index 3197689ce7e..f6c9adb231b 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionResolver.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionResolver.cs @@ -98,7 +98,7 @@ internal sealed class ExtractToNewComponentCodeActionResolver( newComponentContent += Environment.NewLine + Environment.NewLine; // Ensure there's a newline after the dependencies if any exist. } - newComponentContent = newComponentContent + text.GetSubTextString(new CodeAnalysis.Text.TextSpan(actionParams.ExtractStart, actionParams.ExtractEnd - actionParams.ExtractStart)).Trim(); + newComponentContent += text.GetSubTextString(new CodeAnalysis.Text.TextSpan(actionParams.ExtractStart, actionParams.ExtractEnd - actionParams.ExtractStart)).Trim(); var start = componentDocument.Source.Text.Lines.GetLinePosition(actionParams.ExtractStart); var end = componentDocument.Source.Text.Lines.GetLinePosition(actionParams.ExtractEnd); diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs index b9e1ffac657..739ff1f7dd7 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs @@ -154,6 +154,7 @@ public async Task Handle_MultiPointSelection_ReturnsNotEmpty() var location = new SourceLocation(cursorPosition, -1, -1); var context = CreateRazorCodeActionContext(request, location, documentPath, contents); + AddMultiPointSelectionToContext(ref context, selectionSpan); var lineSpan = context.SourceText.GetLinePositionSpan(selectionSpan); request.Range = VsLspFactory.CreateRange(lineSpan); @@ -230,7 +231,7 @@ @namespace MarketApp.Pages.Product.Home } [Fact] - public async Task Handle_InProperMarkup_ReturnsNull() + public async Task Handle_InProperMarkup_ReturnsEmpty() { // Arrange var documentPath = "c:/Test.razor"; @@ -272,82 +273,7 @@ public async Task Handle_InProperMarkup_ReturnsNull() var commandOrCodeActionContainer = await provider.ProvideAsync(context, default); // Assert - Assert.Null(commandOrCodeActionContainer); - } - - [Theory] - [InlineData(""" -
- [|
-

Div a title

-

Div a par

-
|] -
-

Div b title

-

Div b par

-
-
- """)] - [InlineData(""" -
-
-

Div a title

- [|

Div a par

|] -
-
-

Div b title

-

Div b par

-
-
- """)] - [InlineData(""" -
-
-

Div a title

- [|

Div a par

-
-
-

Div b title

-

Div b par

|] -
-
- """)] - public async Task Handle_ValidElementSelection_ReturnsNotNull(string markupElementSelection) - { - // Arrange - var documentPath = "c:/Test.razor"; - var contents = $$""" - page "/" - - Home - - {{markupElementSelection}} - -

Hello, world!

- - Welcome to your new app. - """; - - TestFileMarkupParser.GetPositionAndSpans( - contents, out contents, out int cursorPosition, out ImmutableArray spans); - - var request = new VSCodeActionParams() - { - TextDocument = new VSTextDocumentIdentifier { Uri = new Uri(documentPath) }, - Range = new Range(), - Context = new VSInternalCodeActionContext() - }; - - var location = new SourceLocation(cursorPosition, -1, -1); - var context = CreateRazorCodeActionContext(request, location, documentPath, contents, supportsFileCreation: true); - - var provider = new ExtractToNewComponentCodeActionProvider(LoggerFactory); - - // Act - var commandOrCodeActionContainer = await provider.ProvideAsync(context, default); - - // Assert - Assert.NotNull(commandOrCodeActionContainer); + Assert.Empty(commandOrCodeActionContainer); } private static RazorCodeActionContext CreateRazorCodeActionContext(VSCodeActionParams request, SourceLocation location, string filePath, string text, bool supportsFileCreation = true) From 019d6a46973021f876f6dd5758fe8a78c3267d3c Mon Sep 17 00:00:00 2001 From: marcarro Date: Tue, 30 Jul 2024 09:05:19 -0700 Subject: [PATCH 05/19] Test updates --- .../Razor/ExtractToNewComponentCodeActionProviderTest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs index 739ff1f7dd7..a3dd0a33fae 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs @@ -182,6 +182,8 @@ public async Task Handle_MultiPointSelection_WithEndAfterElement_ReturnsCurrentE @namespace MarketApp.Pages.Product.Home @page "/" + namespace MarketApp.Pages.Product.Home + Home
From dcee92380408a8c7b51f6712da6217a79b8d5c47 Mon Sep 17 00:00:00 2001 From: marcarro Date: Mon, 15 Jul 2024 10:04:42 -0700 Subject: [PATCH 06/19] Completed and corrected selected range extraction functionality --- .../Razor/ExtractToNewComponentCodeActionProvider.cs | 1 + .../Razor/ExtractToNewComponentCodeActionProviderTest.cs | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionProvider.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionProvider.cs index 925067c64aa..d418a917d80 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionProvider.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionProvider.cs @@ -9,6 +9,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using ICSharpCode.Decompiler.CSharp.Syntax; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Components; using Microsoft.AspNetCore.Razor.Language.Extensions; diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs index a3dd0a33fae..1e5e7ac16db 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs @@ -251,10 +251,10 @@ public async Task Handle_InProperMarkup_ReturnsEmpty()

Div b title

Div b par

- +

Hello, world!

- + Welcome to your new app. """; TestFileMarkupParser.GetPosition(contents, out contents, out var cursorPosition); From 6e8d2aa64bca2c0655a1b0ded166dfe7556971b5 Mon Sep 17 00:00:00 2001 From: marcarro Date: Tue, 30 Jul 2024 10:23:41 -0700 Subject: [PATCH 07/19] Nits --- .../Razor/ExtractToNewComponentCodeActionProvider.cs | 11 +++++------ .../ExtractToNewComponentCodeActionProviderTest.cs | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionProvider.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionProvider.cs index d418a917d80..84241605f80 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionProvider.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionProvider.cs @@ -52,9 +52,8 @@ public Task> ProvideAsync(RazorCodeAct return SpecializedTasks.EmptyImmutableArray(); } - var (startElementNode, endElementNode) = GetStartAndEndElements(context, syntaxTree, _logger); - // Make sure the selection starts on an element tag + var (startElementNode, endElementNode) = GetStartAndEndElements(context, syntaxTree, _logger); if (startElementNode is null) { return SpecializedTasks.EmptyImmutableArray(); @@ -72,7 +71,7 @@ public Task> ProvideAsync(RazorCodeAct AddComponentDependenciesInRange(dependencyScanRoot, actionParams.ExtractStart, actionParams.ExtractEnd, - ref actionParams); + actionParams); if (IsMultiPointSelection(context.Request.Range)) { @@ -273,7 +272,7 @@ private static (SyntaxNode? Start, SyntaxNode? End) FindContainingSiblingPair(Sy return null; } - private static void AddComponentDependenciesInRange(SyntaxNode root, int extractStart, int extractEnd, ref ExtractToNewComponentCodeActionParams actionParams) + private static void AddComponentDependenciesInRange(SyntaxNode root, int extractStart, int extractEnd, ExtractToNewComponentCodeActionParams actionParams) { var components = new HashSet(); var extractSpan = new TextSpan(extractStart, extractEnd - extractStart); @@ -285,7 +284,7 @@ private static void AddComponentDependenciesInRange(SyntaxNode root, int extract var tagHelperInfo = GetTagHelperInfo(node); if (tagHelperInfo != null) { - AddDependenciesFromTagHelperInfo(tagHelperInfo, components, ref actionParams); + AddDependenciesFromTagHelperInfo(tagHelperInfo, components, actionParams); } } } @@ -307,7 +306,7 @@ private static bool IsMarkupTagHelperElement(SyntaxNode node, TextSpan extractSp return null; } - private static void AddDependenciesFromTagHelperInfo(TagHelperInfo tagHelperInfo, HashSet components, ref ExtractToNewComponentCodeActionParams actionParams) + private static void AddDependenciesFromTagHelperInfo(TagHelperInfo tagHelperInfo, HashSet components, ExtractToNewComponentCodeActionParams actionParams) { foreach (var descriptor in tagHelperInfo.BindingResult.Descriptors) { diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs index 1e5e7ac16db..aabaa032ef7 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs @@ -238,7 +238,7 @@ public async Task Handle_InProperMarkup_ReturnsEmpty() // Arrange var documentPath = "c:/Test.razor"; var contents = """ - page "/" + @page "/" Home From 17479d4a415b8a885c502d9bf36d8efa3d7ace5e Mon Sep 17 00:00:00 2001 From: marcarro Date: Thu, 1 Aug 2024 14:35:09 -0700 Subject: [PATCH 08/19] Added method to scan identifiers --- .../ExtractToNewComponentCodeActionParams.cs | 6 ++ ...ExtractToNewComponentCodeActionProvider.cs | 63 ++++++++++++++----- ...actToNewComponentCodeActionProviderTest.cs | 63 ++++++++++++++++++- 3 files changed, 114 insertions(+), 18 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Models/ExtractToNewComponentCodeActionParams.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Models/ExtractToNewComponentCodeActionParams.cs index 4ed31edc459..91a3c1b498f 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Models/ExtractToNewComponentCodeActionParams.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Models/ExtractToNewComponentCodeActionParams.cs @@ -23,4 +23,10 @@ internal sealed class ExtractToNewComponentCodeActionParams [JsonPropertyName("dependencies")] public required List Dependencies { get; set; } + + [JsonPropertyName("usedIdentifiers")] + public required HashSet UsedIdentifiers { get; set; } + + [JsonPropertyName("usedMembers")] + public required HashSet UsedMembers { get; set; } } diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionProvider.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionProvider.cs index 84241605f80..4bf70362f03 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionProvider.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionProvider.cs @@ -1,5 +1,5 @@ // Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root for license information. +// Licensed under the MIT license. See License.txt in the project divNode for license information. using System; using System.Collections.Generic; @@ -17,6 +17,7 @@ using Microsoft.AspNetCore.Razor.Language.Syntax; using Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Models; using Microsoft.AspNetCore.Razor.Threading; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Razor.Logging; using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.CodeAnalysis.Text; @@ -59,25 +60,30 @@ public Task> ProvideAsync(RazorCodeAct return SpecializedTasks.EmptyImmutableArray(); } + if (endElementNode is null) + { + endElementNode = startElementNode; + } + if (!TryGetNamespace(context.CodeDocument, out var @namespace)) { return SpecializedTasks.EmptyImmutableArray(); } - var actionParams = CreateInitialActionParams(context, startElementNode, @namespace); - - var dependencyScanRoot = FindNearestCommonAncestor(startElementNode, endElementNode); - - AddComponentDependenciesInRange(dependencyScanRoot, - actionParams.ExtractStart, - actionParams.ExtractEnd, - actionParams); + var actionParams = CreateInitialActionParams(context, startElementNode, @namespace); if (IsMultiPointSelection(context.Request.Range)) { ProcessMultiPointSelection(startElementNode, endElementNode, actionParams); } + var utilityScanRoot = FindNearestCommonAncestor(startElementNode, endElementNode) ?? startElementNode; + AddComponentDependenciesInRange(utilityScanRoot, + actionParams.ExtractStart, + actionParams.ExtractEnd, + actionParams); + GetUsedIdentifiers(utilityScanRoot, syntaxTree.Root, actionParams); + var resolutionParams = new RazorCodeActionResolutionParams() { Action = LanguageServerConstants.CodeActions.ExtractToNewComponentAction, @@ -106,8 +112,6 @@ private static (MarkupElementSyntax? Start, MarkupElementSyntax? End) GetStartAn var endElementNode = GetEndElementNode(context, syntaxTree, logger); - endElementNode ??= startElementNode; - return (startElementNode, endElementNode); } @@ -156,7 +160,9 @@ private static ExtractToNewComponentCodeActionParams CreateInitialActionParams(R ExtractStart = startElementNode.Span.Start, ExtractEnd = startElementNode.Span.End, Namespace = @namespace, - Dependencies = [] + Dependencies = [], + UsedIdentifiers = [], + UsedMembers = [], }; } @@ -221,7 +227,7 @@ private static (SyntaxNode? Start, SyntaxNode? End) FindContainingSiblingPair(Sy { // Find the lowest common ancestor of both nodes var nearestCommonAncestor = FindNearestCommonAncestor(startNode, endNode); - if (nearestCommonAncestor == null) + if (nearestCommonAncestor is null) { return (null, null); } @@ -237,7 +243,7 @@ private static (SyntaxNode? Start, SyntaxNode? End) FindContainingSiblingPair(Sy { var childSpan = child.Span; - if (startContainingNode == null && childSpan.Contains(startSpan)) + if (startContainingNode is null && childSpan.Contains(startSpan)) { startContainingNode = child; if (endContainingNode is not null) @@ -259,7 +265,7 @@ private static (SyntaxNode? Start, SyntaxNode? End) FindContainingSiblingPair(Sy { var current = node1; - while (current.Kind == SyntaxKind.MarkupElement && current is not null) + while (current is MarkupElementSyntax && current is not null) { if (current.Span.Contains(node2.Span)) { @@ -282,7 +288,7 @@ private static void AddComponentDependenciesInRange(SyntaxNode root, int extract if (IsMarkupTagHelperElement(node, extractSpan)) { var tagHelperInfo = GetTagHelperInfo(node); - if (tagHelperInfo != null) + if (tagHelperInfo is not null) { AddDependenciesFromTagHelperInfo(tagHelperInfo, components, actionParams); } @@ -310,7 +316,7 @@ private static void AddDependenciesFromTagHelperInfo(TagHelperInfo tagHelperInfo { foreach (var descriptor in tagHelperInfo.BindingResult.Descriptors) { - if (descriptor != null) + if (descriptor is not null) { foreach (var metadata in descriptor.Metadata) { @@ -325,4 +331,27 @@ metadata.Value is not null && } } } + + private static void GetUsedIdentifiers(SyntaxNode divNode, SyntaxNode documentRoot, ExtractToNewComponentCodeActionParams actionParams) + { + HashSet identifiersInScope = []; + HashSet identifiersInBlock = []; + + + foreach (var node in divNode.DescendantNodes().Where(static node => node.Kind is SyntaxKind.Identifier)) + { + identifiersInScope.Add(node.GetContent()); + } + + foreach (var codeBlock in documentRoot.DescendantNodes().Where(static node => node.Kind is SyntaxKind.RazorDirective)) + { + foreach (var node in codeBlock.DescendantNodes().Where(static node => node.Kind is SyntaxKind.Identifier)) + { + identifiersInBlock.Add(node.GetContent()); + } + } + + identifiersInBlock.IntersectWith(identifiersInScope); + actionParams.UsedIdentifiers = identifiersInBlock; + } } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs index aabaa032ef7..67ff43f3dbd 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs @@ -118,6 +118,67 @@ public async Task Handle_SinglePointSelection_ReturnsNotEmpty() Assert.NotEmpty(commandOrCodeActionContainer); } + [Fact] + public async Task Handle_CodeInsideDiv_ScansCorrect() + { + // Arrange + var documentPath = "c:/Test.cs"; + var contents = """ + @page "/" + + Home + + <$$div id="codeInside"> + @for(int idx = 0; idx < 10; idx++) { + string s = someFunc(idx * myField); + } + + +
+
+

Div a title

+

Div a par

+
+
+

Div b title

+

Div b par

+
+
+ + @code { + public int myField = 7; + + public string someFunc(int num) { + return "Hello for number" + num; + } + } + """; + TestFileMarkupParser.GetPosition(contents, out contents, out var cursorPosition); + + var request = new VSCodeActionParams() + { + TextDocument = new VSTextDocumentIdentifier { Uri = new Uri(documentPath) }, + Range = new Range(), + Context = new VSInternalCodeActionContext() + }; + + var location = new SourceLocation(cursorPosition, -1, -1); + var context = CreateRazorCodeActionContext(request, location, documentPath, contents); + + var provider = new ExtractToNewComponentCodeActionProvider(LoggerFactory); + + // Act + var commandOrCodeActionContainer = await provider.ProvideAsync(context, default); + + // Assert + Assert.NotEmpty(commandOrCodeActionContainer); + var codeAction = Assert.Single(commandOrCodeActionContainer); + var razorCodeActionResolutionParams = ((JsonElement)codeAction.Data!).Deserialize(); + Assert.NotNull(razorCodeActionResolutionParams); + var actionParams = ((JsonElement)razorCodeActionResolutionParams.Data).Deserialize(); + Assert.NotNull(actionParams); + } + [Fact] public async Task Handle_MultiPointSelection_ReturnsNotEmpty() { @@ -179,8 +240,8 @@ public async Task Handle_MultiPointSelection_WithEndAfterElement_ReturnsCurrentE // Arrange var documentPath = "c:/Test.razor"; var contents = """ - @namespace MarketApp.Pages.Product.Home @page "/" + @namespace MarketApp.Pages.Product.Home namespace MarketApp.Pages.Product.Home From 2c4b7095c320c00597238111fd3a1c89a72b851f Mon Sep 17 00:00:00 2001 From: marcarro Date: Mon, 5 Aug 2024 09:39:38 -0700 Subject: [PATCH 09/19] Corrected naming and nits --- ...r.cs => ExtractToComponentCodeActionProvider.cs} | 13 +++++++++---- ...r.cs => ExtractToComponentCodeActionResolver.cs} | 7 +++---- .../CodeActions/Razor/RazorCodeActionFactory.cs | 8 ++++---- .../Extensions/IServiceCollectionExtensions.cs | 4 ++-- .../Resources/SR.resx | 2 +- .../Resources/xlf/SR.cs.xlf | 2 +- .../Resources/xlf/SR.de.xlf | 2 +- .../Resources/xlf/SR.es.xlf | 2 +- .../Resources/xlf/SR.fr.xlf | 2 +- .../Resources/xlf/SR.it.xlf | 2 +- .../Resources/xlf/SR.ja.xlf | 2 +- .../Resources/xlf/SR.ko.xlf | 2 +- .../Resources/xlf/SR.pl.xlf | 2 +- .../Resources/xlf/SR.pt-BR.xlf | 2 +- .../Resources/xlf/SR.ru.xlf | 2 +- .../Resources/xlf/SR.tr.xlf | 2 +- .../Resources/xlf/SR.zh-Hans.xlf | 2 +- .../Resources/xlf/SR.zh-Hant.xlf | 2 +- ... => ExtractToComponentCodeActionProviderTest.cs} | 12 ++++++------ 19 files changed, 38 insertions(+), 34 deletions(-) rename src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/{ExtractToNewComponentCodeActionProvider.cs => ExtractToComponentCodeActionProvider.cs} (96%) rename src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/{ExtractToNewComponentCodeActionResolver.cs => ExtractToComponentCodeActionResolver.cs} (96%) rename src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/{ExtractToNewComponentCodeActionProviderTest.cs => ExtractToComponentCodeActionProviderTest.cs} (96%) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionProvider.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs similarity index 96% rename from src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionProvider.cs rename to src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs index 4bf70362f03..e8dc1cd8456 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionProvider.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs @@ -1,5 +1,5 @@ // Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT license. See License.txt in the project divNode for license information. +// Licensed under the MIT license. See License.txt in the project root for license information. using System; using System.Collections.Generic; @@ -26,9 +26,9 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Razor; -internal sealed class ExtractToNewComponentCodeActionProvider(ILoggerFactory loggerFactory) : IRazorCodeActionProvider +internal sealed class ExtractToComponentCodeActionProvider(ILoggerFactory loggerFactory) : IRazorCodeActionProvider { - private readonly ILogger _logger = loggerFactory.GetOrCreateLogger(); + private readonly ILogger _logger = loggerFactory.GetOrCreateLogger(); public Task> ProvideAsync(RazorCodeActionContext context, CancellationToken cancellationToken) { @@ -91,7 +91,7 @@ public Task> ProvideAsync(RazorCodeAct Data = actionParams, }; - var codeAction = RazorCodeActionFactory.CreateExtractToNewComponent(resolutionParams); + var codeAction = RazorCodeActionFactory.CreateExtractToComponent(resolutionParams); return Task.FromResult>([codeAction]); } @@ -337,10 +337,13 @@ private static void GetUsedIdentifiers(SyntaxNode divNode, SyntaxNode documentRo HashSet identifiersInScope = []; HashSet identifiersInBlock = []; + HashSet nodesInScope = []; + HashSet nodesInBlock = []; foreach (var node in divNode.DescendantNodes().Where(static node => node.Kind is SyntaxKind.Identifier)) { identifiersInScope.Add(node.GetContent()); + nodesInScope.Add(node); } foreach (var codeBlock in documentRoot.DescendantNodes().Where(static node => node.Kind is SyntaxKind.RazorDirective)) @@ -348,9 +351,11 @@ private static void GetUsedIdentifiers(SyntaxNode divNode, SyntaxNode documentRo foreach (var node in codeBlock.DescendantNodes().Where(static node => node.Kind is SyntaxKind.Identifier)) { identifiersInBlock.Add(node.GetContent()); + nodesInBlock.Add(node); } } + nodesInBlock.IntersectWith(nodesInScope); identifiersInBlock.IntersectWith(identifiersInScope); actionParams.UsedIdentifiers = identifiersInBlock; } diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionResolver.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionResolver.cs similarity index 96% rename from src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionResolver.cs rename to src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionResolver.cs index f6c9adb231b..920267c632a 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToNewComponentCodeActionResolver.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionResolver.cs @@ -27,7 +27,8 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Razor; -internal sealed class ExtractToNewComponentCodeActionResolver( +internal sealed class ExtractToComponentCodeActionResolver + ( IDocumentContextFactory documentContextFactory, LanguageServerFeatureOptions languageServerFeatureOptions) : IRazorCodeActionResolver { @@ -108,9 +109,7 @@ internal sealed class ExtractToNewComponentCodeActionResolver( End = new Position(end.Line, end.Character) }; - - - var componentDocumentIdentifier = new OptionalVersionedTextDocumentIdentifier { Uri = actionParams.Uri }; + var componentDocumentIdentifier = new OptionalVersionedTextDocumentIdentifier { Uri = actionParams.Uri }; var newComponentDocumentIdentifier = new OptionalVersionedTextDocumentIdentifier { Uri = newComponentUri }; var documentChanges = new SumType[] diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/RazorCodeActionFactory.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/RazorCodeActionFactory.cs index 6a7d697c1ca..f9f4a94cd27 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/RazorCodeActionFactory.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/RazorCodeActionFactory.cs @@ -14,7 +14,7 @@ internal static class RazorCodeActionFactory private readonly static Guid s_fullyQualifyComponentTelemetryId = new("3d9abe36-7d10-4e08-8c18-ad88baa9a923"); private readonly static Guid s_createComponentFromTagTelemetryId = new("a28e0baa-a4d5-4953-a817-1db586035841"); private readonly static Guid s_createExtractToCodeBehindTelemetryId = new("f63167f7-fdc6-450f-8b7b-b240892f4a27"); - private readonly static Guid s_createExtractToNewComponentTelemetryId = new("af67b0a3-f84b-4808-97a7-b53e85b22c64"); + private readonly static Guid s_createExtractToComponentTelemetryId = new("af67b0a3-f84b-4808-97a7-b53e85b22c64"); private readonly static Guid s_generateMethodTelemetryId = new("c14fa003-c752-45fc-bb29-3a123ae5ecef"); private readonly static Guid s_generateAsyncMethodTelemetryId = new("9058ca47-98e2-4f11-bf7c-a16a444dd939"); @@ -68,15 +68,15 @@ public static RazorVSInternalCodeAction CreateExtractToCodeBehind(RazorCodeActio return codeAction; } - public static RazorVSInternalCodeAction CreateExtractToNewComponent(RazorCodeActionResolutionParams resolutionParams) + public static RazorVSInternalCodeAction CreateExtractToComponent(RazorCodeActionResolutionParams resolutionParams) { - var title = SR.ExtractTo_NewComponent_Title; + var title = SR.ExtractTo_Component_Title; var data = JsonSerializer.SerializeToElement(resolutionParams); var codeAction = new RazorVSInternalCodeAction() { Title = title, Data = data, - TelemetryId = s_createExtractToNewComponentTelemetryId, + TelemetryId = s_createExtractToComponentTelemetryId, }; return codeAction; } diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Extensions/IServiceCollectionExtensions.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Extensions/IServiceCollectionExtensions.cs index a4e0cc1bc80..dad0994085a 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Extensions/IServiceCollectionExtensions.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Extensions/IServiceCollectionExtensions.cs @@ -146,8 +146,8 @@ public static void AddCodeActionsServices(this IServiceCollection services) // Razor Code actions services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/SR.resx b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/SR.resx index b94c06607a4..cd69afc13d3 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/SR.resx +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/SR.resx @@ -183,7 +183,7 @@ statement - + Extract element to new component \ No newline at end of file diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.cs.xlf b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.cs.xlf index e9bd2e979f3..de1af9b86ea 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.cs.xlf +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.cs.xlf @@ -47,7 +47,7 @@ Extrahovat blok do kódu na pozadí - + Extract element to new component Extract element to new component diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.de.xlf b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.de.xlf index 94402ecb4de..7da645a36d2 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.de.xlf +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.de.xlf @@ -47,7 +47,7 @@ Block auf CodeBehind extrahieren - + Extract element to new component Extract element to new component diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.es.xlf b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.es.xlf index 3e5b8b11c1e..72e2980b035 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.es.xlf +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.es.xlf @@ -47,7 +47,7 @@ Extraer el bloque al código subyacente - + Extract element to new component Extract element to new component diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.fr.xlf b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.fr.xlf index 4808f1c26a4..0c3ee61e8de 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.fr.xlf +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.fr.xlf @@ -47,7 +47,7 @@ Extraire le bloc vers le code-behind - + Extract element to new component Extract element to new component diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.it.xlf b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.it.xlf index 33470714554..20333e772ac 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.it.xlf +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.it.xlf @@ -47,7 +47,7 @@ Estrai il blocco in code-behind - + Extract element to new component Extract element to new component diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.ja.xlf b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.ja.xlf index fd8f4765763..6016bbea305 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.ja.xlf +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.ja.xlf @@ -47,7 +47,7 @@ ブロック抽出から分離コード - + Extract element to new component Extract element to new component diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.ko.xlf b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.ko.xlf index 62d14e8d091..4579e5803b2 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.ko.xlf +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.ko.xlf @@ -47,7 +47,7 @@ 코드 숨김에 블록 추출 - + Extract element to new component Extract element to new component diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.pl.xlf b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.pl.xlf index b5ca44e0fb3..765d2f018bc 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.pl.xlf +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.pl.xlf @@ -47,7 +47,7 @@ Wyodrębnij blok do kodu znajdującego się poza - + Extract element to new component Extract element to new component diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.pt-BR.xlf b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.pt-BR.xlf index 023270c89c0..62ad6fd0364 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.pt-BR.xlf +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.pt-BR.xlf @@ -47,7 +47,7 @@ Extrair o bloco para codificar atrás - + Extract element to new component Extract element to new component diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.ru.xlf b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.ru.xlf index 9b4b541b350..df1d6913ba1 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.ru.xlf +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.ru.xlf @@ -47,7 +47,7 @@ Извлечь блок в код программной части - + Extract element to new component Extract element to new component diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.tr.xlf b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.tr.xlf index 211840b046c..002635c7ab8 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.tr.xlf +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.tr.xlf @@ -47,7 +47,7 @@ Bloğu arkadaki koda ayıkla - + Extract element to new component Extract element to new component diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.zh-Hans.xlf b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.zh-Hans.xlf index c746d3ecc08..c75b40f673c 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.zh-Hans.xlf +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.zh-Hans.xlf @@ -47,7 +47,7 @@ 将块提取到代码隐藏中 - + Extract element to new component Extract element to new component diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.zh-Hant.xlf b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.zh-Hant.xlf index ddd96b48565..588a1945b6c 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.zh-Hant.xlf +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Resources/xlf/SR.zh-Hant.xlf @@ -47,7 +47,7 @@ 擷取區塊以在後方編碼 - + Extract element to new component Extract element to new component diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToComponentCodeActionProviderTest.cs similarity index 96% rename from src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs rename to src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToComponentCodeActionProviderTest.cs index 67ff43f3dbd..eb27c0b6450 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToNewComponentCodeActionProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToComponentCodeActionProviderTest.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Test.CodeActions.Razor; -public class ExtractToNewComponentCodeActionProviderTest(ITestOutputHelper testOutput) : LanguageServerTestBase(testOutput) +public class ExtractToComponentCodeActionProviderTest(ITestOutputHelper testOutput) : LanguageServerTestBase(testOutput) { [Fact] public async Task Handle_InvalidFileKind() @@ -63,7 +63,7 @@ public async Task Handle_InvalidFileKind() var context = CreateRazorCodeActionContext(request, location, documentPath, contents); context.CodeDocument.SetFileKind(FileKinds.Legacy); - var provider = new ExtractToNewComponentCodeActionProvider(LoggerFactory); + var provider = new ExtractToComponentCodeActionProvider(LoggerFactory); // Act var commandOrCodeActionContainer = await provider.ProvideAsync(context, default); @@ -109,7 +109,7 @@ public async Task Handle_SinglePointSelection_ReturnsNotEmpty() var location = new SourceLocation(cursorPosition, -1, -1); var context = CreateRazorCodeActionContext(request, location, documentPath, contents, supportsFileCreation: true); - var provider = new ExtractToNewComponentCodeActionProvider(LoggerFactory); + var provider = new ExtractToComponentCodeActionProvider(LoggerFactory); // Act var commandOrCodeActionContainer = await provider.ProvideAsync(context, default); @@ -165,7 +165,7 @@ public string someFunc(int num) { var location = new SourceLocation(cursorPosition, -1, -1); var context = CreateRazorCodeActionContext(request, location, documentPath, contents); - var provider = new ExtractToNewComponentCodeActionProvider(LoggerFactory); + var provider = new ExtractToComponentCodeActionProvider(LoggerFactory); // Act var commandOrCodeActionContainer = await provider.ProvideAsync(context, default); @@ -277,7 +277,7 @@ namespace MarketApp.Pages.Product.Home var lineSpan = context.SourceText.GetLinePositionSpan(selectionSpan); request.Range = VsLspFactory.CreateRange(lineSpan); - var provider = new ExtractToNewComponentCodeActionProvider(LoggerFactory); + var provider = new ExtractToComponentCodeActionProvider(LoggerFactory); // Act var commandOrCodeActionContainer = await provider.ProvideAsync(context, default); @@ -330,7 +330,7 @@ public async Task Handle_InProperMarkup_ReturnsEmpty() var location = new SourceLocation(cursorPosition, -1, -1); var context = CreateRazorCodeActionContext(request, location, documentPath, contents); - var provider = new ExtractToNewComponentCodeActionProvider(LoggerFactory); + var provider = new ExtractToComponentCodeActionProvider(LoggerFactory); // Act var commandOrCodeActionContainer = await provider.ProvideAsync(context, default); From 5bab54836f2daa5d91d3fa9097f4e5bf1f47c3be Mon Sep 17 00:00:00 2001 From: marcarro Date: Wed, 7 Aug 2024 14:27:53 -0700 Subject: [PATCH 10/19] Set up basic end to end test for extract component --- .../CodeActionEndToEndTest.NetFx.cs | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CodeActionEndToEndTest.NetFx.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CodeActionEndToEndTest.NetFx.cs index 33b35e3df98..f5c15499e68 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CodeActionEndToEndTest.NetFx.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CodeActionEndToEndTest.NetFx.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Castle.Core.Logging; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Components; using Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Models; @@ -38,6 +39,7 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.CodeActions; public class CodeActionEndToEndTest(ITestOutputHelper testOutput) : SingleServerDelegatingEndpointTestBase(testOutput) { private const string GenerateEventHandlerTitle = "Generate Event Handler 'DoesNotExist'"; + private const string ExtractToComponentTitle = "Extract element to new component"; private const string GenerateAsyncEventHandlerTitle = "Generate Async Event Handler 'DoesNotExist'"; private const string GenerateEventHandlerReturnType = "void"; private const string GenerateAsyncEventHandlerReturnType = "global::System.Threading.Tasks.Task"; @@ -59,6 +61,17 @@ private GenerateMethodCodeActionResolver[] CreateRazorCodeActionResolvers( razorFormattingService) ]; + // TODO: Make this func + private ExtractToComponentCodeActionResolver[] CreateExtractComponentCodeActionResolvers(string filePath, RazorCodeDocument codeDocument) + { + var emptyDocumentContextFactory = new TestDocumentContextFactory(); + return [ + new ExtractToComponentCodeActionResolver( + new GenerateMethodResolverDocumentContextFactory(filePath, codeDocument), + TestLanguageServerFeatureOptions.Instance) + ]; + } + #region CSharp CodeAction Tests [Fact] @@ -1005,6 +1018,36 @@ await ValidateCodeActionAsync(input, diagnostics: [new Diagnostic() { Code = "CS0103", Message = "The name 'DoesNotExist' does not exist in the current context" }]); } + [Fact] + public async Task Handle_ExtractComponent() + { + var input = """ + <[||]div id="a"> +

Div a title

+ +

Div a par

+ +
+ +
+ """; + + var expectedRazorComponent = """ +
+

Div a title

+ +

Div a par

+
+ """; + + await ValidateExtractComponentCodeActionAsync( + input, + expectedRazorComponent, + ExtractToComponentTitle, + razorCodeActionProviders: [new ExtractToComponentCodeActionProvider(LoggerFactory)], + codeActionResolversCreator: CreateExtractComponentCodeActionResolvers); + } + #endregion private async Task ValidateCodeBehindFileAsync( @@ -1148,6 +1191,66 @@ private async Task ValidateCodeActionAsync( AssertEx.EqualOrDiff(expected, actual); } + private async Task ValidateExtractComponentCodeActionAsync( + string input, + string? expected, + string codeAction, + int childActionIndex = 0, + IRazorCodeActionProvider[]? razorCodeActionProviders = null, + Func? codeActionResolversCreator = null, + RazorLSPOptionsMonitor? optionsMonitor = null, + Diagnostic[]? diagnostics = null) + { + TestFileMarkupParser.GetSpan(input, out input, out var textSpan); + + var razorFilePath = "C:/path/test.razor"; + var componentFilePath = "C:/path/Component.razor"; + var codeDocument = CreateCodeDocument(input, filePath: razorFilePath); + var sourceText = codeDocument.GetSourceText(); + var uri = new Uri(razorFilePath); + var languageServer = await CreateLanguageServerAsync(codeDocument, razorFilePath); + var documentContext = CreateDocumentContext(uri, codeDocument); + var requestContext = new RazorRequestContext(documentContext, null!, "lsp/method", uri: null); + + var result = await GetCodeActionsAsync( + uri, + textSpan, + sourceText, + requestContext, + languageServer, + razorCodeActionProviders, + diagnostics); + + Assert.NotEmpty(result); + var codeActionToRun = GetCodeActionToRun(codeAction, childActionIndex, result); + + if (expected is null) + { + Assert.Null(codeActionToRun); + return; + } + + Assert.NotNull(codeActionToRun); + + var formattingService = await TestRazorFormattingService.CreateWithFullSupportAsync(LoggerFactory, codeDocument, documentContext.Snapshot, optionsMonitor?.CurrentValue); + var changes = await GetEditsAsync( + codeActionToRun, + requestContext, + languageServer, + codeActionResolversCreator?.Invoke(razorFilePath, codeDocument) ?? []); + + var edits = new List(); + + // Only get changes made in the new component file + foreach (var change in changes.Where(e => e.TextDocument.Uri.AbsolutePath == componentFilePath)) + { + edits.AddRange(change.Edits.Select(e => e.ToTextChange(sourceText))); + } + + var actual = sourceText.WithChanges(edits).ToString(); + AssertEx.EqualOrDiff(expected, actual); + } + private static VSInternalCodeAction? GetCodeActionToRun(string codeAction, int childActionIndex, SumType[] result) { var codeActionToRun = (VSInternalCodeAction?)result.SingleOrDefault(e => ((RazorVSInternalCodeAction)e.Value!).Name == codeAction || ((RazorVSInternalCodeAction)e.Value!).Title == codeAction).Value; @@ -1306,4 +1409,78 @@ static IEnumerable BuildTagHelpers() } } } + + private class ExtractToComponentResolverDocumentContextFactory : TestDocumentContextFactory + { + private readonly List _tagHelperDescriptors; + + public ExtractToComponentResolverDocumentContextFactory + (string filePath, + RazorCodeDocument codeDocument, + TagHelperDescriptor[]? tagHelpers = null, + int? version = null) + : base(filePath, codeDocument, version) + { + _tagHelperDescriptors = CreateTagHelperDescriptors(); + if (tagHelpers is not null) + { + _tagHelperDescriptors.AddRange(tagHelpers); + } + } + + public override bool TryCreate( + Uri documentUri, + VSProjectContext? projectContext, + bool versioned, + [NotNullWhen(true)] out DocumentContext? context) + { + if (FilePath is null || CodeDocument is null) + { + context = null; + return false; + } + + var projectWorkspaceState = ProjectWorkspaceState.Create(_tagHelperDescriptors.ToImmutableArray()); + var testDocumentSnapshot = TestDocumentSnapshot.Create(FilePath, CodeDocument.GetSourceText().ToString(), CodeAnalysis.VersionStamp.Default, projectWorkspaceState); + testDocumentSnapshot.With(CodeDocument); + + context = CreateDocumentContext(new Uri(FilePath), testDocumentSnapshot); + return true; + } + + private static List CreateTagHelperDescriptors() + { + return BuildTagHelpers().ToList(); + + static IEnumerable BuildTagHelpers() + { + var builder = TagHelperDescriptorBuilder.Create("oncontextmenu", "Microsoft.AspNetCore.Components"); + builder.SetMetadata( + new KeyValuePair(ComponentMetadata.EventHandler.EventArgsType, "Microsoft.AspNetCore.Components.Web.MouseEventArgs"), + new KeyValuePair(ComponentMetadata.SpecialKindKey, ComponentMetadata.EventHandler.TagHelperKind)); + yield return builder.Build(); + + builder = TagHelperDescriptorBuilder.Create("onclick", "Microsoft.AspNetCore.Components"); + builder.SetMetadata( + new KeyValuePair(ComponentMetadata.EventHandler.EventArgsType, "Microsoft.AspNetCore.Components.Web.MouseEventArgs"), + new KeyValuePair(ComponentMetadata.SpecialKindKey, ComponentMetadata.EventHandler.TagHelperKind)); + + yield return builder.Build(); + + builder = TagHelperDescriptorBuilder.Create("oncopy", "Microsoft.AspNetCore.Components"); + builder.SetMetadata( + new KeyValuePair(ComponentMetadata.EventHandler.EventArgsType, "Microsoft.AspNetCore.Components.Web.ClipboardEventArgs"), + new KeyValuePair(ComponentMetadata.SpecialKindKey, ComponentMetadata.EventHandler.TagHelperKind)); + + yield return builder.Build(); + + builder = TagHelperDescriptorBuilder.Create("ref", "Microsoft.AspNetCore.Components"); + builder.SetMetadata( + new KeyValuePair(ComponentMetadata.SpecialKindKey, ComponentMetadata.Ref.TagHelperKind), + new KeyValuePair(ComponentMetadata.Common.DirectiveAttribute, bool.TrueString)); + + yield return builder.Build(); + } + } + } } From 83703f6bea72098cf786b7c1df65d6d67173c684 Mon Sep 17 00:00:00 2001 From: marcarro Date: Thu, 22 Aug 2024 13:32:10 -0700 Subject: [PATCH 11/19] Finished fixing rebase stuff --- ... => ExtractToComponentCodeActionParams.cs} | 4 +- .../ExtractToComponentCodeActionProvider.cs | 20 +++--- .../ExtractToComponentCodeActionResolver.cs | 2 +- .../CodeActionEndToEndTest.NetFx.cs | 6 +- ...xtractToComponentCodeActionProviderTest.cs | 68 +------------------ 5 files changed, 18 insertions(+), 82 deletions(-) rename src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Models/{ExtractToNewComponentCodeActionParams.cs => ExtractToComponentCodeActionParams.cs} (78%) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Models/ExtractToNewComponentCodeActionParams.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Models/ExtractToComponentCodeActionParams.cs similarity index 78% rename from src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Models/ExtractToNewComponentCodeActionParams.cs rename to src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Models/ExtractToComponentCodeActionParams.cs index 91a3c1b498f..25b7960d216 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Models/ExtractToNewComponentCodeActionParams.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Models/ExtractToComponentCodeActionParams.cs @@ -7,7 +7,9 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Models; -internal sealed class ExtractToNewComponentCodeActionParams +// NOTE: As mentioned before, these have changed in future PRs, where much of the Provider logic was moved to the resolver. +// The last three properties are not used in the current implementation. +internal sealed class ExtractToComponentCodeActionParams { [JsonPropertyName("uri")] public required Uri Uri { get; set; } diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs index e8dc1cd8456..a4f0220d239 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs @@ -22,7 +22,6 @@ using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; -using Range = Microsoft.VisualStudio.LanguageServer.Protocol.Range; namespace Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Razor; @@ -72,10 +71,7 @@ public Task> ProvideAsync(RazorCodeAct var actionParams = CreateInitialActionParams(context, startElementNode, @namespace); - if (IsMultiPointSelection(context.Request.Range)) - { - ProcessMultiPointSelection(startElementNode, endElementNode, actionParams); - } + ProcessSelection(startElementNode, endElementNode, actionParams); var utilityScanRoot = FindNearestCommonAncestor(startElementNode, endElementNode) ?? startElementNode; AddComponentDependenciesInRange(utilityScanRoot, @@ -110,7 +106,7 @@ private static (MarkupElementSyntax? Start, MarkupElementSyntax? End) GetStartAn return (null, null); } - var endElementNode = GetEndElementNode(context, syntaxTree, logger); + var endElementNode = GetEndElementNode(context, syntaxTree); return (startElementNode, endElementNode); } @@ -152,9 +148,9 @@ private static bool IsInsideProperHtmlContent(RazorCodeActionContext context, Ma return endOwner.FirstAncestorOrSelf(); } - private static ExtractToNewComponentCodeActionParams CreateInitialActionParams(RazorCodeActionContext context, MarkupElementSyntax startElementNode, string @namespace) + private static ExtractToComponentCodeActionParams CreateInitialActionParams(RazorCodeActionContext context, MarkupElementSyntax startElementNode, string @namespace) { - return new ExtractToNewComponentCodeActionParams + return new ExtractToComponentCodeActionParams { Uri = context.Request.TextDocument.Uri, ExtractStart = startElementNode.Span.Start, @@ -172,7 +168,7 @@ private static ExtractToNewComponentCodeActionParams CreateInitialActionParams(R /// The starting element of the selection. /// The ending element of the selection, if it exists. /// The parameters for the extraction action, which will be updated. - private static void ProcessSelection(MarkupElementSyntax startElementNode, MarkupElementSyntax? endElementNode, ExtractToNewComponentCodeActionParams actionParams) + private static void ProcessSelection(MarkupElementSyntax startElementNode, MarkupElementSyntax? endElementNode, ExtractToComponentCodeActionParams actionParams) { // If there's no end element, we can't process a multi-point selection if (endElementNode is null) @@ -278,7 +274,7 @@ private static (SyntaxNode? Start, SyntaxNode? End) FindContainingSiblingPair(Sy return null; } - private static void AddComponentDependenciesInRange(SyntaxNode root, int extractStart, int extractEnd, ExtractToNewComponentCodeActionParams actionParams) + private static void AddComponentDependenciesInRange(SyntaxNode root, int extractStart, int extractEnd, ExtractToComponentCodeActionParams actionParams) { var components = new HashSet(); var extractSpan = new TextSpan(extractStart, extractEnd - extractStart); @@ -312,7 +308,7 @@ private static bool IsMarkupTagHelperElement(SyntaxNode node, TextSpan extractSp return null; } - private static void AddDependenciesFromTagHelperInfo(TagHelperInfo tagHelperInfo, HashSet components, ExtractToNewComponentCodeActionParams actionParams) + private static void AddDependenciesFromTagHelperInfo(TagHelperInfo tagHelperInfo, HashSet components, ExtractToComponentCodeActionParams actionParams) { foreach (var descriptor in tagHelperInfo.BindingResult.Descriptors) { @@ -332,7 +328,7 @@ metadata.Value is not null && } } - private static void GetUsedIdentifiers(SyntaxNode divNode, SyntaxNode documentRoot, ExtractToNewComponentCodeActionParams actionParams) + private static void GetUsedIdentifiers(SyntaxNode divNode, SyntaxNode documentRoot, ExtractToComponentCodeActionParams actionParams) { HashSet identifiersInScope = []; HashSet identifiersInBlock = []; diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionResolver.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionResolver.cs index 920267c632a..f29ce6dab30 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionResolver.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionResolver.cs @@ -45,7 +45,7 @@ internal sealed class ExtractToComponentCodeActionResolver return null; } - var actionParams = JsonSerializer.Deserialize(data.GetRawText()); + var actionParams = JsonSerializer.Deserialize(data.GetRawText()); if (actionParams is null) { return null; diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CodeActionEndToEndTest.NetFx.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CodeActionEndToEndTest.NetFx.cs index f5c15499e68..040a227f907 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CodeActionEndToEndTest.NetFx.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CodeActionEndToEndTest.NetFx.cs @@ -1206,7 +1206,7 @@ private async Task ValidateExtractComponentCodeActionAsync( var razorFilePath = "C:/path/test.razor"; var componentFilePath = "C:/path/Component.razor"; var codeDocument = CreateCodeDocument(input, filePath: razorFilePath); - var sourceText = codeDocument.GetSourceText(); + var sourceText = codeDocument.Source.Text; var uri = new Uri(razorFilePath); var languageServer = await CreateLanguageServerAsync(codeDocument, razorFilePath); var documentContext = CreateDocumentContext(uri, codeDocument); @@ -1244,7 +1244,7 @@ private async Task ValidateExtractComponentCodeActionAsync( // Only get changes made in the new component file foreach (var change in changes.Where(e => e.TextDocument.Uri.AbsolutePath == componentFilePath)) { - edits.AddRange(change.Edits.Select(e => e.ToTextChange(sourceText))); + edits.AddRange(change.Edits.Select(sourceText.GetTextChange)); } var actual = sourceText.WithChanges(edits).ToString(); @@ -1441,7 +1441,7 @@ public override bool TryCreate( } var projectWorkspaceState = ProjectWorkspaceState.Create(_tagHelperDescriptors.ToImmutableArray()); - var testDocumentSnapshot = TestDocumentSnapshot.Create(FilePath, CodeDocument.GetSourceText().ToString(), CodeAnalysis.VersionStamp.Default, projectWorkspaceState); + var testDocumentSnapshot = TestDocumentSnapshot.Create(FilePath, CodeDocument.Source.Text.ToString(), CodeAnalysis.VersionStamp.Default, projectWorkspaceState); testDocumentSnapshot.With(CodeDocument); context = CreateDocumentContext(new Uri(FilePath), testDocumentSnapshot); diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToComponentCodeActionProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToComponentCodeActionProviderTest.cs index eb27c0b6450..d884dc9c0b9 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToComponentCodeActionProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToComponentCodeActionProviderTest.cs @@ -118,67 +118,6 @@ public async Task Handle_SinglePointSelection_ReturnsNotEmpty() Assert.NotEmpty(commandOrCodeActionContainer); } - [Fact] - public async Task Handle_CodeInsideDiv_ScansCorrect() - { - // Arrange - var documentPath = "c:/Test.cs"; - var contents = """ - @page "/" - - Home - - <$$div id="codeInside"> - @for(int idx = 0; idx < 10; idx++) { - string s = someFunc(idx * myField); - } - - -
-
-

Div a title

-

Div a par

-
-
-

Div b title

-

Div b par

-
-
- - @code { - public int myField = 7; - - public string someFunc(int num) { - return "Hello for number" + num; - } - } - """; - TestFileMarkupParser.GetPosition(contents, out contents, out var cursorPosition); - - var request = new VSCodeActionParams() - { - TextDocument = new VSTextDocumentIdentifier { Uri = new Uri(documentPath) }, - Range = new Range(), - Context = new VSInternalCodeActionContext() - }; - - var location = new SourceLocation(cursorPosition, -1, -1); - var context = CreateRazorCodeActionContext(request, location, documentPath, contents); - - var provider = new ExtractToComponentCodeActionProvider(LoggerFactory); - - // Act - var commandOrCodeActionContainer = await provider.ProvideAsync(context, default); - - // Assert - Assert.NotEmpty(commandOrCodeActionContainer); - var codeAction = Assert.Single(commandOrCodeActionContainer); - var razorCodeActionResolutionParams = ((JsonElement)codeAction.Data!).Deserialize(); - Assert.NotNull(razorCodeActionResolutionParams); - var actionParams = ((JsonElement)razorCodeActionResolutionParams.Data).Deserialize(); - Assert.NotNull(actionParams); - } - [Fact] public async Task Handle_MultiPointSelection_ReturnsNotEmpty() { @@ -215,12 +154,11 @@ public async Task Handle_MultiPointSelection_ReturnsNotEmpty() var location = new SourceLocation(cursorPosition, -1, -1); var context = CreateRazorCodeActionContext(request, location, documentPath, contents); - AddMultiPointSelectionToContext(ref context, selectionSpan); var lineSpan = context.SourceText.GetLinePositionSpan(selectionSpan); request.Range = VsLspFactory.CreateRange(lineSpan); - var provider = new ExtractToNewComponentCodeActionProvider(LoggerFactory); + var provider = new ExtractToComponentCodeActionProvider(LoggerFactory); // Act var commandOrCodeActionContainer = await provider.ProvideAsync(context, default); @@ -230,7 +168,7 @@ public async Task Handle_MultiPointSelection_ReturnsNotEmpty() var codeAction = Assert.Single(commandOrCodeActionContainer); var razorCodeActionResolutionParams = ((JsonElement)codeAction.Data!).Deserialize(); Assert.NotNull(razorCodeActionResolutionParams); - var actionParams = ((JsonElement)razorCodeActionResolutionParams.Data).Deserialize(); + var actionParams = ((JsonElement)razorCodeActionResolutionParams.Data).Deserialize(); Assert.NotNull(actionParams); } @@ -287,7 +225,7 @@ namespace MarketApp.Pages.Product.Home var codeAction = Assert.Single(commandOrCodeActionContainer); var razorCodeActionResolutionParams = ((JsonElement)codeAction.Data!).Deserialize(); Assert.NotNull(razorCodeActionResolutionParams); - var actionParams = ((JsonElement)razorCodeActionResolutionParams.Data).Deserialize(); + var actionParams = ((JsonElement)razorCodeActionResolutionParams.Data).Deserialize(); Assert.NotNull(actionParams); Assert.Equal(selectionSpan.Start, actionParams.ExtractStart); Assert.Equal(selectionSpan.End, actionParams.ExtractEnd); From 33c66f419d53062720ad04ff6c037e0364007ede Mon Sep 17 00:00:00 2001 From: marcarro Date: Sun, 25 Aug 2024 15:46:57 -0700 Subject: [PATCH 12/19] Some of the PR feedback that wasn't deferred to the next one, and added tests --- .../ExtractToComponentCodeActionProvider.cs | 50 +++------- .../CodeActionEndToEndTest.NetFx.cs | 94 +++++++++++++++---- 2 files changed, 93 insertions(+), 51 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs index a4f0220d239..a4f24717eca 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs @@ -261,7 +261,10 @@ private static (SyntaxNode? Start, SyntaxNode? End) FindContainingSiblingPair(Sy { var current = node1; - while (current is MarkupElementSyntax && current is not null) + while (current is MarkupElementSyntax or + MarkupTagHelperAttributeSyntax or + MarkupBlockSyntax && + current is not null) { if (current.Span.Contains(node2.Span)) { @@ -279,51 +282,28 @@ private static void AddComponentDependenciesInRange(SyntaxNode root, int extract var components = new HashSet(); var extractSpan = new TextSpan(extractStart, extractEnd - extractStart); - foreach (var node in root.DescendantNodes()) + foreach (var node in root.DescendantNodes().Where(node => extractSpan.Contains(node.Span))) { - if (IsMarkupTagHelperElement(node, extractSpan)) + if (node is MarkupTagHelperElementSyntax { TagHelperInfo: { } tagHelperInfo }) { - var tagHelperInfo = GetTagHelperInfo(node); - if (tagHelperInfo is not null) - { - AddDependenciesFromTagHelperInfo(tagHelperInfo, components, actionParams); - } + AddDependenciesFromTagHelperInfo(tagHelperInfo, components, actionParams); } } } - private static bool IsMarkupTagHelperElement(SyntaxNode node, TextSpan extractSpan) - { - return node is MarkupTagHelperElementSyntax markupElement && - extractSpan.Contains(markupElement.Span); - } - - private static TagHelperInfo? GetTagHelperInfo(SyntaxNode node) - { - if (node is MarkupTagHelperElementSyntax markupElement) - { - return markupElement.TagHelperInfo; - } - - return null; - } - private static void AddDependenciesFromTagHelperInfo(TagHelperInfo tagHelperInfo, HashSet components, ExtractToComponentCodeActionParams actionParams) { foreach (var descriptor in tagHelperInfo.BindingResult.Descriptors) { - if (descriptor is not null) + if (descriptor is null) + { + continue; + } + + var typeNamespace = descriptor.GetTypeNamespace(); + if (components.Add(typeNamespace)) { - foreach (var metadata in descriptor.Metadata) - { - if (metadata.Key == TagHelperMetadata.Common.TypeNamespace && - metadata.Value is not null && - !components.Contains(metadata.Value)) - { - components.Add(metadata.Value); - actionParams.Dependencies.Add($"@using {metadata.Value}"); - } - } + actionParams.Dependencies.Add($"@using {typeNamespace}"); } } } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CodeActionEndToEndTest.NetFx.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CodeActionEndToEndTest.NetFx.cs index 040a227f907..bde051c3a2b 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CodeActionEndToEndTest.NetFx.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CodeActionEndToEndTest.NetFx.cs @@ -61,10 +61,9 @@ private GenerateMethodCodeActionResolver[] CreateRazorCodeActionResolvers( razorFormattingService) ]; - // TODO: Make this func - private ExtractToComponentCodeActionResolver[] CreateExtractComponentCodeActionResolvers(string filePath, RazorCodeDocument codeDocument) + + private ExtractToComponentCodeActionResolver[] CreateExtractComponentCodeActionResolver(string filePath, RazorCodeDocument codeDocument) { - var emptyDocumentContextFactory = new TestDocumentContextFactory(); return [ new ExtractToComponentCodeActionResolver( new GenerateMethodResolverDocumentContextFactory(filePath, codeDocument), @@ -1019,7 +1018,7 @@ await ValidateCodeActionAsync(input, } [Fact] - public async Task Handle_ExtractComponent() + public async Task Handle_ExtractComponent_SingleElement_ReturnsResult() { var input = """ <[||]div id="a"> @@ -1045,7 +1044,75 @@ await ValidateExtractComponentCodeActionAsync( expectedRazorComponent, ExtractToComponentTitle, razorCodeActionProviders: [new ExtractToComponentCodeActionProvider(LoggerFactory)], - codeActionResolversCreator: CreateExtractComponentCodeActionResolvers); + codeActionResolversCreator: CreateExtractComponentCodeActionResolver); + } + + [Fact] + public async Task Handle_ExtractComponent_SiblingElement_ReturnsResult() + { + var input = """ + <[|div id="a"> +

Div a title

+ +

Div a par

+ +
+ + + """; + + var expectedRazorComponent = """ +
+

Div a title

+ +

Div a par

+
+
+ +
+ """; + + await ValidateExtractComponentCodeActionAsync( + input, + expectedRazorComponent, + ExtractToComponentTitle, + razorCodeActionProviders: [new ExtractToComponentCodeActionProvider(LoggerFactory)], + codeActionResolversCreator: CreateExtractComponentCodeActionResolver); + } + + [Fact] + public async Task Handle_ExtractComponent_StartNodeContainsEndNode_ReturnsResult() + { + var input = """ + <[|div id="parent"> +
+
+
+

Deeply nested par +

+
+
+
+ """; + + var expectedRazorComponent = """ +
+
+
+
+

Deeply nested par

+
+
+
+
+ """; + + await ValidateExtractComponentCodeActionAsync( + input, + expectedRazorComponent, + ExtractToComponentTitle, + razorCodeActionProviders: [new ExtractToComponentCodeActionProvider(LoggerFactory)], + codeActionResolversCreator: CreateExtractComponentCodeActionResolver); } #endregion @@ -1196,6 +1263,7 @@ private async Task ValidateExtractComponentCodeActionAsync( string? expected, string codeAction, int childActionIndex = 0, + IEnumerable<(string filePath, string contents)>? additionalRazorDocuments = null, IRazorCodeActionProvider[]? razorCodeActionProviders = null, Func? codeActionResolversCreator = null, RazorLSPOptionsMonitor? optionsMonitor = null, @@ -1203,12 +1271,12 @@ private async Task ValidateExtractComponentCodeActionAsync( { TestFileMarkupParser.GetSpan(input, out input, out var textSpan); - var razorFilePath = "C:/path/test.razor"; - var componentFilePath = "C:/path/Component.razor"; + var razorFilePath = "C:/path/to/test.razor"; + var componentFilePath = "C:/path/to/Component.razor"; var codeDocument = CreateCodeDocument(input, filePath: razorFilePath); var sourceText = codeDocument.Source.Text; var uri = new Uri(razorFilePath); - var languageServer = await CreateLanguageServerAsync(codeDocument, razorFilePath); + var languageServer = await CreateLanguageServerAsync(codeDocument, razorFilePath, additionalRazorDocuments); var documentContext = CreateDocumentContext(uri, codeDocument); var requestContext = new RazorRequestContext(documentContext, null!, "lsp/method", uri: null); @@ -1239,15 +1307,9 @@ private async Task ValidateExtractComponentCodeActionAsync( languageServer, codeActionResolversCreator?.Invoke(razorFilePath, codeDocument) ?? []); - var edits = new List(); - - // Only get changes made in the new component file - foreach (var change in changes.Where(e => e.TextDocument.Uri.AbsolutePath == componentFilePath)) - { - edits.AddRange(change.Edits.Select(sourceText.GetTextChange)); - } + var edits = changes.Where(change => change.TextDocument.Uri.AbsolutePath == componentFilePath).Single(); + var actual = edits.Edits.Select(edit => edit.NewText).Single(); - var actual = sourceText.WithChanges(edits).ToString(); AssertEx.EqualOrDiff(expected, actual); } From b170f2a0ced2b22b972fb8ec79085476bee68a0c Mon Sep 17 00:00:00 2001 From: marcarro Date: Sun, 25 Aug 2024 15:54:01 -0700 Subject: [PATCH 13/19] More PR Feedback --- .../ExtractToComponentCodeActionParams.cs | 10 ++---- .../ExtractToComponentCodeActionProvider.cs | 35 ++----------------- .../ExtractToComponentCodeActionResolver.cs | 4 +-- 3 files changed, 6 insertions(+), 43 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Models/ExtractToComponentCodeActionParams.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Models/ExtractToComponentCodeActionParams.cs index 25b7960d216..3834355e6c5 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Models/ExtractToComponentCodeActionParams.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Models/ExtractToComponentCodeActionParams.cs @@ -23,12 +23,6 @@ internal sealed class ExtractToComponentCodeActionParams [JsonPropertyName("namespace")] public required string Namespace { get; set; } - [JsonPropertyName("dependencies")] - public required List Dependencies { get; set; } - - [JsonPropertyName("usedIdentifiers")] - public required HashSet UsedIdentifiers { get; set; } - - [JsonPropertyName("usedMembers")] - public required HashSet UsedMembers { get; set; } + [JsonPropertyName("usingDirectives")] + public required List usingDirectives { get; set; } } diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs index a4f24717eca..eed7f65adce 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs @@ -78,7 +78,6 @@ public Task> ProvideAsync(RazorCodeAct actionParams.ExtractStart, actionParams.ExtractEnd, actionParams); - GetUsedIdentifiers(utilityScanRoot, syntaxTree.Root, actionParams); var resolutionParams = new RazorCodeActionResolutionParams() { @@ -156,9 +155,7 @@ private static ExtractToComponentCodeActionParams CreateInitialActionParams(Razo ExtractStart = startElementNode.Span.Start, ExtractEnd = startElementNode.Span.End, Namespace = @namespace, - Dependencies = [], - UsedIdentifiers = [], - UsedMembers = [], + usingDirectives = [] }; } @@ -303,36 +300,8 @@ private static void AddDependenciesFromTagHelperInfo(TagHelperInfo tagHelperInfo var typeNamespace = descriptor.GetTypeNamespace(); if (components.Add(typeNamespace)) { - actionParams.Dependencies.Add($"@using {typeNamespace}"); + actionParams.usingDirectives.Add($"@using {typeNamespace}"); } } } - - private static void GetUsedIdentifiers(SyntaxNode divNode, SyntaxNode documentRoot, ExtractToComponentCodeActionParams actionParams) - { - HashSet identifiersInScope = []; - HashSet identifiersInBlock = []; - - HashSet nodesInScope = []; - HashSet nodesInBlock = []; - - foreach (var node in divNode.DescendantNodes().Where(static node => node.Kind is SyntaxKind.Identifier)) - { - identifiersInScope.Add(node.GetContent()); - nodesInScope.Add(node); - } - - foreach (var codeBlock in documentRoot.DescendantNodes().Where(static node => node.Kind is SyntaxKind.RazorDirective)) - { - foreach (var node in codeBlock.DescendantNodes().Where(static node => node.Kind is SyntaxKind.Identifier)) - { - identifiersInBlock.Add(node.GetContent()); - nodesInBlock.Add(node); - } - } - - nodesInBlock.IntersectWith(nodesInScope); - identifiersInBlock.IntersectWith(identifiersInScope); - actionParams.UsedIdentifiers = identifiersInBlock; - } } diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionResolver.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionResolver.cs index f29ce6dab30..4cff9dc9a9d 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionResolver.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionResolver.cs @@ -93,8 +93,8 @@ internal sealed class ExtractToComponentCodeActionResolver var componentName = Path.GetFileNameWithoutExtension(componentPath); var newComponentContent = string.Empty; - newComponentContent += string.Join(Environment.NewLine, actionParams.Dependencies); - if (actionParams.Dependencies.Count > 0) + newComponentContent += string.Join(Environment.NewLine, actionParams.usingDirectives); + if (actionParams.usingDirectives.Count > 0) { newComponentContent += Environment.NewLine + Environment.NewLine; // Ensure there's a newline after the dependencies if any exist. } From 5b7e0c6b72d021777e2263e38a2189bfed64a7ec Mon Sep 17 00:00:00 2001 From: marcarro Date: Sun, 25 Aug 2024 16:00:50 -0700 Subject: [PATCH 14/19] Naming corrections --- .../Razor/ExtractToComponentCodeActionProvider.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs index eed7f65adce..0baa903c4fd 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs @@ -74,7 +74,7 @@ public Task> ProvideAsync(RazorCodeAct ProcessSelection(startElementNode, endElementNode, actionParams); var utilityScanRoot = FindNearestCommonAncestor(startElementNode, endElementNode) ?? startElementNode; - AddComponentDependenciesInRange(utilityScanRoot, + AddUsingDirectivesInRange(utilityScanRoot, actionParams.ExtractStart, actionParams.ExtractEnd, actionParams); @@ -274,7 +274,7 @@ MarkupTagHelperAttributeSyntax or return null; } - private static void AddComponentDependenciesInRange(SyntaxNode root, int extractStart, int extractEnd, ExtractToComponentCodeActionParams actionParams) + private static void AddUsingDirectivesInRange(SyntaxNode root, int extractStart, int extractEnd, ExtractToComponentCodeActionParams actionParams) { var components = new HashSet(); var extractSpan = new TextSpan(extractStart, extractEnd - extractStart); @@ -283,12 +283,12 @@ private static void AddComponentDependenciesInRange(SyntaxNode root, int extract { if (node is MarkupTagHelperElementSyntax { TagHelperInfo: { } tagHelperInfo }) { - AddDependenciesFromTagHelperInfo(tagHelperInfo, components, actionParams); + AddUsingFromTagHelperInfo(tagHelperInfo, components, actionParams); } } } - private static void AddDependenciesFromTagHelperInfo(TagHelperInfo tagHelperInfo, HashSet components, ExtractToComponentCodeActionParams actionParams) + private static void AddUsingFromTagHelperInfo(TagHelperInfo tagHelperInfo, HashSet components, ExtractToComponentCodeActionParams actionParams) { foreach (var descriptor in tagHelperInfo.BindingResult.Descriptors) { From 3d9614c55a7ce0a3a91639346a484cd278ef3669 Mon Sep 17 00:00:00 2001 From: marcarro Date: Mon, 26 Aug 2024 10:37:45 -0700 Subject: [PATCH 15/19] Exclude usings that are already in _Imports.razor --- .../ExtractToComponentCodeActionProvider.cs | 51 ++++++++++++++++--- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs index 0baa903c4fd..160c491e164 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Linq; using System.Text; using System.Threading; @@ -16,8 +17,11 @@ using Microsoft.AspNetCore.Razor.Language.Intermediate; using Microsoft.AspNetCore.Razor.Language.Syntax; using Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Models; +using Microsoft.AspNetCore.Razor.PooledObjects; using Microsoft.AspNetCore.Razor.Threading; +using Microsoft.AspNetCore.Razor.Utilities; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor.Logging; using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.CodeAnalysis.Text; @@ -71,13 +75,19 @@ public Task> ProvideAsync(RazorCodeAct var actionParams = CreateInitialActionParams(context, startElementNode, @namespace); + var path = FilePathNormalizer.Normalize(actionParams.Uri.GetAbsoluteOrUNCPath()); + var directoryName = Path.GetDirectoryName(path).AssumeNotNull(); + var importsBuilder = new StringBuilder(); + var usingsInImportsFile = GetUsingsInImportsFile(directoryName, importsBuilder); + ProcessSelection(startElementNode, endElementNode, actionParams); var utilityScanRoot = FindNearestCommonAncestor(startElementNode, endElementNode) ?? startElementNode; AddUsingDirectivesInRange(utilityScanRoot, - actionParams.ExtractStart, - actionParams.ExtractEnd, - actionParams); + usingsInImportsFile, + actionParams.ExtractStart, + actionParams.ExtractEnd, + actionParams); var resolutionParams = new RazorCodeActionResolutionParams() { @@ -159,6 +169,29 @@ private static ExtractToComponentCodeActionParams CreateInitialActionParams(Razo }; } + // When adding usings, we must check for the first _Imports.razor file in the current directory or any parent directories. + // In the new component, only add usings that are not already present in the _Imports.razor file. + public static string GetUsingsInImportsFile(string currentDirectory, StringBuilder importsBuilder) + { + var importsFile = Path.Combine(currentDirectory, "_Imports.razor"); + if (File.Exists(importsFile)) + { + return File.ReadAllLines(importsFile) + .Where(line => line.TrimStart().StartsWith("@using")) + .Select(line => line.Trim()) + .Aggregate(importsBuilder, (sb, line) => sb.AppendLine(line)) + .ToString(); + } + + var parentDirectory = Path.GetDirectoryName(currentDirectory); + if (parentDirectory is not null && Directory.Exists(parentDirectory)) + { + return GetUsingsInImportsFile(parentDirectory, importsBuilder); + } + + return string.Empty; + } + /// /// Processes a multi-point selection to determine the correct range for extraction. /// @@ -274,7 +307,7 @@ MarkupTagHelperAttributeSyntax or return null; } - private static void AddUsingDirectivesInRange(SyntaxNode root, int extractStart, int extractEnd, ExtractToComponentCodeActionParams actionParams) + private static void AddUsingDirectivesInRange(SyntaxNode root, string usingsInImportsFile, int extractStart, int extractEnd, ExtractToComponentCodeActionParams actionParams) { var components = new HashSet(); var extractSpan = new TextSpan(extractStart, extractEnd - extractStart); @@ -283,12 +316,12 @@ private static void AddUsingDirectivesInRange(SyntaxNode root, int extractStart, { if (node is MarkupTagHelperElementSyntax { TagHelperInfo: { } tagHelperInfo }) { - AddUsingFromTagHelperInfo(tagHelperInfo, components, actionParams); + AddUsingFromTagHelperInfo(tagHelperInfo, components, usingsInImportsFile, actionParams); } } } - private static void AddUsingFromTagHelperInfo(TagHelperInfo tagHelperInfo, HashSet components, ExtractToComponentCodeActionParams actionParams) + private static void AddUsingFromTagHelperInfo(TagHelperInfo tagHelperInfo, HashSet components, string usingsInImportsFile, ExtractToComponentCodeActionParams actionParams) { foreach (var descriptor in tagHelperInfo.BindingResult.Descriptors) { @@ -300,7 +333,11 @@ private static void AddUsingFromTagHelperInfo(TagHelperInfo tagHelperInfo, HashS var typeNamespace = descriptor.GetTypeNamespace(); if (components.Add(typeNamespace)) { - actionParams.usingDirectives.Add($"@using {typeNamespace}"); + var usingDirective = $"@using {typeNamespace}"; + if (!usingsInImportsFile.Contains(usingDirective)) + { + actionParams.usingDirectives.Add(usingDirective); + } } } } From d83031b7aa5cef1a7c8a04a55b49ab2dfb6da072 Mon Sep 17 00:00:00 2001 From: marcarro Date: Mon, 26 Aug 2024 12:42:00 -0700 Subject: [PATCH 16/19] Another approach for refining new component usings --- .../ExtractToComponentCodeActionProvider.cs | 50 +++++-------------- 1 file changed, 13 insertions(+), 37 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs index 160c491e164..417214b1b56 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs @@ -75,16 +75,19 @@ public Task> ProvideAsync(RazorCodeAct var actionParams = CreateInitialActionParams(context, startElementNode, @namespace); - var path = FilePathNormalizer.Normalize(actionParams.Uri.GetAbsoluteOrUNCPath()); - var directoryName = Path.GetDirectoryName(path).AssumeNotNull(); - var importsBuilder = new StringBuilder(); - var usingsInImportsFile = GetUsingsInImportsFile(directoryName, importsBuilder); - ProcessSelection(startElementNode, endElementNode, actionParams); var utilityScanRoot = FindNearestCommonAncestor(startElementNode, endElementNode) ?? startElementNode; + + // The new component usings are going to be a subset of the usings in the source razor file + var usingsInFile = context.SourceText.ToString() + .Split('\n') + .Where(line => line.TrimStart().StartsWith("@using")) + .Aggregate(new StringBuilder(), (sb, line) => sb.AppendLine(line)) + .ToString(); + AddUsingDirectivesInRange(utilityScanRoot, - usingsInImportsFile, + usingsInFile, actionParams.ExtractStart, actionParams.ExtractEnd, actionParams); @@ -169,29 +172,6 @@ private static ExtractToComponentCodeActionParams CreateInitialActionParams(Razo }; } - // When adding usings, we must check for the first _Imports.razor file in the current directory or any parent directories. - // In the new component, only add usings that are not already present in the _Imports.razor file. - public static string GetUsingsInImportsFile(string currentDirectory, StringBuilder importsBuilder) - { - var importsFile = Path.Combine(currentDirectory, "_Imports.razor"); - if (File.Exists(importsFile)) - { - return File.ReadAllLines(importsFile) - .Where(line => line.TrimStart().StartsWith("@using")) - .Select(line => line.Trim()) - .Aggregate(importsBuilder, (sb, line) => sb.AppendLine(line)) - .ToString(); - } - - var parentDirectory = Path.GetDirectoryName(currentDirectory); - if (parentDirectory is not null && Directory.Exists(parentDirectory)) - { - return GetUsingsInImportsFile(parentDirectory, importsBuilder); - } - - return string.Empty; - } - /// /// Processes a multi-point selection to determine the correct range for extraction. /// @@ -307,7 +287,7 @@ MarkupTagHelperAttributeSyntax or return null; } - private static void AddUsingDirectivesInRange(SyntaxNode root, string usingsInImportsFile, int extractStart, int extractEnd, ExtractToComponentCodeActionParams actionParams) + private static void AddUsingDirectivesInRange(SyntaxNode root, string usingsInFile, int extractStart, int extractEnd, ExtractToComponentCodeActionParams actionParams) { var components = new HashSet(); var extractSpan = new TextSpan(extractStart, extractEnd - extractStart); @@ -316,7 +296,7 @@ private static void AddUsingDirectivesInRange(SyntaxNode root, string usingsInIm { if (node is MarkupTagHelperElementSyntax { TagHelperInfo: { } tagHelperInfo }) { - AddUsingFromTagHelperInfo(tagHelperInfo, components, usingsInImportsFile, actionParams); + AddUsingFromTagHelperInfo(tagHelperInfo, components, usingsInFile, actionParams); } } } @@ -331,13 +311,9 @@ private static void AddUsingFromTagHelperInfo(TagHelperInfo tagHelperInfo, HashS } var typeNamespace = descriptor.GetTypeNamespace(); - if (components.Add(typeNamespace)) + if (components.Add(typeNamespace) && usingsInImportsFile.Contains(typeNamespace)) { - var usingDirective = $"@using {typeNamespace}"; - if (!usingsInImportsFile.Contains(usingDirective)) - { - actionParams.usingDirectives.Add(usingDirective); - } + actionParams.usingDirectives.Add($"@using {typeNamespace}"); } } } From 1c239a1f96a020a109ee7b29cb95af47bcf99282 Mon Sep 17 00:00:00 2001 From: marcarro Date: Mon, 26 Aug 2024 13:15:43 -0700 Subject: [PATCH 17/19] Use syntaxTree instead of sourceText --- .../Razor/ExtractToComponentCodeActionProvider.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs index 417214b1b56..9df3cc8a848 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs @@ -80,9 +80,10 @@ public Task> ProvideAsync(RazorCodeAct var utilityScanRoot = FindNearestCommonAncestor(startElementNode, endElementNode) ?? startElementNode; // The new component usings are going to be a subset of the usings in the source razor file - var usingsInFile = context.SourceText.ToString() - .Split('\n') - .Where(line => line.TrimStart().StartsWith("@using")) + var usingsInFile = syntaxTree.Root.DescendantNodes() + .OfType() + .Where(literal => literal.SerializedValue.Contains("using")) + .Select(literal => literal.SerializedValue) .Aggregate(new StringBuilder(), (sb, line) => sb.AppendLine(line)) .ToString(); From cd677c7a1f6a6671fdda31d483759d4ec1fd2eed Mon Sep 17 00:00:00 2001 From: marcarro Date: Mon, 26 Aug 2024 17:09:10 -0700 Subject: [PATCH 18/19] Adjustments to addUsingDirectives and adjusted for relative using namespaces --- .../ExtractToComponentCodeActionProvider.cs | 46 +++++++++++++------ 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs index 9df3cc8a848..027bdd239f9 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs @@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Razor.Language.Components; using Microsoft.AspNetCore.Razor.Language.Extensions; using Microsoft.AspNetCore.Razor.Language.Intermediate; +using Microsoft.AspNetCore.Razor.Language.Legacy; using Microsoft.AspNetCore.Razor.Language.Syntax; using Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Models; using Microsoft.AspNetCore.Razor.PooledObjects; @@ -79,16 +80,14 @@ public Task> ProvideAsync(RazorCodeAct var utilityScanRoot = FindNearestCommonAncestor(startElementNode, endElementNode) ?? startElementNode; - // The new component usings are going to be a subset of the usings in the source razor file - var usingsInFile = syntaxTree.Root.DescendantNodes() - .OfType() - .Where(literal => literal.SerializedValue.Contains("using")) - .Select(literal => literal.SerializedValue) - .Aggregate(new StringBuilder(), (sb, line) => sb.AppendLine(line)) - .ToString(); + // The new component usings are going to be a subset of the usings in the source razor file. + var usingStrings = syntaxTree.Root.DescendantNodes().Where(node => node.IsUsingDirective(out var _)).Select(node => node.ToFullString().TrimEnd()); + + // Get only the namespace after the "using" keyword. + var usingNamespaceStrings = usingStrings.Select(usingString => usingString.Substring("using ".Length)); AddUsingDirectivesInRange(utilityScanRoot, - usingsInFile, + usingNamespaceStrings, actionParams.ExtractStart, actionParams.ExtractEnd, actionParams); @@ -288,7 +287,7 @@ MarkupTagHelperAttributeSyntax or return null; } - private static void AddUsingDirectivesInRange(SyntaxNode root, string usingsInFile, int extractStart, int extractEnd, ExtractToComponentCodeActionParams actionParams) + private static void AddUsingDirectivesInRange(SyntaxNode root, IEnumerable usingsInSourceRazor, int extractStart, int extractEnd, ExtractToComponentCodeActionParams actionParams) { var components = new HashSet(); var extractSpan = new TextSpan(extractStart, extractEnd - extractStart); @@ -297,12 +296,12 @@ private static void AddUsingDirectivesInRange(SyntaxNode root, string usingsInFi { if (node is MarkupTagHelperElementSyntax { TagHelperInfo: { } tagHelperInfo }) { - AddUsingFromTagHelperInfo(tagHelperInfo, components, usingsInFile, actionParams); + AddUsingFromTagHelperInfo(tagHelperInfo, components, usingsInSourceRazor, actionParams); } } } - private static void AddUsingFromTagHelperInfo(TagHelperInfo tagHelperInfo, HashSet components, string usingsInImportsFile, ExtractToComponentCodeActionParams actionParams) + private static void AddUsingFromTagHelperInfo(TagHelperInfo tagHelperInfo, HashSet components, IEnumerable usingsInSourceRazor, ExtractToComponentCodeActionParams actionParams) { foreach (var descriptor in tagHelperInfo.BindingResult.Descriptors) { @@ -312,9 +311,30 @@ private static void AddUsingFromTagHelperInfo(TagHelperInfo tagHelperInfo, HashS } var typeNamespace = descriptor.GetTypeNamespace(); - if (components.Add(typeNamespace) && usingsInImportsFile.Contains(typeNamespace)) + + // Since the using directive at the top of the file may be relative and not absolute, + // we need to generate all possible partial namespaces from `typeNamespace`. + + // Potentially, the alternative could be to ask if the using namespace at the top is a substring of `typeNamespace`. + // The only potential edge case is if there are very similar namespaces where one + // is a substring of the other, but they're actually different (e.g., "My.App" and "My.Apple"). + + // Generate all possible partial namespaces from `typeNamespace`, from least to most specific + // (assuming that the user writes absolute `using` namespaces most of the time) + + // This is a bit inefficient because at most 'k' string operations are performed (k = parts in the namespace), + // for each potential using directive. + + var parts = typeNamespace.Split('.'); + for (var i = 0; i < parts.Length; i++) { - actionParams.usingDirectives.Add($"@using {typeNamespace}"); + var partialNamespace = string.Join(".", parts.Skip(i)); + + if (components.Add(partialNamespace) && usingsInSourceRazor.Contains(partialNamespace)) + { + actionParams.usingDirectives.Add($"@using {partialNamespace}"); + break; + } } } } From 851fb45733e0bfb1a40cb498ac301f95e5e7a34c Mon Sep 17 00:00:00 2001 From: marcarro Date: Mon, 26 Aug 2024 19:47:03 -0700 Subject: [PATCH 19/19] Nits. I'm not sure why tests are failing --- .../ExtractToComponentCodeActionProvider.cs | 18 +++--------------- .../CodeActionEndToEndTest.NetFx.cs | 1 - 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs index 027bdd239f9..d6d8967308a 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToComponentCodeActionProvider.cs @@ -5,26 +5,14 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; -using System.IO; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; -using ICSharpCode.Decompiler.CSharp.Syntax; using Microsoft.AspNetCore.Razor.Language; -using Microsoft.AspNetCore.Razor.Language.Components; -using Microsoft.AspNetCore.Razor.Language.Extensions; -using Microsoft.AspNetCore.Razor.Language.Intermediate; -using Microsoft.AspNetCore.Razor.Language.Legacy; using Microsoft.AspNetCore.Razor.Language.Syntax; using Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Models; -using Microsoft.AspNetCore.Razor.PooledObjects; using Microsoft.AspNetCore.Razor.Threading; -using Microsoft.AspNetCore.Razor.Utilities; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor.Logging; -using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -74,7 +62,7 @@ public Task> ProvideAsync(RazorCodeAct return SpecializedTasks.EmptyImmutableArray(); } - var actionParams = CreateInitialActionParams(context, startElementNode, @namespace); + var actionParams = CreateInitialActionParams(context, startElementNode, @namespace); ProcessSelection(startElementNode, endElementNode, actionParams); @@ -209,7 +197,7 @@ private static void ProcessSelection(MarkupElementSyntax startElementNode, Marku // |}|} // // In this case, we need to find the smallest set of complete elements that covers the entire selection. - + // Find the closest containing sibling pair that encompasses both the start and end elements var (extractStart, extractEnd) = FindContainingSiblingPair(startElementNode, endElementNode); @@ -324,7 +312,7 @@ private static void AddUsingFromTagHelperInfo(TagHelperInfo tagHelperInfo, HashS // This is a bit inefficient because at most 'k' string operations are performed (k = parts in the namespace), // for each potential using directive. - + var parts = typeNamespace.Split('.'); for (var i = 0; i < parts.Length; i++) { diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CodeActionEndToEndTest.NetFx.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CodeActionEndToEndTest.NetFx.cs index bde051c3a2b..992ff410c0e 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CodeActionEndToEndTest.NetFx.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CodeActionEndToEndTest.NetFx.cs @@ -61,7 +61,6 @@ private GenerateMethodCodeActionResolver[] CreateRazorCodeActionResolvers( razorFormattingService) ]; - private ExtractToComponentCodeActionResolver[] CreateExtractComponentCodeActionResolver(string filePath, RazorCodeDocument codeDocument) { return [