Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Further refactoring of Razor tooling project system #11320

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
fcfd3ce
ProjectState: Replace LanguageServerFeatureOptions with enum
DustinCampbell Dec 12, 2024
eb5d916
Improve ProjectState.Create(...) factory methods and constructors
DustinCampbell Dec 12, 2024
ea3fd03
Clean up TestProjectEngineFactoryProvider a bit
DustinCampbell Dec 12, 2024
9042ae4
Seal DocumentState and ProjectState and make effectively immutable
DustinCampbell Dec 12, 2024
ee0e239
Rework ProjectState.ImportsToRelatedDocuments map
DustinCampbell Dec 13, 2024
3ba0eda
Simplify DocumentState.Version tracking
DustinCampbell Dec 16, 2024
fdf9486
Remove unused DocumentState property
DustinCampbell Dec 16, 2024
5b0bfb6
Clean up DocumentState With* methods
DustinCampbell Dec 16, 2024
8d376cc
Move DocumentState helper methods for computing imports
DustinCampbell Dec 16, 2024
4601b2d
Clean up EmptyTextLoader (currently only used by tests)
DustinCampbell Dec 16, 2024
e3a9a86
Replace DocumentState.EmptyLoader with EmptyTextLoader.Instance
DustinCampbell Dec 16, 2024
4d18137
Replace CreateEmptyTextLoader with EmptyTextLoader.Instance
DustinCampbell Dec 16, 2024
4c9becf
Clean up TestMocks.CreateTextLoader calls
DustinCampbell Dec 16, 2024
a3b25f8
Clean up calls to ProjectState.AddDocument with empty loaders
DustinCampbell Dec 16, 2024
18ad48f
Augment Assumed class with new helpers and message parameters
DustinCampbell Dec 16, 2024
ae4e86c
Introduce AsyncLazy<T> variation from Roslyn
DustinCampbell Dec 16, 2024
813e448
Introduce ITextAndVersionSource to manage text loading in DocumentState
DustinCampbell Dec 16, 2024
b508614
Don't update ProjectState when DocumentState doesn't
DustinCampbell Dec 16, 2024
1f9144f
Move DocumentState helper methods for compilation
DustinCampbell Dec 17, 2024
df3e21c
Remove DocumentState.ComputedStateTracker
DustinCampbell Dec 17, 2024
1e045ea
Introduce legacy snapshot interfaces to support legacy editor
DustinCampbell Dec 17, 2024
0049ac0
Remove IProjectSnapshot.Version
DustinCampbell Dec 17, 2024
b5acc30
Remove ProjectState.DocumentCollectionVersion
DustinCampbell Dec 17, 2024
f26bd81
Remove remaining version logic from ProjectState
DustinCampbell Dec 17, 2024
b341603
Make IProjectSnapshot.GetProjectEngine() async
DustinCampbell Dec 17, 2024
aafea13
Remove IProjectSnapshotManager interface
DustinCampbell Dec 18, 2024
c838a75
Remove IProjectSnapshot.GetProjectEngineAsync
DustinCampbell Dec 18, 2024
49a973c
Remove IProjectSnapshot.ProjectWorkspaceState
DustinCampbell Dec 18, 2024
04bf4bf
Move IDocumentSnapshot and IProjectSnapshot to lower tooling layer
DustinCampbell Dec 18, 2024
f27d768
Remove IProjectSnapshot.Configuration
DustinCampbell Dec 18, 2024
67e3227
Don't use LINQ Count() method
DustinCampbell Dec 18, 2024
295d482
Compute test span correctly
DustinCampbell Dec 18, 2024
74167ba
Double-checked lock
DustinCampbell Dec 18, 2024
670eae2
Fix comment
DustinCampbell Dec 18, 2024
f28f239
Remove unused method
DustinCampbell Dec 18, 2024
284c7d7
Rename method to ProjectState.UpdateRelatedDocumentsIfNecessary
DustinCampbell Dec 18, 2024
f952c9b
Delete unused ExtractToComponentResolverDocumentContextFactory
DustinCampbell Dec 18, 2024
1deb4a0
Remove all but one ProjectState.Create(...) factory method
DustinCampbell Dec 18, 2024
d35e121
RazorProjectService: Use TryGetDocument rather than pattern matching
DustinCampbell Dec 18, 2024
b51fabc
RazorProjectService: Rename for consistency throughout
DustinCampbell Dec 18, 2024
f3fed12
ProjectState: Don't perform string replacements unless necessary
DustinCampbell Dec 18, 2024
80709ba
Merge branch 'main' into cleanup-project-and-documentstate
DustinCampbell Jan 6, 2025
f9096fe
Move FORMAT_FUSE constant to Directory.Build.props
DustinCampbell Jan 6, 2025
4d299ee
Merge branch 'main' into cleanup-project-and-documentstate
DustinCampbell Jan 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Utilities;

