Skip to content

Commit b7fd5df

Browse files
Copilotkoenbeuk
andauthored
Merge branch 'main' into feat/synthesized-sources
Co-authored-by: koenbeuk <2912652+koenbeuk@users.noreply.github.com>
2 parents 4196b46 + 9b15f1d commit b7fd5df

170 files changed

Lines changed: 740 additions & 2985 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/Docs/Playground.Wasm/Program.cs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@
1111
builder.RootComponents.RegisterCustomElement<PlaygroundHost>("expressive-playground");
1212

1313
// BaseAddress must point to the playground's own directory (where _framework/
14-
// lives) so PlaygroundReferences can fetch reference DLLs. When the web
15-
// component is hosted on a VitePress page, HostEnvironment.BaseAddress is
16-
// the docs site root — not the playground subdirectory. We detect this by
17-
// checking whether the base ends with /playground/.
14+
// lives) so PlaygroundReferences can fetch reference DLLs. On a VitePress page
15+
// HostEnvironment.BaseAddress is the docs site root, not the playground
16+
// subdirectory — detect by checking whether the base ends with /playground/.
1817
var baseAddress = builder.HostEnvironment.BaseAddress;
1918
if (!baseAddress.TrimEnd('/').EndsWith("/_playground", StringComparison.OrdinalIgnoreCase)
2019
&& !baseAddress.TrimEnd('/').EndsWith("/playground", StringComparison.OrdinalIgnoreCase))
@@ -29,8 +28,6 @@
2928

3029
var host = builder.Build();
3130

32-
// Register Monaco completion + hover providers via our JS interop module.
33-
// The DotNetObjectReference callbacks dispatch to PlaygroundLanguageServices.
3431
var runtime = host.Services.GetRequiredService<PlaygroundRuntime>();
3532
var jsRuntime = host.Services.GetRequiredService<IJSRuntime>();
3633

@@ -40,10 +37,8 @@
4037

4138
await host.RunAsync();
4239

