diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Protocol/DelegatedTypes.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Protocol/DelegatedTypes.cs index 0907f1c6eee..98919635d25 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Protocol/DelegatedTypes.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Protocol/DelegatedTypes.cs @@ -63,3 +63,8 @@ internal record DelegatedProjectContextsParams( internal record DelegatedDocumentSymbolParams( TextDocumentIdentifierAndVersion Identifier); + +internal record DelegatedSimplifyMethodParams( + TextDocumentIdentifierAndVersion Identifier, + bool RequiresVirtualDocument, + TextEdit TextEdit); diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/CodeBlockService.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/CodeBlockService.cs index d3439a11b95..f1a51361905 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/CodeBlockService.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/CodeBlockService.cs @@ -36,7 +36,7 @@ internal static class CodeBlockService /// /// A that will place the formatted generated method within a @code block in the file. /// - public static TextEdit CreateFormattedTextEdit(RazorCodeDocument code, string templateWithMethodSignature, RazorLSPOptions options) + public static TextEdit[] CreateFormattedTextEdit(RazorCodeDocument code, string templateWithMethodSignature, RazorLSPOptions options) { var csharpCodeBlock = code.GetSyntaxTree().Root.DescendantNodes() .Select(RazorSyntaxFacts.TryGetCSharpCodeFromCodeBlock) @@ -45,27 +45,43 @@ public static TextEdit CreateFormattedTextEdit(RazorCodeDocument code, string te || !csharpCodeBlock.Children.TryGetOpenBraceNode(out var openBrace) || !csharpCodeBlock.Children.TryGetCloseBraceNode(out var closeBrace)) { - // No well-formed @code block exists. Generate the method within an @code block at the end of the file. + // No well-formed @code block exists. Generate the method within an @code block at the end of the file and conduct manual formatting. var indentedMethod = FormattingUtilities.AddIndentationToMethod(templateWithMethodSignature, options, startingIndent: 0); - var textWithCodeBlock = "@code {" + Environment.NewLine + indentedMethod + Environment.NewLine + "}"; + var codeBlockStartText = "@code {" + Environment.NewLine; var lastCharacterLocation = code.Source.Lines.GetLocation(code.Source.Length - 1); var insertCharacterIndex = 0; if (lastCharacterLocation.LineIndex == code.Source.Lines.Count - 1 && !IsLineEmpty(code.Source, lastCharacterLocation)) { // The last line of the file is not empty so we need to place the code at the end of that line with a new line at the beginning. insertCharacterIndex = lastCharacterLocation.CharacterIndex + 1; - textWithCodeBlock = $"{Environment.NewLine}{textWithCodeBlock}"; + codeBlockStartText = $"{Environment.NewLine}{codeBlockStartText}"; } - var eof = new Position(code.Source.Lines.Count - 1, insertCharacterIndex); - return new TextEdit() + var eofPosition = new Position(code.Source.Lines.Count - 1, insertCharacterIndex); + var eofRange = new Range { Start = eofPosition, End = eofPosition }; + var start = new TextEdit() { - Range = new Range { Start = eof, End = eof }, - NewText = textWithCodeBlock + NewText = codeBlockStartText, + Range = eofRange }; + + var method = new TextEdit() + { + NewText = indentedMethod, + Range = eofRange + }; + + var end = new TextEdit() + { + NewText = Environment.NewLine + "}", + Range = eofRange + }; + + return new TextEdit[] { start, method, end }; } // A well-formed @code block exists, generate the method within it. + var openBraceLocation = openBrace.GetSourceLocation(code.Source); var closeBraceLocation = closeBrace.GetSourceLocation(code.Source); var previousLine = code.Source.Lines.GetLocation(closeBraceLocation.AbsoluteIndex - closeBraceLocation.CharacterIndex - 1); @@ -88,11 +104,14 @@ public static TextEdit CreateFormattedTextEdit(RazorCodeDocument code, string te ? closeBraceLocation.CharacterIndex : 0; var insertPosition = new Position(insertLineLocation.LineIndex, insertCharacter); - return new TextEdit() + + var edit = new TextEdit() { Range = new Range { Start = insertPosition, End = insertPosition }, NewText = formattedGeneratedMethod }; + + return new TextEdit[] { edit }; } private static string FormatMethodInCodeBlock( diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/GenerateMethodCodeActionResolver.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/GenerateMethodCodeActionResolver.cs index a211cb99328..deb62af78b1 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/GenerateMethodCodeActionResolver.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/GenerateMethodCodeActionResolver.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; @@ -11,6 +12,7 @@ using Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Models; using Microsoft.AspNetCore.Razor.LanguageServer.Common; using Microsoft.AspNetCore.Razor.LanguageServer.Formatting; +using Microsoft.AspNetCore.Razor.LanguageServer.Protocol; using Microsoft.AspNetCore.Razor.Utilities; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -25,6 +27,9 @@ internal class GenerateMethodCodeActionResolver : IRazorCodeActionResolver { private readonly DocumentContextFactory _documentContextFactory; private readonly RazorLSPOptionsMonitor _razorLSPOptionsMonitor; + private readonly ClientNotifierServiceBase _languageServer; + private readonly IRazorDocumentMappingService _documentMappingService; + private readonly IRazorFormattingService _razorFormattingService; private static readonly string s_beginningIndents = $"{FormattingUtilities.InitialIndent}{FormattingUtilities.Indent}"; private static readonly string s_returnType = "$$ReturnType$$"; @@ -33,15 +38,23 @@ internal class GenerateMethodCodeActionResolver : IRazorCodeActionResolver private static readonly string s_generateMethodTemplate = $"{s_beginningIndents}private {s_returnType} {s_methodName}({s_eventArgs}){Environment.NewLine}" + s_beginningIndents + "{" + Environment.NewLine + - $"{s_beginningIndents}{FormattingUtilities.Indent}throw new System.NotImplementedException();{Environment.NewLine}" + + $"{s_beginningIndents}{FormattingUtilities.Indent}throw new global::System.NotImplementedException();{Environment.NewLine}" + s_beginningIndents + "}"; public string Action => LanguageServerConstants.CodeActions.GenerateEventHandler; - public GenerateMethodCodeActionResolver(DocumentContextFactory documentContextFactory, RazorLSPOptionsMonitor razorLSPOptionsMonitor) + public GenerateMethodCodeActionResolver( + DocumentContextFactory documentContextFactory, + RazorLSPOptionsMonitor razorLSPOptionsMonitor, + ClientNotifierServiceBase languageServer, + IRazorDocumentMappingService razorDocumentMappingService, + IRazorFormattingService razorFormattingService) { _documentContextFactory = documentContextFactory; _razorLSPOptionsMonitor = razorLSPOptionsMonitor; + _languageServer = languageServer; + _documentMappingService = razorDocumentMappingService; + _razorFormattingService = razorFormattingService; } public async Task ResolveAsync(JObject data, CancellationToken cancellationToken) @@ -63,8 +76,6 @@ public GenerateMethodCodeActionResolver(DocumentContextFactory documentContextFa return null; } - var templateWithMethodSignature = PopulateMethodSignature(documentContext, actionParams); - var code = await documentContext.GetCodeDocumentAsync(cancellationToken).ConfigureAwait(false); var uriPath = FilePathNormalizer.Normalize(actionParams.Uri.GetAbsoluteOrUNCPath()); var razorClassName = Path.GetFileNameWithoutExtension(uriPath); @@ -74,27 +85,38 @@ public GenerateMethodCodeActionResolver(DocumentContextFactory documentContextFa || razorClassName is null || !code.TryComputeNamespace(fallbackToRootNamespace: true, out var razorNamespace)) { - return GenerateMethodInCodeBlock(code, actionParams, templateWithMethodSignature); + return await GenerateMethodInCodeBlockAsync( + code, + actionParams, + documentContext, + razorNamespace: null, + razorClassName, + cancellationToken).ConfigureAwait(false); } var content = File.ReadAllText(codeBehindPath); - var mock = CSharpSyntaxFactory.ParseCompilationUnit(content); - var @namespace = mock.Members - .FirstOrDefault(m => m is BaseNamespaceDeclarationSyntax { } @namespace && @namespace.Name.ToString() == razorNamespace); - if (@namespace is null) + if (GetCSharpClassDeclarationSyntax(content, razorNamespace, razorClassName) is not { } @class) { // The code behind file is malformed, generate the code in the razor file instead. - return GenerateMethodInCodeBlock(code, actionParams, templateWithMethodSignature); + return await GenerateMethodInCodeBlockAsync( + code, + actionParams, + documentContext, + razorNamespace, + razorClassName, + cancellationToken).ConfigureAwait(false); } - var @class = ((BaseNamespaceDeclarationSyntax)@namespace).Members - .FirstOrDefault(m => m is ClassDeclarationSyntax { } @class && razorClassName == @class.Identifier.Text); - if (@class is null) + var codeBehindUri = new UriBuilder { - // The code behind file is malformed, generate the code in the razor file instead. - return GenerateMethodInCodeBlock(code, actionParams, templateWithMethodSignature); - } + Scheme = Uri.UriSchemeFile, + Path = codeBehindPath, + Host = string.Empty, + }.Uri; + var codeBehindTextDocumentIdentifier = new OptionalVersionedTextDocumentIdentifier() { Uri = codeBehindUri }; + + var templateWithMethodSignature = PopulateMethodSignature(documentContext, actionParams); var classLocationLineSpan = @class.GetLocation().GetLineSpan(); var formattedMethod = FormattingUtilities.AddIndentationToMethod( templateWithMethodSignature, @@ -103,13 +125,6 @@ public GenerateMethodCodeActionResolver(DocumentContextFactory documentContextFa classLocationLineSpan.StartLinePosition.Character, content); - var codeBehindUri = new UriBuilder - { - Scheme = Uri.UriSchemeFile, - Path = codeBehindPath, - Host = string.Empty, - }.Uri; - var insertPosition = new Position(classLocationLineSpan.EndLinePosition.Line, 0); var edit = new TextEdit() { @@ -117,22 +132,119 @@ public GenerateMethodCodeActionResolver(DocumentContextFactory documentContextFa NewText = $"{formattedMethod}{Environment.NewLine}" }; + var delegatedParams = new DelegatedSimplifyMethodParams( + new TextDocumentIdentifierAndVersion(new TextDocumentIdentifier() { Uri = codeBehindUri}, 1), + RequiresVirtualDocument: false, + edit); + + var result = await _languageServer.SendRequestAsync( + CustomMessageNames.RazorSimplifyMethodEndpointName, + delegatedParams, + cancellationToken).ConfigureAwait(false) + ?? new TextEdit[] { edit }; + var codeBehindTextDocEdit = new TextDocumentEdit() { - TextDocument = new OptionalVersionedTextDocumentIdentifier() { Uri = codeBehindUri }, - Edits = new TextEdit[] { edit } + TextDocument = codeBehindTextDocumentIdentifier, + Edits = result }; return new WorkspaceEdit() { DocumentChanges = new[] { codeBehindTextDocEdit } }; } - private WorkspaceEdit GenerateMethodInCodeBlock(RazorCodeDocument code, GenerateMethodCodeActionParams actionParams, string templateWithMethodSignature) + private async Task GenerateMethodInCodeBlockAsync( + RazorCodeDocument code, + GenerateMethodCodeActionParams actionParams, + VersionedDocumentContext documentContext, + string? razorNamespace, + string? razorClassName, + CancellationToken cancellationToken) { - var edit = CodeBlockService.CreateFormattedTextEdit(code, templateWithMethodSignature, _razorLSPOptionsMonitor.CurrentValue); + var templateWithMethodSignature = PopulateMethodSignature(documentContext, actionParams); + var edits = CodeBlockService.CreateFormattedTextEdit(code, templateWithMethodSignature, _razorLSPOptionsMonitor.CurrentValue); + + // If there are 3 edits, this means that there is no existing @code block, so we have an edit for '@code {', the method stub, and '}'. + // Otherwise, a singular edit means that an @code block does exist and the only edit is adding the method stub. + var editToSendToRoslyn = edits.Length == 3 ? edits[1] : edits[0]; + if (edits.Length == 3 + && razorClassName is not null + && (razorNamespace is not null || code.TryComputeNamespace(fallbackToRootNamespace: true, out razorNamespace)) + && GetCSharpClassDeclarationSyntax(code.GetCSharpDocument().GeneratedCode, razorNamespace, razorClassName) is { } @class) + { + // There is no existing @code block. This means that there is no code block source mapping in the generated C# document + // to place the code, so we cannot utilize the document mapping service and the formatting service. + // We are going to arbitrarily place the method at the end of the class in the generated C# file to + // just get the simplified text that comes back from Roslyn. + + var classLocationLineSpan = @class.GetLocation().GetLineSpan(); + var insertPosition = new Position(classLocationLineSpan.EndLinePosition.Line, 0); + var tempTextEdit = new TextEdit() + { + NewText = editToSendToRoslyn.NewText, + Range = new Range() { Start = insertPosition, End = insertPosition } + }; + + var delegatedParams = new DelegatedSimplifyMethodParams(documentContext.Identifier, RequiresVirtualDocument: true, tempTextEdit); + var result = await _languageServer.SendRequestAsync( + CustomMessageNames.RazorSimplifyMethodEndpointName, + delegatedParams, + cancellationToken).ConfigureAwait(false); + + // Roslyn should have passed back 2 edits. One that contains the simplified method stub and the other that contains the new + // location for the class end brace since we had asked to insert the method stub at the original class end brace location. + // We will only use the edit that contains the method stub. + Debug.Assert(result is null || result.Length == 2, $"Unexpected response to {CustomMessageNames.RazorSimplifyMethodEndpointName} from Roslyn"); + var simplificationEdit = result?.FirstOrDefault(edit => edit.NewText.Contains("private")); + if (simplificationEdit is not null) + { + // Roslyn will have removed the beginning formatting, put it back. + var formatting = editToSendToRoslyn.NewText[0..editToSendToRoslyn.NewText.IndexOf("private")]; + editToSendToRoslyn.NewText = $"{formatting}{simplificationEdit.NewText.TrimEnd()}"; + } + } + else if (_documentMappingService.TryMapToGeneratedDocumentRange(code.GetCSharpDocument(), editToSendToRoslyn.Range, out var remappedRange)) + { + // If the call to Roslyn is successful, the razor formatting service will format incorrectly if our manual formatting is present, + // strip our manual formatting from the method so we just have a valid method signature. + var unformattedMethodSignature = templateWithMethodSignature + .Replace(FormattingUtilities.InitialIndent, string.Empty) + .Replace(FormattingUtilities.Indent, string.Empty); + + var remappedEdit = new TextEdit() + { + NewText = unformattedMethodSignature, + Range = remappedRange + }; + + var delegatedParams = new DelegatedSimplifyMethodParams(documentContext.Identifier, RequiresVirtualDocument: true, remappedEdit); + var result = await _languageServer.SendRequestAsync( + CustomMessageNames.RazorSimplifyMethodEndpointName, + delegatedParams, + cancellationToken).ConfigureAwait(false); + + if (result is not null) + { + var formattingOptions = new FormattingOptions() + { + TabSize = _razorLSPOptionsMonitor.CurrentValue.TabSize, + InsertSpaces = _razorLSPOptionsMonitor.CurrentValue.InsertSpaces, + }; + + var formattedEdits = await _razorFormattingService.FormatCodeActionAsync( + documentContext, + RazorLanguageKind.CSharp, + result, + formattingOptions, + CancellationToken.None).ConfigureAwait(false); + + edits = formattedEdits; + } + } + var razorTextDocEdit = new TextDocumentEdit() { TextDocument = new OptionalVersionedTextDocumentIdentifier() { Uri = actionParams.Uri }, - Edits = new TextEdit[] { edit }, + Edits = edits, }; return new WorkspaceEdit() { DocumentChanges = new[] { razorTextDocEdit } }; @@ -142,15 +254,35 @@ private static string PopulateMethodSignature(VersionedDocumentContext documentC { var templateWithMethodSignature = s_generateMethodTemplate.Replace(s_methodName, actionParams.MethodName); - var returnType = actionParams.IsAsync ? "System.Threading.Tasks.Task" : "void"; + var returnType = actionParams.IsAsync ? "global::System.Threading.Tasks.Task" : "void"; templateWithMethodSignature = templateWithMethodSignature.Replace(s_returnType, returnType); var eventTagHelper = documentContext.Project.TagHelpers .FirstOrDefault(th => th.Name == actionParams.EventName && th.IsEventHandlerTagHelper() && th.GetEventArgsType() is not null); var eventArgsType = eventTagHelper is null ? string.Empty // Couldn't find the params, generate no params instead. - : $"{eventTagHelper.GetEventArgsType()} e"; + : $"global::{eventTagHelper.GetEventArgsType()} e"; return templateWithMethodSignature.Replace(s_eventArgs, eventArgsType); } + + private static ClassDeclarationSyntax? GetCSharpClassDeclarationSyntax(string csharpContent, string razorNamespace, string razorClassName) + { + var mock = CSharpSyntaxFactory.ParseCompilationUnit(csharpContent); + var @namespace = mock.Members + .FirstOrDefault(m => m is BaseNamespaceDeclarationSyntax { } @namespace && @namespace.Name.ToString() == razorNamespace); + if (@namespace is null) + { + return null; + } + + var @class = ((BaseNamespaceDeclarationSyntax)@namespace).Members + .FirstOrDefault(m => m is ClassDeclarationSyntax { } @class && razorClassName == @class.Identifier.Text); + if (@class is null) + { + return null; + } + + return (ClassDeclarationSyntax)@class; + } } diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/SimplifyMethodParams.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/SimplifyMethodParams.cs new file mode 100644 index 00000000000..82597cd3fdd --- /dev/null +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/SimplifyMethodParams.cs @@ -0,0 +1,27 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Runtime.Serialization; +using Microsoft.VisualStudio.LanguageServer.Protocol; + +namespace Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Razor; + +// +// Summary: +// Class representing the parameters sent from the client to the server for the +// roslyn/simplifyMethod request. +[DataContract] +internal record SimplifyMethodParams : ITextDocumentParams +{ + // + // Summary: + // Gets or sets the value which identifies the document in which the edit will be applied. + [DataMember(Name = "textDocument")] + public required TextDocumentIdentifier TextDocument { get; set; } + + // + // Summary: + // Gets or sets the value which is the text edit to be simplified. + [DataMember(Name = "textEdit")] + public required TextEdit TextEdit { get; set; } +} diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Common/CustomMessageNames.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Common/CustomMessageNames.cs index bc1547fbcd6..0dd3487048c 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Common/CustomMessageNames.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Common/CustomMessageNames.cs @@ -51,4 +51,6 @@ internal static class CustomMessageNames public const string RazorImplementationEndpointName = "razor/implementation"; public const string RazorReferencesEndpointName = "razor/references"; + + public const string RazorSimplifyMethodEndpointName = "razor/simplifyMethod"; } diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/Endpoints/SimplifyMethod.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/Endpoints/SimplifyMethod.cs new file mode 100644 index 00000000000..8e3d9acb5d0 --- /dev/null +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/Endpoints/SimplifyMethod.cs @@ -0,0 +1,60 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Razor; +using Microsoft.AspNetCore.Razor.LanguageServer.Common; +using Microsoft.AspNetCore.Razor.LanguageServer.Protocol; +using Microsoft.VisualStudio.LanguageServer.Protocol; +using Microsoft.VisualStudio.LanguageServerClient.Razor.Extensions; +using Newtonsoft.Json.Linq; +using StreamJsonRpc; + +namespace Microsoft.VisualStudio.LanguageServerClient.Razor; + +internal partial class RazorCustomMessageTarget +{ + [JsonRpcMethod(CustomMessageNames.RazorSimplifyMethodEndpointName, UseSingleObjectParameterDeserialization = true)] + public async Task SimplifyTypeAsync(DelegatedSimplifyMethodParams request, CancellationToken cancellationToken) + { + var identifier = request.Identifier.TextDocumentIdentifier; + if (request.RequiresVirtualDocument) + { + var (synchronized, virtualDocument) = await _documentSynchronizer.TrySynchronizeVirtualDocumentAsync( + request.Identifier.Version, + request.Identifier.TextDocumentIdentifier.Uri, + cancellationToken).ConfigureAwait(false); + if (!synchronized) + { + return null; + } + + identifier = identifier.WithUri(virtualDocument.Uri); + } + + var simplifyTypeNamesParams = new SimplifyMethodParams() + { + TextDocument = identifier, + TextEdit = request.TextEdit + }; + + var response = await _requestInvoker.ReinvokeRequestOnServerAsync( + RazorLSPConstants.RoslynSimplifyMethodEndpointName, + RazorLSPConstants.RazorCSharpLanguageServerName, + SupportsSimplifyMethod, + simplifyTypeNamesParams, + cancellationToken).ConfigureAwait(false); + + return response.Result; + } + + private static bool SupportsSimplifyMethod(JToken token) + { + var serverCapabilities = token.ToObject(); + + return serverCapabilities?.Experimental is JObject experimental + && experimental.TryGetValue(RazorLSPConstants.RoslynSimplifyMethodEndpointName, out var supportsSimplifyMethod) + && supportsSimplifyMethod.ToObject(); + } +} diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/RazorLSPConstants.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/RazorLSPConstants.cs index f1ce8738b3f..f3a7e3d9163 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/RazorLSPConstants.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/RazorLSPConstants.cs @@ -20,4 +20,6 @@ internal static class RazorLSPConstants public const string CSharpContentTypeName = "CSharp"; public const string HtmlLSPDelegationContentTypeName = "html-delegation"; + + public const string RoslynSimplifyMethodEndpointName = "roslyn/simplifyMethod"; } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CodeActionEndToEndTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CodeActionEndToEndTest.cs index 9f03e8bcf7b..b811faf54a2 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CodeActionEndToEndTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CodeActionEndToEndTest.cs @@ -37,18 +37,22 @@ public class CodeActionEndToEndTest : SingleServerDelegatingEndpointTestBase private const string GenerateEventHandlerTitle = "Generate Event Handler 'DoesNotExist'"; private const string GenerateAsyncEventHandlerTitle = "Generate Async Event Handler 'DoesNotExist'"; private const string GenerateEventHandlerReturnType = "void"; - private const string GenerateAsyncEventHandlerReturnType = "System.Threading.Tasks.Task"; + private const string GenerateAsyncEventHandlerReturnType = "global::System.Threading.Tasks.Task"; private const string CodeBehindTestReplaceNamespace = "$$Replace_Namespace$$"; - private static GenerateMethodCodeActionResolver[] CreateRazorCodeActionResolversFn( + private GenerateMethodCodeActionResolver[] CreateRazorCodeActionResolversFn( string filePath, RazorCodeDocument codeDocument, + IRazorFormattingService razorFormattingService, RazorLSPOptionsMonitor? optionsMonitor = null) => new[] { new GenerateMethodCodeActionResolver( new GenerateMethodResolverDocumentContextFactory(filePath, codeDocument), - optionsMonitor ?? TestRazorLSPOptionsMonitor.Create()) + optionsMonitor ?? TestRazorLSPOptionsMonitor.Create(), + LanguageServer, + new RazorDocumentMappingService(TestLanguageServerFeatureOptions.Instance, new TestDocumentContextFactory(), LoggerFactory), + razorFormattingService) }; public CodeActionEndToEndTest(ITestOutputHelper testOutput) @@ -375,9 +379,9 @@ public async Task Handle_GenerateMethod_NoCodeBlock(string cursorAndMethodName) var expected = $$""" @code { - private {{GenerateEventHandlerReturnType}} DoesNotExist(Microsoft.AspNetCore.Components.Web.MouseEventArgs e) + private {{GenerateEventHandlerReturnType}} DoesNotExist(global::Microsoft.AspNetCore.Components.Web.MouseEventArgs e) { - throw new System.NotImplementedException(); + throw new global::System.NotImplementedException(); } } """; @@ -404,9 +408,9 @@ public async Task Handle_GenerateAsyncMethod_NoCodeBlock(string cursorAndMethodN var expected = $$""" @code { - private {{GenerateAsyncEventHandlerReturnType}} DoesNotExist(Microsoft.AspNetCore.Components.Web.MouseEventArgs e) + private {{GenerateAsyncEventHandlerReturnType}} DoesNotExist(global::Microsoft.AspNetCore.Components.Web.MouseEventArgs e) { - throw new System.NotImplementedException(); + throw new global::System.NotImplementedException(); } } """; @@ -434,9 +438,9 @@ public async Task Handle_GenerateMethod_Empty_CodeBlock(string codeBlock) var expected = $$""" @code { - private {{GenerateEventHandlerReturnType}} DoesNotExist(Microsoft.AspNetCore.Components.Web.MouseEventArgs e) + private {{GenerateEventHandlerReturnType}} DoesNotExist(global::Microsoft.AspNetCore.Components.Web.MouseEventArgs e) { - throw new System.NotImplementedException(); + throw new global::System.NotImplementedException(); } } """; @@ -464,9 +468,9 @@ public async Task Handle_GenerateAsyncMethod_Empty_CodeBlock(string codeBlock) var expected = $$""" @code { - private {{GenerateAsyncEventHandlerReturnType}} DoesNotExist(Microsoft.AspNetCore.Components.Web.MouseEventArgs e) + private {{GenerateAsyncEventHandlerReturnType}} DoesNotExist(global::Microsoft.AspNetCore.Components.Web.MouseEventArgs e) { - throw new System.NotImplementedException(); + throw new global::System.NotImplementedException(); } } """; @@ -503,9 +507,9 @@ public void Exists() { } - private {{returnType}} DoesNotExist(Microsoft.AspNetCore.Components.Web.MouseEventArgs e) + private {{returnType}} DoesNotExist(global::Microsoft.AspNetCore.Components.Web.MouseEventArgs e) { - throw new System.NotImplementedException(); + throw new global::System.NotImplementedException(); } } """; @@ -540,9 +544,9 @@ public void Exists() { } - private {{GenerateAsyncEventHandlerReturnType}} DoesNotExist(Microsoft.AspNetCore.Components.Web.MouseEventArgs e) + private {{GenerateAsyncEventHandlerReturnType}} DoesNotExist(global::Microsoft.AspNetCore.Components.Web.MouseEventArgs e) { - throw new System.NotImplementedException(); + throw new global::System.NotImplementedException(); } } """; @@ -650,9 +654,9 @@ public async Task Handle_GenerateMethod_VaryIndentSize(bool insertSpaces, int ta var expected = $$""" {{inputIndentString}}@code { - {{initialIndentString}}{{indent}}private void DoesNotExist(Microsoft.AspNetCore.Components.Web.MouseEventArgs e) + {{initialIndentString}}{{indent}}private void DoesNotExist(global::Microsoft.AspNetCore.Components.Web.MouseEventArgs e) {{initialIndentString}}{{indent}}{ - {{initialIndentString}}{{indent}}{{indent}}throw new System.NotImplementedException(); + {{initialIndentString}}{{indent}}{{indent}}throw new global::System.NotImplementedException(); {{initialIndentString}}{{indent}}} {{inputIndentString}}} """; @@ -698,9 +702,9 @@ namespace {{CodeBehindTestReplaceNamespace}} { public partial class test {{{spacingOrMethod}} - private {{GenerateEventHandlerReturnType}} DoesNotExist(Microsoft.AspNetCore.Components.Web.MouseEventArgs e) + private {{GenerateEventHandlerReturnType}} DoesNotExist(global::Microsoft.AspNetCore.Components.Web.MouseEventArgs e) { - throw new System.NotImplementedException(); + throw new global::System.NotImplementedException(); } } } @@ -742,9 +746,9 @@ namespace {{CodeBehindTestReplaceNamespace}} { public partial class test {{{spacingOrMethod}} - private {{GenerateAsyncEventHandlerReturnType}} DoesNotExist(Microsoft.AspNetCore.Components.Web.MouseEventArgs e) + private {{GenerateAsyncEventHandlerReturnType}} DoesNotExist(global::Microsoft.AspNetCore.Components.Web.MouseEventArgs e) { - throw new System.NotImplementedException(); + throw new global::System.NotImplementedException(); } } } @@ -770,9 +774,9 @@ public async Task Handle_GenerateMethod_CodeBehindFile_Malformed(string initialC var expectedRazorContent = """ @code { - private void DoesNotExist(Microsoft.AspNetCore.Components.Web.MouseEventArgs e) + private void DoesNotExist(global::Microsoft.AspNetCore.Components.Web.MouseEventArgs e) { - throw new System.NotImplementedException(); + throw new global::System.NotImplementedException(); } } """; @@ -807,9 +811,9 @@ public partial class test namespace {{CodeBehindTestReplaceNamespace}}; public partial class test { - private void DoesNotExist(Microsoft.AspNetCore.Components.Web.MouseEventArgs e) + private void DoesNotExist(global::Microsoft.AspNetCore.Components.Web.MouseEventArgs e) { - throw new System.NotImplementedException(); + throw new global::System.NotImplementedException(); } } """; @@ -854,7 +858,8 @@ private async Task ValidateCodeBehindFileAsync( File.WriteAllText(codeBehindFilePath, initialCodeBehindContent); var result = await GetCodeActionsAsync(uri, textSpan, razorSourceText, requestContext, razorCodeActionProviders: new[] { new GenerateMethodCodeActionProvider() }, diagnostics); - var changes = await GetEditsAsync(result, requestContext, codeAction, CreateRazorCodeActionResolversFn(razorFilePath, codeDocument)); + var formattingService = await TestRazorFormattingService.CreateWithFullSupportAsync(); + var changes = await GetEditsAsync(result, requestContext, codeAction, CreateRazorCodeActionResolversFn(razorFilePath, codeDocument, formattingService)); var razorEdits = new List(); var codeBehindEdits = new List(); @@ -889,7 +894,7 @@ private async Task ValidateCodeActionAsync( string codeAction, int childActionIndex = 0, IRazorCodeActionProvider[]? razorCodeActionProviders = null, - Func? createRazorCodeActionResolversFn = null, + Func? createRazorCodeActionResolversFn = null, RazorLSPOptionsMonitor? optionsMonitor = null, Diagnostic[]? diagnostics = null) { @@ -905,10 +910,10 @@ private async Task ValidateCodeActionAsync( var result = await GetCodeActionsAsync(uri, textSpan, sourceText, requestContext, razorCodeActionProviders, diagnostics); Assert.NotEmpty(result); - + var formattingService = await TestRazorFormattingService.CreateWithFullSupportAsync(codeDocument, documentContext.Snapshot, LoggerFactory, optionsMonitor?.CurrentValue); var razorCodeActionResolvers = createRazorCodeActionResolversFn is null ? Array.Empty() - : createRazorCodeActionResolversFn(razorFilePath, codeDocument, optionsMonitor); + : createRazorCodeActionResolversFn(razorFilePath, codeDocument, formattingService, optionsMonitor); var changes = await GetEditsAsync(result, requestContext, codeAction, razorCodeActionResolvers, childActionIndex); var edits = new List(); diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/SingleServerDelegatingEndpointTestBase.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/SingleServerDelegatingEndpointTestBase.cs index 13e03c05427..f9c9b212443 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/SingleServerDelegatingEndpointTestBase.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/SingleServerDelegatingEndpointTestBase.cs @@ -13,7 +13,6 @@ using Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Models; using Microsoft.AspNetCore.Razor.LanguageServer.Common; using Microsoft.AspNetCore.Razor.LanguageServer.Diagnostics; -using Microsoft.AspNetCore.Razor.LanguageServer.EndpointContracts; using Microsoft.AspNetCore.Razor.LanguageServer.Extensions; using Microsoft.AspNetCore.Razor.LanguageServer.Folding; using Microsoft.AspNetCore.Razor.LanguageServer.Protocol; @@ -138,12 +137,19 @@ public async override Task SendRequestAsync(strin CustomMessageNames.RazorSpellCheckEndpoint => await HandleSpellCheckAsync(@params), CustomMessageNames.RazorDocumentSymbolEndpoint => await HandleDocumentSymbolAsync(@params), CustomMessageNames.RazorProjectContextsEndpoint => await HandleProjectContextsAsync(@params), + CustomMessageNames.RazorSimplifyMethodEndpointName => HandleSimplifyMethod(@params), _ => throw new NotImplementedException($"I don't know how to handle the '{method}' method.") }; return (TResponse)result; } + private static TextEdit[] HandleSimplifyMethod(TParams @params) + { + Assert.IsType(@params); + return null; + } + private async Task HandleProjectContextsAsync(TParams @params) { Assert.IsType(@params);