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);