43-
/// <summary>
44-
/// Bridge object exposed to JS via DotNetObjectReference. Monaco's completion
45-
/// and hover providers call back into these [JSInvokable] methods.
46-
/// </summary>
40+
// Bridge object exposed to JS via DotNetObjectReference. Monaco's completion
41+
// and hover providers call back into these [JSInvokable] methods.
4742
internal sealed class MonacoLanguageProviderBridge
4843
{
4944
private readonly PlaygroundRuntime _runtime;

src/Docs/Playground.Wasm/Services/ManagedSqliteStub.cs

Lines changed: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
1-
// ManagedSqliteStub — a fully-managed SQLitePCL.ISQLite3Provider implementation
2-
// that EF Core can interrogate at startup *without* needing the native sqlite3
3-
// engine. The playground only ever calls ToQueryString() on a queryable; that
4-
// path goes through EF Core's relational query translator and never executes
5-
// real SQL, but the SqliteUpdateSqlGenerator constructor probes
6-
// connection.ServerVersion → SQLitePCL.raw.sqlite3_libversion() during DI
7-
// graph construction. This stub answers the metadata calls that EF Core makes
8-
// during model build, and throws NotSupportedException for anything that would
9-
// require running real queries.
1+
// Fully-managed SQLitePCL.ISQLite3Provider so EF Core can be interrogated at
2+
// startup without the native sqlite3 engine. The playground only calls
3+
// ToQueryString() — never executes real SQL — but SqliteUpdateSqlGenerator's
4+
// ctor probes connection.ServerVersion → sqlite3_libversion() during DI graph
5+
// construction. This stub answers the metadata calls EF Core makes during
6+
// model build; everything else throws.
107
//
11-
// The interface has 152 methods (utf8z is a ref struct so DispatchProxy can't
12-
// be used). Most just throw — only a handful of "metadata" calls have real
13-
// answers. Generated from reflection at design time and committed.
8+
// utf8z is a ref struct so DispatchProxy can't be used; the 152 methods are
9+
// generated from reflection at design time and committed.
1410

1511
#pragma warning disable IDE0060 // Unused parameters — interface forces us to declare them
1612

@@ -20,10 +16,7 @@ namespace ExpressiveSharp.Docs.Playground.Wasm.Services;
2016

2117
public sealed class ManagedSqliteStub : ISQLite3Provider
2218
{
23-
/// <summary>
24-
/// Registers this stub as the global SQLitePCL provider. Idempotent.
25-
/// Must be called before any DbContext that uses Sqlite.Core is constructed.
26-
/// </summary>
19+
// Idempotent — must be called before any DbContext using Sqlite.Core is constructed.
2720
public static void Register()
2821
{
2922
try
@@ -32,37 +25,30 @@ public static void Register()
3225
}
3326
catch (System.InvalidOperationException)
3427
{
35-
// SetProvider throws if a provider is already registered. Tolerate
36-
// double-registration so the stub can be installed eagerly from
37-
// multiple host entrypoints (Program.cs, test setup, etc).
28+
// SetProvider throws if a provider is already registered; tolerate
29+
// double-registration from multiple host entrypoints.
3830
}
3931
}
4032

41-
// ── Metadata calls EF Core makes during DbContext init ─────────────────
42-
43-
// Reported as the SQLite library version. EF Core uses this for feature
44-
// detection (RETURNING clause support added in 3.35). Reporting a recent
45-
// version unlocks all relational translator features.
33+
// EF Core uses libversion for feature detection (RETURNING added in 3.35).
34+
// Reporting a recent version unlocks all relational translator features.
4635
public utf8z sqlite3_libversion() => utf8z.FromString("3.45.0");
4736

48-
// Companion to libversion. Format is major*1_000_000 + minor*1_000 + patch.
37+
// Format is major*1_000_000 + minor*1_000 + patch.
4938
public int sqlite3_libversion_number() => 3045000;
5039

5140
public utf8z sqlite3_sourceid() => utf8z.FromString("managed-stub-no-sourceid");
5241

53-
// 1 = single-threaded. WASM is single-threaded anyway.
5442
public int sqlite3_threadsafe() => 1;
5543

5644
public string GetNativeLibraryName() => "managed-stub";
5745

58-
// SQLitePCL.raw initializes by calling these once. They must succeed.
46+
// SQLitePCL.raw calls these once during init — they must succeed.
5947
public int sqlite3_initialize() => 0; // SQLITE_OK
6048
public int sqlite3_shutdown() => 0;
6149
public int sqlite3_config_log(global::SQLitePCL.delegate_log @func, global::System.Object @v) => 0;
6250
public int sqlite3_enable_shared_cache(int @enable) => 0;
6351

64-
// ── Everything else throws ─────────────────────────────────────────────
65-
6652
private static System.Exception NotSupported(string method) =>
6753
new System.NotSupportedException(
6854
$"ManagedSqliteStub does not implement '{method}'. " +

src/Docs/Playground.Wasm/Services/MonacoTypes.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
namespace ExpressiveSharp.Docs.Playground.Wasm.Services;
22

3-
// DTOs for JSInterop serialization with Monaco editor.
4-
// These replace the BlazorMonaco C# types with plain records
5-
// that serialize cleanly to/from the Monaco JS API.
3+
// DTOs for JSInterop with Monaco editor; replace BlazorMonaco's types with
4+
// plain records that serialize cleanly to/from the Monaco JS API.
65

76
public sealed class MonacoPosition
87
{
@@ -50,7 +49,6 @@ public sealed class MonacoMarkerData
5049
public int EndColumn { get; set; }
5150
}
5251

53-
// Completion types
5452
public sealed class MonacoCompletionList
5553
{
5654
public List<MonacoCompletionItem> Suggestions { get; set; } = new();
@@ -68,7 +66,6 @@ public sealed class MonacoCompletionItem
6866
public MonacoRange? Range { get; set; }
6967
}
7068

71-
// Hover types
7269
public sealed class MonacoHover
7370
{
7471
public List<MonacoMarkdownString> Contents { get; set; } = new();
@@ -81,7 +78,7 @@ public sealed class MonacoMarkdownString
8178
public bool IsTrusted { get; set; }
8279
}
8380

84-
// Monaco CompletionItemKind enum values (matching monaco.languages.CompletionItemKind)
81+
// Values match monaco.languages.CompletionItemKind.
8582
public static class MonacoCompletionItemKind
8683
{
8784
public const int Method = 0;
@@ -109,7 +106,7 @@ public static class MonacoCompletionItemKind
109106
public const int TypeParameter = 24;
110107
}
111108

112-
// Monaco MarkerSeverity values (matching monaco.MarkerSeverity)
109+
// Values match monaco.MarkerSeverity.
113110
public static class MonacoMarkerSeverity
114111
{
115112
public const int Hint = 1;

src/Docs/Playground.Wasm/Services/PlaygroundLanguageServices.cs

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
// PlaygroundLanguageServices — long-lived AdhocWorkspace that backs Monaco's
2-
// completion + hover providers. Separate from SnippetCompiler (which builds
3-
// fresh CSharpCompilations per keystroke for the run path); the workspace's
4-
// incremental semantic model gives sub-ms per-keystroke completion.
5-
//
6-
// Architecture inspired by DotNetLab/src/Compiler/LanguageServices.cs (MIT):
7-
// one AdhocWorkspace, one Project, one Document per <expressive-playground>
8-
// instance routed by Monaco model URI. The DefaultPersistentStorageConfiguration
9-
// cctor PNSE is bypassed by the WorkspaceShim project — see its header.
1+
// Long-lived AdhocWorkspace backing Monaco's completion + hover providers; the
2+
// incremental semantic model gives sub-ms per-keystroke completion. Separate
3+
// from SnippetCompiler, which builds fresh CSharpCompilations per run.
4+
// One Document per <expressive-playground> instance, routed by Monaco model
5+
// URI. The DefaultPersistentStorageConfiguration cctor PNSE on WASM is bypassed
6+
// by the WorkspaceShim project — see its header.
7+
// Architecture inspired by DotNetLab/src/Compiler/LanguageServices.cs (MIT).
108

119
using ExpressiveSharp.Docs.Playground.Core.Services;
1210
using ExpressiveSharp.Docs.Playground.Core.Services.Scenarios;
@@ -38,11 +36,10 @@ public PlaygroundLanguageServices(PlaygroundReferences references)
3836
throw new InvalidOperationException(
3937
"PlaygroundReferences must be loaded before PlaygroundLanguageServices is constructed.");
4038

41-
// Append the WorkspaceShim assembly AFTER MefHostServices.DefaultAssemblies.
42-
// The shim's NoOpPersistentStorageConfiguration is exported with
43-
// ServiceLayer.Test which gives it MEF priority over Roslyn's broken
44-
// DefaultPersistentStorageConfiguration, so the latter's cctor never
45-
// runs and Process.GetCurrentProcess() (PNSE on WASM) is never called.
39+
// Append WorkspaceShim AFTER DefaultAssemblies. Its NoOpPersistentStorageConfiguration
40+
// exports with ServiceLayer.Test, gaining MEF priority over Roslyn's
41+
// DefaultPersistentStorageConfiguration so the latter's cctor never runs
42+
// (it calls Process.GetCurrentProcess() — PNSE on WASM).
4643
var hostServices = MefHostServices.Create(
4744
MefHostServices.DefaultAssemblies
4845
.Append(typeof(NoOpPersistentStorageConfiguration).Assembly));
@@ -56,8 +53,7 @@ public PlaygroundLanguageServices(PlaygroundReferences references)
5653
assemblyName: "PlaygroundProject",
5754
language: LanguageNames.CSharp,
5855
metadataReferences: references.References,
59-
// WithConcurrentBuild(false): WASM is single-threaded, parallel
60-
// Roslyn compile threads deadlock or throw.
56+
// WASM is single-threaded; parallel Roslyn compile threads deadlock or throw.
6157
compilationOptions: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
6258
.WithNullableContextOptions(NullableContextOptions.Enable)
6359
.WithConcurrentBuild(false),
@@ -67,12 +63,9 @@ public PlaygroundLanguageServices(PlaygroundReferences references)
6763
_projectId = projectInfo.Id;
6864
}
6965

70-
/// <summary>
71-
/// Forces MEF composition during page load instead of lazily on the first
72-
/// keystroke. Adds a throwaway document, asks for its CompletionService
73-
/// (the act of resolving it triggers MEF + the cctor moment WorkspaceShim
74-
/// guards against), discards. Subsequent real completions hit warm caches.
75-
/// </summary>
66+
// Forces MEF composition during page load instead of lazily on the first
67+
// keystroke. Resolving CompletionService triggers MEF + the cctor moment
68+
// WorkspaceShim guards against; subsequent real completions hit warm caches.
7669
public async Task PrewarmAsync()
7770
{
7871
await _lock.WaitAsync();
@@ -117,8 +110,7 @@ public async Task RegisterEditorAsync(string modelUri, string snippetText, strin
117110
await _lock.WaitAsync();
118111
try
119112
{
120-
// Drop any prior document under the same URI (rare — happens if
121-
// an instance disposes and remounts) to avoid duplicate routing.
113+
// Drop any prior document under the same URI (instance dispose + remount).
122114
if (_modelToDocument.TryGetValue(modelUri, out var existingId))
123115
{
124116
_workspace.TryApplyChanges(_workspace.CurrentSolution.RemoveDocument(existingId));
@@ -137,7 +129,7 @@ public async Task RegisterEditorAsync(string modelUri, string snippetText, strin
137129
}
138130
}
139131

140-
// Per-keystroke (no debounce — Roslyn diffs the syntax tree, sub-ms).
132+
// Called per-keystroke; no debounce — Roslyn diffs the syntax tree (sub-ms).
141133
public async Task UpdateEditorAsync(string modelUri, string snippetText, string? setupText, IPlaygroundScenario scenario)
142134
{
143135
var wrap = SnippetWrap.Build(scenario.WrapperTemplate, snippetText, setupText);
@@ -158,7 +150,6 @@ public async Task UpdateEditorAsync(string modelUri, string snippetText, string?
158150
}
159151
}
160152

161-
// Best-effort: silently no-ops if the model URI is unknown.
162153
public async Task UnregisterEditorAsync(string modelUri)
163154
{
164155
await _lock.WaitAsync();
@@ -236,9 +227,9 @@ public async Task UnregisterEditorAsync(string modelUri)
236227
}
237228

238229
// Returns -1 if the position falls outside the snippet region.
230+
// Monaco is 1-based, LinePosition is 0-based.
239231
private static int MonacoPositionToCaretOffset(MonacoPosition position, SourceText text, SnippetWrap wrap)
240232
{
241-
// Monaco is 1-based, LinePosition is 0-based.
242233
var snippetRelative = new LinePosition(
243234
line: Math.Max(0, position.LineNumber - 1),
244235
character: Math.Max(0, position.Column - 1));

src/Docs/Playground.Wasm/Services/PlaygroundReferences.cs

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
1-
// PlaygroundReferences — fetches the reference assemblies the SnippetCompiler
2-
// needs into MetadataReference instances. In a Blazor WASM app every loaded
3-
// assembly is shipped under /_framework/<name>.dll, so we use HttpClient to
4-
// pull the raw bytes and feed them to MetadataReference.CreateFromImage.
5-
//
6-
// The reference set is the union of every scenario's ReferenceAssemblies,
7-
// loaded once at startup and cached. Adding a new scenario in Phase 2
8-
// automatically extends the reference set with its assemblies — no edits to
9-
// this file are required.
1+
// In Blazor WASM every loaded assembly ships under /_framework/<name>.dll, so
2+
// HttpClient pulls the raw bytes and feeds them to MetadataReference.CreateFromImage.
3+
// The reference set is the union of every scenario's ReferenceAssemblies —
4+
// adding a scenario to ScenarioRegistry automatically extends the set.
105

116
using System.Collections.Immutable;
127
using Basic.Reference.Assemblies;
@@ -36,17 +31,9 @@ public async Task LoadAsync()
3631

3732
var builder = ImmutableArray.CreateBuilder<MetadataReference>();
3833

39-
// .NET 10 ref-only BCL — embedded as resources in the
40-
// Basic.Reference.Assemblies.Net100 assembly. No HTTP needed; the
41-
// package's own embedded resources are loaded by the normal Blazor
42-
// assembly loader at startup.
34+
// .NET 10 ref-only BCL — embedded resources in Basic.Reference.Assemblies.Net100.
4335
builder.AddRange(Net100.References.All);
4436

45-
// Take the union of every scenario's ReferenceAssemblies. With one
46-
// scenario today this loads ExpressiveSharp + ExpressiveSharp.EntityFrameworkCore
47-
// + EF Core 10 + the PlaygroundModel assembly. Future scenarios are
48-
// additive: registering a Mongo scenario in ScenarioRegistry would
49-
// automatically pull in MongoDB.Driver here.
5037
var seenNames = new HashSet<string>(StringComparer.Ordinal);
5138
var fetchTasks = new List<Task<MetadataReference?>>();
5239

@@ -78,8 +65,7 @@ public async Task LoadAsync()
7865
}
7966
catch (HttpRequestException)
8067
{
81-
// Missing DLL → skip; Roslyn surfaces a "missing reference" diagnostic
82-
// later if the snippet actually needs the type.
68+
// Missing DLL → skip; Roslyn reports "missing reference" later if needed.
8369
return null;
8470
}
8571
}

0 commit comments

Comments
 (0)