Repository navigation
fix(#1802): give language-service diagnostics the emit path's import scope - #1805
Conversation
…scope `lsp_diagnostics_for_node` reported 12 errors on `SocialMedia/PostsHub` while `get_diagnostics` said the assembly "was built without errors and is loaded" — same node, same minute. The LSP was wrong, and two agents spent a session fixing source that was never broken. The emit path concatenates every source INTO the generated skeleton (`NodeCompileShaping.CombineSources` → `DynamicMeshNodeAttributeGenerator`), so the skeleton's 22-namespace preamble and every file's hoisted `using`s cover the whole compilation. The language-service paths keep ONE TREE PER FILE so a diagnostic carries the MeshNode path the user edits — and a C# `using` is FILE-SCOPED, so the preamble reached none of those trees. Every source relying on it reported phantom CS0246/CS0308. Fix: render the same scope as `global using` — the language feature for exactly this — and give it to all three consumers that assemble per-file trees (`SpeculativeCompilation`, `CompileDiagnostics.DiagnoseInputs`, `MeshNodeLanguageService.BuildOrReuseWorkspace`). Per-file trees are kept, so positions still point at the file being edited. Not routed through `CombineSources` + `GenerateAttributeSource` as first proposed: that collapses every source into one document and destroys the per-file positions the language service exists to provide. The anti-drift intent is met instead by making both renderings read ONE declaration — `DynamicMeshNodeAttributeGenerator.StandardUsings` — and by hoisting sibling `using`s through the SAME `ExtractUsingStatements` the emit path hoists with. Tests pin both halves (preamble types, and a type imported only by a sibling). Verified as a real gate: with the global usings suppressed both FAIL with the exact production signature — CS0246 `IMeshService`, CS0246 `IMessageHub`, CS0103 `CultureInfo` — and pass with the fix. Full class 24/24, Release -warnaserror clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes a mismatch between the dynamic NodeType emit compilation path and the language-service/LSP diagnostic path by ensuring per-file Roslyn trees receive the same effective import scope that the real compiler gets via source concatenation. That removes false error reports (notably CS0246/CS0308) from lsp_diagnostics_for_node and related editor features while preserving per-file diagnostic locations.
Changes:
- Introduces a generated
global usingdocument (derived from the canonical “standard usings” list plus the union of authoredusings) and includes it in all per-file compilation/workspace builders. - Centralizes the “standard usings” list in
DynamicMeshNodeAttributeGenerator.StandardUsingsand makes the emit path read from that single declaration to prevent drift. - Adds regression tests covering (1) reliance on the generated preamble and (2) reliance on a sibling file’s
using.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| test/MeshWeaver.Hosting.Monolith.Test/MeshNodeLanguageServiceTest.cs | Adds regression tests proving diagnostics align with the emit path for preamble-only and sibling-using scenarios. |
| src/MeshWeaver.Graph/Configuration/MeshNodeLanguageService.cs | Adds a global-usings document to the Roslyn workspace and filters generated-tree diagnostics accordingly. |
| src/MeshWeaver.Graph/Configuration/MeshNodeCompilationService.cs | Populates CompilationInputs.GlobalUsingsSource using the shared generator so downstream LSP/diagnostics consumers get the correct scope. |
| src/MeshWeaver.Compiler/SpeculativeCompilation.cs | Recomputes global-usings scope from the effective (substituted) sources so Monaco edits immediately affect diagnostics. |
| src/MeshWeaver.Compiler/DynamicMeshNodeAttributeGenerator.cs | Introduces StandardUsings as the single source of truth and adds GenerateGlobalUsingsSource for the language-service path. |
| src/MeshWeaver.Compiler/CompileDiagnostics.cs | Extends per-file diagnostic shaping (DiagnoseInputs) to include the global-usings tree and filters its diagnostics from user-facing output. |
| src/MeshWeaver.Compiler/CompilationInputs.cs | Adds GlobalUsingsSource to the shared compilation/workspace input contract with a correctness-focused doc comment. |
| src/MeshWeaver.Documentation/Data/WhatsNew/2026-08-17-the-editor-stops-reporting-errors-that-are-not-there.md | Adds a WhatsNew entry documenting the user-visible fix and its impact on editor diagnostics. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Test Results (shard 5)1 371 tests 1 370 ✅ 5m 55s ⏱️ Results for commit a1148bd. |
Test Results (shard 2)2 462 tests 2 458 ✅ 7m 15s ⏱️ Results for commit a1148bd. |
Test Results (shard 1)2 247 tests 2 144 ✅ 8m 23s ⏱️ Results for commit a1148bd. |
Closes #1802.
The bug
Same node, same minute, two platform tools disagreeing:
@SocialMedia/PostsHubget_diagnosticsOk— "Compile SUCCEEDED at 20:07:44Z. The NodeType assembly was built without errors and is loaded."lsp_diagnostics_for_nodeIMeshService×2, CS1061 ×6The LSP was wrong. Two agents spent a session about to "fix" source that compiles and ships.
Root cause
NodeCompileShaping.CombineSourcesjoins ALL sources into ONECodeConfiguration.Code;DynamicMeshNodeAttributeGeneratorprepends a 22-namespace preamble and hoists every file'susings. One unit ⇒ everything in scope.usingis file-scoped, so the skeleton's preamble covered nothing, and every source relying on it reported phantom CS0246/CS0308.The fix
Render the same scope as
global using— the language feature for precisely this — and hand it to all three consumers that assemble per-file trees:SpeculativeCompilation(recomputed from the substituted sources, so ausingtyped in Monaco counts)CompileDiagnostics.DiagnoseInputs← the one behindlsp_diagnostics_for_nodeMeshNodeLanguageService.BuildOrReuseWorkspace(hover/completions)Per-file trees are preserved, so positions still point at the file being edited. Generated-tree diagnostics are filtered, as the skeleton's already were.
Why not
CombineSources+GenerateAttributeSource(the first-proposed route)That collapses every source into one document and destroys the per-file positions the language service exists to provide — every diagnostic, hover and completion would report a location in a synthetic combined file. Preserving them would need an offset-mapping layer, which is itself a second implementation of the compile.
The actual intent — don't let the diagnostic path drift from the compile — is met structurally instead:
DynamicMeshNodeAttributeGenerator.StandardUsings(the emit path's inline list is now a loop over it, so adding a namespace updates both);usings are hoisted through the sameExtractUsingStatementsthe emit path hoists with.Verification — the tests are a real gate
Two tests pin both halves: a source using preamble-only types (
IMeshService,IMessageHub, genericGetService<T>) with nousingof its own, and a source using a type imported only by a sibling.Proven to fail without the fix — with the global usings suppressed both go red with the exact production signature:
MeshNodeLanguageServiceTest24/24dotnet build -c Release -warnaserrorclean;WhatsNewEntryIntegrityTestpassesWhy this mattered
AGENTS.md mandates
LspDiagnosticsForNodeas the pre-prod sweep. With the known false PASS (#1592) it was untrustworthy in both directions — a sweep could report phantom failures and miss real ones.🤖 Generated with Claude Code