namespace Microsoft.AspNetCore.Razor.Threading;

internal static class SemaphoreSlimExtensions
{
public static SemaphoreDisposer DisposableWait(this SemaphoreSlim semaphore, CancellationToken cancellationToken = default)
{
semaphore.Wait(cancellationToken);
return new SemaphoreDisposer(semaphore);
}

public static async ValueTask<SemaphoreDisposer> DisposableWaitAsync(this SemaphoreSlim semaphore, CancellationToken cancellationToken = default)
{
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
return new SemaphoreDisposer(semaphore);
}

[NonCopyable]
internal struct SemaphoreDisposer : IDisposable
{
private SemaphoreSlim? _semaphore;

public SemaphoreDisposer(SemaphoreSlim semaphore)
{
_semaphore = semaphore;
}

public void Dispose()
{
// Officially, Dispose() being called more than once is allowable, but in this case
// if that were to ever happen that means something is very, very wrong. Since it's an internal
// type, better to be strict.

// Nulling this out also means it's a bit easier to diagnose some async deadlocks; if you have an
// async deadlock where a SemaphoreSlim is held but you're unsure why, as long all the users of the
// SemaphoreSlim used the Disposable helpers, you can search memory and find the instance that
// is pointing to the SemaphoreSlim that hasn't nulled out this field yet; in that case you know
// that's holding the lock and can figure out who is holding that SemaphoreDisposer.
var semaphoreToDispose = Interlocked.Exchange(ref _semaphore, null);

if (semaphoreToDispose is null)
{
throw new ObjectDisposedException($"Somehow a {nameof(SemaphoreDisposer)} is being disposed twice.");
}

semaphoreToDispose.Release();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ void ConvertCodeActionsToSumType(ImmutableArray<RazorVSInternalCodeAction> codeA
public async Task<VSCodeActionParams?> GetCSharpCodeActionsRequestAsync(IDocumentSnapshot documentSnapshot, VSCodeActionParams request, CancellationToken cancellationToken)
{
// For C# we have to map the ranges to the generated document
var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(forceDesignTimeGeneratedOutput: false, cancellationToken).ConfigureAwait(false);
var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(cancellationToken).ConfigureAwait(false);
var csharpDocument = codeDocument.GetCSharpDocument();
if (!_documentMappingService.TryMapToGeneratedDocumentRange(csharpDocument, request.Range, out var projectedRange))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Microsoft.AspNetCore.Razor.Language.Legacy;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Razor.Workspaces;
Expand All @@ -19,6 +22,7 @@ internal static class RazorCodeDocumentExtensions
{
private static readonly object s_csharpSourceTextKey = new();
private static readonly object s_htmlSourceTextKey = new();
private static readonly object s_csharpSyntaxTreeKey = new();

public static SourceText GetCSharpSourceText(this RazorCodeDocument document)
{
Expand Down Expand Up @@ -48,6 +52,24 @@ public static SourceText GetHtmlSourceText(this RazorCodeDocument document)
return sourceText.AssumeNotNull();
}

/// <summary>
/// Retrieves a cached Roslyn <see cref="SyntaxTree"/> from the generated C# document.
/// If a tree has not yet been cached, a new one will be parsed and added to the cache.
/// </summary>
public static SyntaxTree GetOrParseCSharpSyntaxTree(this RazorCodeDocument document, CancellationToken cancellationToken)
{
if (!document.Items.TryGetValue(s_csharpSyntaxTreeKey, out SyntaxTree? syntaxTree))
{
var csharpText = document.GetCSharpSourceText();
syntaxTree = CSharpSyntaxTree.ParseText(csharpText, cancellationToken: cancellationToken);
document.Items[s_csharpSyntaxTreeKey] = syntaxTree;

return syntaxTree;
}

return syntaxTree.AssumeNotNull();
}

public static bool TryGetGeneratedDocument(
this RazorCodeDocument codeDocument,
Uri generatedDocumentUri,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,8 @@ public async Task<FormattingContext> WithTextAsync(SourceText changedText, Cance
var changedSnapshot = OriginalSnapshot.WithText(changedText);

// Formatting always uses design time document
var codeDocument = await changedSnapshot.GetGeneratedOutputAsync(forceDesignTimeGeneratedOutput: true, cancellationToken).ConfigureAwait(false);
var generator = (IDesignTimeCodeGenerator)changedSnapshot;
var codeDocument = await generator.GenerateDesignTimeOutputAsync(cancellationToken).ConfigureAwait(false);

DEBUG_ValidateComponents(CodeDocument, codeDocument);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ public async Task<ImmutableArray<TextChange>> GetDocumentFormattingChangesAsync(
RazorFormattingOptions options,
CancellationToken cancellationToken)
{
var codeDocument = await documentContext.Snapshot.GetGeneratedOutputAsync(forceDesignTimeGeneratedOutput: true, cancellationToken).ConfigureAwait(false);
var generator = (IDesignTimeCodeGenerator)documentContext.Snapshot;
var codeDocument = await generator.GenerateDesignTimeOutputAsync(cancellationToken).ConfigureAwait(false);

// Range formatting happens on every paste, and if there are Razor diagnostics in the file
// that can make some very bad results. eg, given:
Expand Down Expand Up @@ -114,7 +115,8 @@ public async Task<ImmutableArray<TextChange>> GetDocumentFormattingChangesAsync(
public async Task<ImmutableArray<TextChange>> GetCSharpOnTypeFormattingChangesAsync(DocumentContext documentContext, RazorFormattingOptions options, int hostDocumentIndex, char triggerCharacter, CancellationToken cancellationToken)
{
var documentSnapshot = documentContext.Snapshot;
var codeDocument = await documentContext.Snapshot.GetGeneratedOutputAsync(forceDesignTimeGeneratedOutput: true, cancellationToken).ConfigureAwait(false);
var generator = (IDesignTimeCodeGenerator)documentSnapshot;
var codeDocument = await generator.GenerateDesignTimeOutputAsync(cancellationToken).ConfigureAwait(false);

return await ApplyFormattedChangesAsync(
documentSnapshot,
Expand All @@ -132,7 +134,8 @@ public async Task<ImmutableArray<TextChange>> GetCSharpOnTypeFormattingChangesAs
public async Task<ImmutableArray<TextChange>> GetHtmlOnTypeFormattingChangesAsync(DocumentContext documentContext, ImmutableArray<TextChange> htmlChanges, RazorFormattingOptions options, int hostDocumentIndex, char triggerCharacter, CancellationToken cancellationToken)
{
var documentSnapshot = documentContext.Snapshot;
var codeDocument = await documentContext.Snapshot.GetGeneratedOutputAsync(forceDesignTimeGeneratedOutput: true, cancellationToken).ConfigureAwait(false);
var generator = (IDesignTimeCodeGenerator)documentSnapshot;
var codeDocument = await generator.GenerateDesignTimeOutputAsync(cancellationToken).ConfigureAwait(false);

return await ApplyFormattedChangesAsync(
documentSnapshot,
Expand All @@ -151,7 +154,7 @@ public async Task<ImmutableArray<TextChange>> GetHtmlOnTypeFormattingChangesAsyn
{
var documentSnapshot = documentContext.Snapshot;
// Since we've been provided with an edit from the C# generated doc, forcing design time would make things not line up
var codeDocument = await documentContext.Snapshot.GetGeneratedOutputAsync(forceDesignTimeGeneratedOutput: false, cancellationToken).ConfigureAwait(false);
var codeDocument = await documentContext.Snapshot.GetGeneratedOutputAsync(cancellationToken).ConfigureAwait(false);

var razorChanges = await ApplyFormattedChangesAsync(
documentSnapshot,
Expand All @@ -171,7 +174,7 @@ public async Task<ImmutableArray<TextChange>> GetHtmlOnTypeFormattingChangesAsyn
{
var documentSnapshot = documentContext.Snapshot;
// Since we've been provided with edits from the C# generated doc, forcing design time would make things not line up
var codeDocument = await documentContext.Snapshot.GetGeneratedOutputAsync(forceDesignTimeGeneratedOutput: false, cancellationToken).ConfigureAwait(false);
var codeDocument = await documentContext.Snapshot.GetGeneratedOutputAsync(cancellationToken).ConfigureAwait(false);

var razorChanges = await ApplyFormattedChangesAsync(
documentSnapshot,
Expand All @@ -193,7 +196,7 @@ public async Task<ImmutableArray<TextChange>> GetHtmlOnTypeFormattingChangesAsyn

var documentSnapshot = documentContext.Snapshot;
// Since we've been provided with edits from the C# generated doc, forcing design time would make things not line up
var codeDocument = await documentContext.Snapshot.GetGeneratedOutputAsync(forceDesignTimeGeneratedOutput: false, cancellationToken).ConfigureAwait(false);
var codeDocument = await documentContext.Snapshot.GetGeneratedOutputAsync(cancellationToken).ConfigureAwait(false);

var razorChanges = await ApplyFormattedChangesAsync(
documentSnapshot,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using Microsoft.AspNetCore.Razor;
using System.Collections.Immutable;
using Microsoft.AspNetCore.Razor.Language;

namespace Microsoft.CodeAnalysis.Razor.ProjectSystem;

internal readonly struct CodeDocumentGenerator(RazorProjectEngine projectEngine, RazorCompilerOptions compilerOptions)
{
public RazorCodeDocument Generate(
RazorSourceDocument source,
string fileKind,
ImmutableArray<RazorSourceDocument> importSources,
ImmutableArray<TagHelperDescriptor> tagHelpers)
{
var forceRuntimeCodeGeneration = compilerOptions.IsFlagSet(RazorCompilerOptions.ForceRuntimeCodeGeneration);

return forceRuntimeCodeGeneration
? projectEngine.Process(source, fileKind, importSources, tagHelpers)
: projectEngine.ProcessDesignTime(source, fileKind, importSources, tagHelpers);
}

public RazorCodeDocument GenerateDesignTime(
RazorSourceDocument source,
string fileKind,
ImmutableArray<RazorSourceDocument> importSources,
ImmutableArray<TagHelperDescriptor> tagHelpers)
{
return projectEngine.ProcessDesignTime(source, fileKind, importSources, tagHelpers);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,82 @@
// Licensed under the MIT license. See License.txt in the project root for license information.

using System.Collections.Immutable;
using System.Threading.Tasks;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor;
using Microsoft.AspNetCore.Razor.Language;

namespace Microsoft.CodeAnalysis.Razor.ProjectSystem;

internal static class CompilationHelpers
{
internal static RazorCodeDocument GenerateCodeDocument(
RazorProjectEngine projectEngine,
RazorCompilerOptions compilerOptions,
RazorSourceDocument source,
string fileKind,
ImmutableArray<RazorSourceDocument> importSources,
ImmutableArray<TagHelperDescriptor> tagHelpers)
{
var forceRuntimeCodeGeneration = compilerOptions.IsFlagSet(RazorCompilerOptions.ForceRuntimeCodeGeneration);

return forceRuntimeCodeGeneration
DustinCampbell marked this conversation as resolved.
Show resolved Hide resolved
? projectEngine.Process(source, fileKind, importSources, tagHelpers)
: projectEngine.ProcessDesignTime(source, fileKind, importSources, tagHelpers);
}

internal static async Task<RazorCodeDocument> GenerateCodeDocumentAsync(
IDocumentSnapshot document,
RazorProjectEngine projectEngine,
bool forceRuntimeCodeGeneration,
RazorCompilerOptions compilerOptions,
CancellationToken cancellationToken)
{
var importItems = await ImportHelpers.GetImportItemsAsync(document, projectEngine, cancellationToken).ConfigureAwait(false);

return await GenerateCodeDocumentAsync(
document, projectEngine, importItems, forceRuntimeCodeGeneration, cancellationToken).ConfigureAwait(false);
document, projectEngine, importItems, compilerOptions, cancellationToken).ConfigureAwait(false);
}

internal static async Task<RazorCodeDocument> GenerateCodeDocumentAsync(
IDocumentSnapshot document,
RazorProjectEngine projectEngine,
ImmutableArray<ImportItem> imports,
bool forceRuntimeCodeGeneration,
RazorCompilerOptions compilerOptions,
CancellationToken cancellationToken)
{
var importSources = ImportHelpers.GetImportSources(imports, projectEngine);
var tagHelpers = await document.Project.GetTagHelpersAsync(cancellationToken).ConfigureAwait(false);
var source = await ImportHelpers.GetSourceAsync(document, projectEngine, cancellationToken).ConfigureAwait(false);

return forceRuntimeCodeGeneration
? projectEngine.Process(source, document.FileKind, importSources, tagHelpers)
: projectEngine.ProcessDesignTime(source, document.FileKind, importSources, tagHelpers);
var generator = new CodeDocumentGenerator(projectEngine, compilerOptions);
return generator.Generate(source, document.FileKind, importSources, tagHelpers);
}

internal static async Task<RazorCodeDocument> GenerateDesignTimeCodeDocumentAsync(
IDocumentSnapshot document,
RazorProjectEngine projectEngine,
ImmutableArray<ImportItem> imports,
CancellationToken cancellationToken)
{
var importSources = ImportHelpers.GetImportSources(imports, projectEngine);
var tagHelpers = await document.Project.GetTagHelpersAsync(cancellationToken).ConfigureAwait(false);
var source = await ImportHelpers.GetSourceAsync(document, projectEngine, cancellationToken).ConfigureAwait(false);

var generator = new CodeDocumentGenerator(projectEngine, RazorCompilerOptions.None);
return generator.GenerateDesignTime(source, document.FileKind, importSources, tagHelpers);
}

internal static async Task<RazorCodeDocument> GenerateDesignTimeCodeDocumentAsync(
IDocumentSnapshot document,
RazorProjectEngine projectEngine,
CancellationToken cancellationToken)
{
var importItems = await ImportHelpers.GetImportItemsAsync(document, projectEngine, cancellationToken).ConfigureAwait(false);
var importSources = ImportHelpers.GetImportSources(importItems, projectEngine);
var tagHelpers = await document.Project.GetTagHelpersAsync(cancellationToken).ConfigureAwait(false);
var source = await ImportHelpers.GetSourceAsync(document, projectEngine, cancellationToken).ConfigureAwait(false);

var generator = new CodeDocumentGenerator(projectEngine, RazorCompilerOptions.None);
return generator.GenerateDesignTime(source, document.FileKind, importSources, tagHelpers);
}
}
Loading