Skip to content

Commit 966d762

Browse files
authored
Fix FormattingContext disposal (#10887)
From a conversation on Teams. `FormattingContext` is disposable because it owns a workspace, but it also gets non-destructively mutated so it's unclear as to whether the disposal was working as intended, and unclear to consumers what needed to happen. Upon review of the code, this PR: * Moves workspace ownership to the caller of the formatting code, so its disposal is clear * Makes `AdhocWorkspaceFactory` shared code, because it did the same thing in OOP and LSP server * Adds `IHostServicesProvider` to OOP, because thats the thing that was actually different between OOP and LSP server * Random cleanup of some related things
2 parents a068170 + 103645f commit 966d762

21 files changed

+131
-248
lines changed

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/AdhocWorkspaceFactory.cs

Lines changed: 0 additions & 26 deletions
This file was deleted.

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultHostServicesProvider.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using Microsoft.AspNetCore.Razor.LanguageServer.Hosting;
66
using Microsoft.CodeAnalysis;
77
using Microsoft.CodeAnalysis.Host;
8+
using Microsoft.CodeAnalysis.Razor.Workspaces;
89

910
namespace Microsoft.AspNetCore.Razor.LanguageServer;
1011

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/InlineCompletion/InlineCompletionEndPoint.cs

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ internal sealed class InlineCompletionEndpoint(
2929
IDocumentMappingService documentMappingService,
3030
IClientConnection clientConnection,
3131
IFormattingCodeDocumentProvider formattingCodeDocumentProvider,
32-
IAdhocWorkspaceFactory adhocWorkspaceFactory,
3332
RazorLSPOptionsMonitor optionsMonitor,
3433
ILoggerFactory loggerFactory)
3534
: IRazorRequestHandler<VSInternalInlineCompletionRequest, VSInternalInlineCompletionList?>, ICapabilitiesProvider
@@ -39,10 +38,9 @@ internal sealed class InlineCompletionEndpoint(
3938
"if", "indexer", "interface", "invoke", "iterator", "iterindex", "lock", "mbox", "namespace", "#if", "#region", "prop",
4039
"propfull", "propg", "sim", "struct", "svm", "switch", "try", "tryf", "unchecked", "unsafe", "using", "while");
4140

42-
private readonly IDocumentMappingService _documentMappingService = documentMappingService ?? throw new ArgumentNullException(nameof(documentMappingService));
43-
private readonly IClientConnection _clientConnection = clientConnection ?? throw new ArgumentNullException(nameof(clientConnection));
41+
private readonly IDocumentMappingService _documentMappingService = documentMappingService;
42+
private readonly IClientConnection _clientConnection = clientConnection;
4443
private readonly IFormattingCodeDocumentProvider _formattingCodeDocumentProvider = formattingCodeDocumentProvider;
45-
private readonly IAdhocWorkspaceFactory _adhocWorkspaceFactory = adhocWorkspaceFactory ?? throw new ArgumentNullException(nameof(adhocWorkspaceFactory));
4644
private readonly RazorLSPOptionsMonitor _optionsMonitor = optionsMonitor;
4745
private readonly ILogger _logger = loggerFactory.GetOrCreateLogger<InlineCompletionEndpoint>();
4846

@@ -63,10 +61,7 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(VSInternalInlineCompleti
6361

6462
public async Task<VSInternalInlineCompletionList?> HandleRequestAsync(VSInternalInlineCompletionRequest request, RazorRequestContext requestContext, CancellationToken cancellationToken)
6563
{
66-
if (request is null)
67-
{
68-
throw new ArgumentNullException(nameof(request));
69-
}
64+
ArgHelper.ThrowIfNull(request);
7065

7166
_logger.LogInformation($"Starting request for {request.TextDocument.Uri} at {request.Position}.");
7267

@@ -118,7 +113,6 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(VSInternalInlineCompleti
118113
using var items = new PooledArrayBuilder<VSInternalInlineCompletionItem>(list.Items.Length);
119114
foreach (var item in list.Items)
120115
{
121-
var containsSnippet = item.TextFormat == InsertTextFormat.Snippet;
122116
var range = item.Range ?? projectedPosition.ToZeroWidthRange();
123117

124118
if (!_documentMappingService.TryMapToHostDocumentRange(codeDocument.GetCSharpDocument(), range, out var rangeInRazorDoc))
@@ -128,13 +122,11 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(VSInternalInlineCompleti
128122
}
129123

130124
var options = RazorFormattingOptions.From(request.Options, _optionsMonitor.CurrentValue.CodeBlockBraceOnNextLine);
131-
using var formattingContext = FormattingContext.Create(
132-
request.TextDocument.Uri,
125+
var formattingContext = FormattingContext.Create(
133126
documentContext.Snapshot,
134127
codeDocument,
135128
options,
136-
_formattingCodeDocumentProvider,
137-
_adhocWorkspaceFactory);
129+
_formattingCodeDocumentProvider);
138130
if (!TryGetSnippetWithAdjustedIndentation(formattingContext, item.Text, hostDocumentIndex, out var newSnippetText))
139131
{
140132
continue;

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/LspWorkspaceProvider.cs

Lines changed: 0 additions & 37 deletions
This file was deleted.

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,6 @@ protected override ILspServices ConstructLspServices()
119119
// Add the logger as a service in case anything in CLaSP pulls it out to do logging
120120
services.AddSingleton<ILspLogger>(Logger);
121121

122-
services.AddSingleton<IAdhocWorkspaceFactory, AdhocWorkspaceFactory>();
123-
services.AddSingleton<IWorkspaceProvider, LspWorkspaceProvider>();
124-
125122
services.AddSingleton<IFormattingCodeDocumentProvider, LspFormattingCodeDocumentProvider>();
126123

127124
var featureOptions = _featureOptions ?? new DefaultLanguageServerFeatureOptions();

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/CSharpFormatter.cs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Microsoft.CodeAnalysis.CSharp;
1313
using Microsoft.CodeAnalysis.CSharp.Syntax;
1414
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
15+
using Microsoft.CodeAnalysis.Host;
1516
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
1617
using Microsoft.CodeAnalysis.Text;
1718
using Microsoft.VisualStudio.LanguageServer.Protocol;
@@ -24,28 +25,29 @@ internal sealed class CSharpFormatter(IDocumentMappingService documentMappingSer
2425

2526
private readonly IDocumentMappingService _documentMappingService = documentMappingService;
2627

27-
public async Task<ImmutableArray<TextChange>> FormatAsync(FormattingContext context, LinePositionSpan spanToFormat, CancellationToken cancellationToken)
28+
public async Task<ImmutableArray<TextChange>> FormatAsync(HostWorkspaceServices hostWorkspaceServices, Document csharpDocument, FormattingContext context, LinePositionSpan spanToFormat, CancellationToken cancellationToken)
2829
{
2930
if (!_documentMappingService.TryMapToGeneratedDocumentRange(context.CodeDocument.GetCSharpDocument(), spanToFormat, out var projectedSpan))
3031
{
3132
return [];
3233
}
3334

34-
var edits = await GetFormattingEditsAsync(context, projectedSpan, cancellationToken).ConfigureAwait(false);
35+
var edits = await GetFormattingEditsAsync(hostWorkspaceServices, csharpDocument, projectedSpan, context.Options.ToIndentationOptions(), cancellationToken).ConfigureAwait(false);
3536
var mappedEdits = MapEditsToHostDocument(context.CodeDocument, edits);
3637
return mappedEdits;
3738
}
3839

3940
public static async Task<IReadOnlyDictionary<int, int>> GetCSharpIndentationAsync(
4041
FormattingContext context,
4142
HashSet<int> projectedDocumentLocations,
43+
HostWorkspaceServices hostWorkspaceServices,
4244
CancellationToken cancellationToken)
4345
{
4446
// Sorting ensures we count the marker offsets correctly.
4547
// We also want to ensure there are no duplicates to avoid duplicate markers.
4648
var filteredLocations = projectedDocumentLocations.OrderAsArray();
4749

48-
var indentations = await GetCSharpIndentationCoreAsync(context, filteredLocations, cancellationToken).ConfigureAwait(false);
50+
var indentations = await GetCSharpIndentationCoreAsync(context, filteredLocations, hostWorkspaceServices, cancellationToken).ConfigureAwait(false);
4951
return indentations;
5052
}
5153

@@ -56,18 +58,18 @@ private ImmutableArray<TextChange> MapEditsToHostDocument(RazorCodeDocument code
5658
return actualEdits.ToImmutableArray();
5759
}
5860

59-
private static async Task<ImmutableArray<TextChange>> GetFormattingEditsAsync(FormattingContext context, LinePositionSpan projectedSpan, CancellationToken cancellationToken)
61+
private static async Task<ImmutableArray<TextChange>> GetFormattingEditsAsync(HostWorkspaceServices hostWorkspaceServices, Document csharpDocument, LinePositionSpan projectedSpan, RazorIndentationOptions indentationOptions, CancellationToken cancellationToken)
6062
{
61-
var csharpSourceText = context.CodeDocument.GetCSharpSourceText();
63+
var root = await csharpDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
64+
var csharpSourceText = await csharpDocument.GetTextAsync(cancellationToken).ConfigureAwait(false);
6265
var spanToFormat = csharpSourceText.GetTextSpan(projectedSpan);
63-
var root = await context.CSharpWorkspaceDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
6466
Assumes.NotNull(root);
6567

66-
var changes = RazorCSharpFormattingInteractionService.GetFormattedTextChanges(context.CSharpWorkspace.Services, root, spanToFormat, context.Options.ToIndentationOptions(), cancellationToken);
68+
var changes = RazorCSharpFormattingInteractionService.GetFormattedTextChanges(hostWorkspaceServices, root, spanToFormat, indentationOptions, cancellationToken);
6769
return changes.ToImmutableArray();
6870
}
6971

70-
private static async Task<Dictionary<int, int>> GetCSharpIndentationCoreAsync(FormattingContext context, ImmutableArray<int> projectedDocumentLocations, CancellationToken cancellationToken)
72+
private static async Task<Dictionary<int, int>> GetCSharpIndentationCoreAsync(FormattingContext context, ImmutableArray<int> projectedDocumentLocations, HostWorkspaceServices hostWorkspaceServices, CancellationToken cancellationToken)
7173
{
7274
// No point calling the C# formatting if we won't be interested in any of its work anyway
7375
if (projectedDocumentLocations.Length == 0)
@@ -83,7 +85,7 @@ private static async Task<Dictionary<int, int>> GetCSharpIndentationCoreAsync(Fo
8385

8486
// At this point, we have added all the necessary markers and attached annotations.
8587
// Let's invoke the C# formatter and hope for the best.
86-
var formattedRoot = RazorCSharpFormattingInteractionService.Format(context.CSharpWorkspace.Services, root, context.Options.ToIndentationOptions(), cancellationToken);
88+
var formattedRoot = RazorCSharpFormattingInteractionService.Format(hostWorkspaceServices, root, context.Options.ToIndentationOptions(), cancellationToken);
8789
var formattedText = formattedRoot.GetText();
8890

8991
var desiredIndentationMap = new Dictionary<int, int>();

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingContext.cs

Lines changed: 2 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,20 @@
1212
using Microsoft.AspNetCore.Razor.Language.Syntax;
1313
using Microsoft.CodeAnalysis;
1414
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
15-
using Microsoft.CodeAnalysis.Razor.Workspaces;
1615
using Microsoft.CodeAnalysis.Text;
1716
using Microsoft.VisualStudio.LanguageServer.Protocol;
1817

1918
namespace Microsoft.CodeAnalysis.Razor.Formatting;
2019

21-
internal sealed class FormattingContext : IDisposable
20+
internal sealed class FormattingContext
2221
{
23-
private readonly IAdhocWorkspaceFactory _workspaceFactory;
2422
private readonly IFormattingCodeDocumentProvider _codeDocumentProvider;
2523

26-
private Document? _csharpWorkspaceDocument;
27-
private AdhocWorkspace? _csharpWorkspace;
28-
2924
private IReadOnlyList<FormattingSpan>? _formattingSpans;
3025
private IReadOnlyDictionary<int, IndentationContext>? _indentations;
3126

3227
private FormattingContext(
3328
IFormattingCodeDocumentProvider codeDocumentProvider,
34-
IAdhocWorkspaceFactory workspaceFactory,
35-
Uri uri,
3629
IDocumentSnapshot originalSnapshot,
3730
RazorCodeDocument codeDocument,
3831
RazorFormattingOptions options,
@@ -41,8 +34,6 @@ private FormattingContext(
4134
char triggerCharacter)
4235
{
4336
_codeDocumentProvider = codeDocumentProvider;
44-
_workspaceFactory = workspaceFactory;
45-
Uri = uri;
4637
OriginalSnapshot = originalSnapshot;
4738
CodeDocument = codeDocument;
4839
Options = options;
@@ -53,7 +44,6 @@ private FormattingContext(
5344

5445
public static bool SkipValidateComponents { get; set; }
5546

56-
public Uri Uri { get; }
5747
public IDocumentSnapshot OriginalSnapshot { get; }
5848
public RazorCodeDocument CodeDocument { get; }
5949
public RazorFormattingOptions Options { get; }
@@ -67,24 +57,6 @@ private FormattingContext(
6757

6858
public string NewLineString => Environment.NewLine;
6959

70-
public Document CSharpWorkspaceDocument
71-
{
72-
get
73-
{
74-
if (_csharpWorkspaceDocument is null)
75-
{
76-
var workspace = CSharpWorkspace;
77-
var project = workspace.AddProject("TestProject", LanguageNames.CSharp);
78-
var csharpSourceText = CodeDocument.GetCSharpSourceText();
79-
_csharpWorkspaceDocument = workspace.AddDocument(project.Id, "TestDocument", csharpSourceText);
80-
}
81-
82-
return _csharpWorkspaceDocument;
83-
}
84-
}
85-
86-
public AdhocWorkspace CSharpWorkspace => _csharpWorkspace ??= _workspaceFactory.Create();
87-
8860
/// <summary>A Dictionary of int (line number) to IndentationContext.</summary>
8961
/// <remarks>
9062
/// Don't use this to discover the indentation level you should have, use
@@ -252,15 +224,6 @@ public bool TryGetFormattingSpan(int absoluteIndex, [NotNullWhen(true)] out Form
252224
return false;
253225
}
254226

255-
public void Dispose()
256-
{
257-
_csharpWorkspace?.Dispose();
258-
if (_csharpWorkspaceDocument != null)
259-
{
260-
_csharpWorkspaceDocument = null;
261-
}
262-
}
263-
264227
public async Task<FormattingContext> WithTextAsync(SourceText changedText)
265228
{
266229
var changedSnapshot = OriginalSnapshot.WithText(changedText);
@@ -271,8 +234,6 @@ public async Task<FormattingContext> WithTextAsync(SourceText changedText)
271234

272235
var newContext = new FormattingContext(
273236
_codeDocumentProvider,
274-
_workspaceFactory,
275-
Uri,
276237
OriginalSnapshot,
277238
codeDocument,
278239
Options,
@@ -302,20 +263,16 @@ private static void DEBUG_ValidateComponents(RazorCodeDocument oldCodeDocument,
302263
}
303264

304265
public static FormattingContext CreateForOnTypeFormatting(
305-
Uri uri,
306266
IDocumentSnapshot originalSnapshot,
307267
RazorCodeDocument codeDocument,
308268
RazorFormattingOptions options,
309269
IFormattingCodeDocumentProvider codeDocumentProvider,
310-
IAdhocWorkspaceFactory workspaceFactory,
311270
bool automaticallyAddUsings,
312271
int hostDocumentIndex,
313272
char triggerCharacter)
314273
{
315274
return new FormattingContext(
316275
codeDocumentProvider,
317-
workspaceFactory,
318-
uri,
319276
originalSnapshot,
320277
codeDocument,
321278
options,
@@ -325,17 +282,13 @@ public static FormattingContext CreateForOnTypeFormatting(
325282
}
326283

327284
public static FormattingContext Create(
328-
Uri uri,
329285
IDocumentSnapshot originalSnapshot,
330286
RazorCodeDocument codeDocument,
331287
RazorFormattingOptions options,
332-
IFormattingCodeDocumentProvider codeDocumentProvider,
333-
IAdhocWorkspaceFactory workspaceFactory)
288+
IFormattingCodeDocumentProvider codeDocumentProvider)
334289
{
335290
return new FormattingContext(
336291
codeDocumentProvider,
337-
workspaceFactory,
338-
uri,
339292
originalSnapshot,
340293
codeDocument,
341294
options,

0 commit comments

Comments
 (0)