From 9a307d7c52ef6dc1ce66f5f8cd63e12537484c60 Mon Sep 17 00:00:00 2001 From: tmat Date: Fri, 29 Apr 2022 15:22:30 -0700 Subject: [PATCH] Explicitly pass formatting options to Roslyn APIs (via External Access) --- .../Formatting/CSharpFormatter.cs | 11 ++++---- .../Formatting/CSharpOnTypeFormattingPass.cs | 15 +++++------ .../Formatting/FormattingOptionsExtensions.cs | 17 +++++++++++++ .../FormattingLanguageServerClient.cs | 25 ++++++++----------- .../Formatting/FormattingTestBase.cs | 8 ++++-- 5 files changed, 45 insertions(+), 31 deletions(-) create mode 100644 src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/FormattingOptionsExtensions.cs diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/CSharpFormatter.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/CSharpFormatter.cs index 33d7cbe4521..8a6140a8815 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/CSharpFormatter.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/CSharpFormatter.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.ExternalAccess.Razor; using Microsoft.CodeAnalysis.Razor.Workspaces.Extensions; using Microsoft.CodeAnalysis.Text; using OmniSharp.Extensions.LanguageServer.Protocol.Models; @@ -135,15 +136,14 @@ private static async Task FormatOnServerAsync( Range projectedRange, CancellationToken cancellationToken) { + var csharpDocument = context.CSharpWorkspaceDocument; var csharpSourceText = context.CodeDocument.GetCSharpSourceText(); var spanToFormat = projectedRange.AsTextSpan(csharpSourceText); var root = await context.CSharpWorkspaceDocument.GetSyntaxRootAsync(cancellationToken); Assumes.NotNull(root); - var workspace = context.CSharpWorkspace; - - // Formatting options will already be set in the workspace. - var changes = CodeAnalysis.Formatting.Formatter.GetFormattedTextChanges(root, spanToFormat, workspace, cancellationToken: cancellationToken); + var services = csharpDocument.Project.Solution.Workspace.Services; + var changes = RazorCSharpFormattingInteractionService.GetFormattedTextChanges(services, root, spanToFormat, context.Options.GetIndentationOptions(), cancellationToken); var edits = changes.Select(c => c.AsTextEdit(csharpSourceText)).ToArray(); return edits; @@ -165,7 +165,8 @@ private static async Task> GetCSharpIndentationCoreAsync(Fo // At this point, we have added all the necessary markers and attached annotations. // Let's invoke the C# formatter and hope for the best. - var formattedRoot = CodeAnalysis.Formatting.Formatter.Format(root, context.CSharpWorkspace, cancellationToken: cancellationToken); + var services = context.CSharpWorkspaceDocument.Project.Solution.Workspace.Services; + var formattedRoot = RazorCSharpFormattingInteractionService.Format(services, root, context.Options.GetIndentationOptions(), cancellationToken); var formattedText = formattedRoot.GetText(); var desiredIndentationMap = new Dictionary(); diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/CSharpOnTypeFormattingPass.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/CSharpOnTypeFormattingPass.cs index a194a00b571..30e8f4c14b8 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/CSharpOnTypeFormattingPass.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/CSharpOnTypeFormattingPass.cs @@ -27,11 +27,13 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting internal class CSharpOnTypeFormattingPass : CSharpFormattingPassBase { private readonly ILogger _logger; + private readonly RazorGlobalOptions _globalOptions; public CSharpOnTypeFormattingPass( RazorDocumentMappingService documentMappingService, FilePathNormalizer filePathNormalizer, ClientNotifierServiceBase server, + RazorGlobalOptions globalOptions, ILoggerFactory loggerFactory) : base(documentMappingService, filePathNormalizer, server) { @@ -41,6 +43,7 @@ public CSharpOnTypeFormattingPass( } _logger = loggerFactory.CreateLogger(); + _globalOptions = globalOptions; } public async override Task ExecuteAsync(FormattingContext context, FormattingResult result, CancellationToken cancellationToken) @@ -65,19 +68,13 @@ public async override Task ExecuteAsync(FormattingContext cont } // Ask C# for formatting changes. - var indentationOptions = new RazorIndentationOptions( - UseTabs: !context.Options.InsertSpaces, - TabSize: context.Options.TabSize, - IndentationSize: context.Options.TabSize); - var autoFormattingOptions = new RazorAutoFormattingOptions( - formatOnReturn: true, formatOnTyping: true, formatOnSemicolon: true, formatOnCloseBrace: true); - + var formattingChanges = await RazorCSharpFormattingInteractionService.GetFormattingChangesAsync( context.CSharpWorkspaceDocument, typedChar: context.TriggerCharacter, projectedIndex, - indentationOptions, - autoFormattingOptions, + context.Options.GetIndentationOptions(), + _globalOptions.GetAutoFormattingOptions(), indentStyle: CodeAnalysis.Formatting.FormattingOptions.IndentStyle.Smart, cancellationToken).ConfigureAwait(false); diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/FormattingOptionsExtensions.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/FormattingOptionsExtensions.cs new file mode 100644 index 00000000000..4c05cebc667 --- /dev/null +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/FormattingOptionsExtensions.cs @@ -0,0 +1,17 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.ExternalAccess.Razor; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; + +namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting +{ + internal static class FormattingOptionsExtensions + { + public static RazorIndentationOptions GetIndentationOptions(this FormattingOptions options) + => new( + UseTabs: !options.InsertSpaces, + TabSize: options.TabSize, + IndentationSize: options.TabSize); + } +} diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/FormattingLanguageServerClient.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/FormattingLanguageServerClient.cs index 66149ac3404..3a8fba26d17 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/FormattingLanguageServerClient.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/FormattingLanguageServerClient.cs @@ -17,7 +17,7 @@ using Microsoft.AspNetCore.Razor.LanguageServer.Protocol; using Microsoft.AspNetCore.Razor.LanguageServer.Test.Common; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.ExternalAccess.Razor; using Microsoft.CodeAnalysis.Razor.Workspaces.Extensions; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Text; @@ -36,7 +36,6 @@ using OmniSharp.Extensions.LanguageServer.Protocol.Server; using OmniSharp.Extensions.LanguageServer.Protocol.Server.WorkDone; using Xunit; -using FormattingOptions = OmniSharp.Extensions.LanguageServer.Protocol.Models.FormattingOptions; namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting { @@ -71,7 +70,7 @@ public void AddCodeDocument(RazorCodeDocument codeDocument) _documents.TryAdd("/" + path, codeDocument); } - private RazorDocumentFormattingResponse Format(DocumentOnTypeFormattingParams @params) + private RazorDocumentFormattingResponse Format() { var response = new RazorDocumentFormattingResponse(); @@ -150,14 +149,13 @@ private RazorDocumentFormattingResponse Format(RazorDocumentRangeFormattingParam throw new InvalidOperationException("We shouldn't be asked to format Razor language kind."); } - var options = @params.Options; var response = new RazorDocumentFormattingResponse(); if (@params.Kind == RazorLanguageKind.CSharp) { var codeDocument = _documents[@params.HostDocumentFilePath]; var csharpSourceText = codeDocument.GetCSharpSourceText(); - var csharpDocument = GetCSharpDocument(codeDocument, @params.Options); + var csharpDocument = GetCSharpDocument(codeDocument); if (!csharpDocument.TryGetSyntaxRoot(out var root)) { throw new InvalidOperationException("Couldn't get syntax root."); @@ -165,7 +163,10 @@ private RazorDocumentFormattingResponse Format(RazorDocumentRangeFormattingParam var spanToFormat = @params.ProjectedRange.AsTextSpan(csharpSourceText); - var changes = Formatter.GetFormattedTextChanges(root, spanToFormat, csharpDocument.Project.Solution.Workspace); + var services = csharpDocument.Project.Solution.Workspace.Services; + var options = @params.Options.GetIndentationOptions(); + var changes = RazorCSharpFormattingInteractionService.GetFormattedTextChanges( + services, root, spanToFormat, options, CancellationToken.None); response.Edits = changes.Select(c => c.AsTextEdit(csharpSourceText)).ToArray(); } @@ -204,15 +205,9 @@ public TextEdit AsTextEdit(SourceText sourceText) } } - private static Document GetCSharpDocument(RazorCodeDocument codeDocument, FormattingOptions options) + private static Document GetCSharpDocument(RazorCodeDocument codeDocument) { var adhocWorkspace = new AdhocWorkspace(); - var csharpOptions = adhocWorkspace.Options - .WithChangedOption(CodeAnalysis.Formatting.FormattingOptions.TabSize, LanguageNames.CSharp, (int)options.TabSize) - .WithChangedOption(CodeAnalysis.Formatting.FormattingOptions.IndentationSize, LanguageNames.CSharp, (int)options.TabSize) - .WithChangedOption(CodeAnalysis.Formatting.FormattingOptions.UseTabs, LanguageNames.CSharp, !options.InsertSpaces); - adhocWorkspace.TryApplyChanges(adhocWorkspace.CurrentSolution.WithOptions(csharpOptions)); - var project = adhocWorkspace.AddProject("TestProject", LanguageNames.CSharp); var csharpSourceText = codeDocument.GetCSharpSourceText(); var csharpDocument = adhocWorkspace.AddDocument(project.Id, "TestDocument", csharpSourceText); @@ -265,10 +260,10 @@ public override Task SendRequestAsync(string method, return Task.FromResult(Convert(response)); } - else if (@params is DocumentOnTypeFormattingParams onTypeFormattingParams && + else if (@params is DocumentOnTypeFormattingParams && string.Equals(method, "textDocument/onTypeFormatting", StringComparison.Ordinal)) { - var response = Format(onTypeFormattingParams); + var response = Format(); return Task.FromResult(Convert(response)); } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/FormattingTestBase.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/FormattingTestBase.cs index 0df397d1dcf..55a534cf84a 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/FormattingTestBase.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/FormattingTestBase.cs @@ -17,6 +17,7 @@ using Microsoft.AspNetCore.Razor.LanguageServer.Test; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Razor; +using Microsoft.CodeAnalysis.ExternalAccess.Razor; using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Razor.Serialization; using Microsoft.CodeAnalysis.Razor.Workspaces.Extensions; @@ -221,19 +222,22 @@ private RazorFormattingService CreateFormattingService(RazorCodeDocument codeDoc var dispatcher = new LSPProjectSnapshotManagerDispatcher(LoggerFactory); var versionCache = new DefaultDocumentVersionCache(dispatcher); + var workspaceFactory = TestAdhocWorkspaceFactory.Instance; + var globalOptions = RazorGlobalOptions.GetGlobalOptions(workspaceFactory.Create()); + var client = new FormattingLanguageServerClient(); client.AddCodeDocument(codeDocument); var passes = new List() { new HtmlFormattingPass(mappingService, FilePathNormalizer, client, versionCache, LoggerFactory), new CSharpFormattingPass(mappingService, FilePathNormalizer, client, LoggerFactory), - new CSharpOnTypeFormattingPass(mappingService, FilePathNormalizer, client, LoggerFactory), + new CSharpOnTypeFormattingPass(mappingService, FilePathNormalizer, client, globalOptions, LoggerFactory), new RazorFormattingPass(mappingService, FilePathNormalizer, client, LoggerFactory), new FormattingDiagnosticValidationPass(mappingService, FilePathNormalizer, client, LoggerFactory), new FormattingContentValidationPass(mappingService, FilePathNormalizer, client, LoggerFactory), }; - return new DefaultRazorFormattingService(passes, LoggerFactory, TestAdhocWorkspaceFactory.Instance); + return new DefaultRazorFormattingService(passes, LoggerFactory, workspaceFactory); } private static SourceText ApplyEdits(SourceText source, TextEdit[] edits)