TML-2984: LSP serves interpreter diagnostics from cached artifacts - #972
Conversation
📝 WalkthroughWalkthroughThe language server now resolves optional PSL interpretation context, lazily computes and maps interpreter diagnostics per document, combines them with existing diagnostics for pull and push flows, and re-runs interpretation after document changes. ChangesInterpreter diagnostics
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant LspClient
participant LanguageServer
participant DocumentArtifacts
participant PslInterpretCapable
participant mapInterpreterDiagnostics
LspClient->>LanguageServer: request diagnostics
LanguageServer->>DocumentArtifacts: combinedDiagnostics
DocumentArtifacts->>PslInterpretCapable: interpret document with context
PslInterpretCapable-->>DocumentArtifacts: ContractSourceDiagnostic[]
DocumentArtifacts->>mapInterpreterDiagnostics: map diagnostics
mapInterpreterDiagnostics-->>DocumentArtifacts: LspDiagnostic[]
DocumentArtifacts-->>LanguageServer: combined diagnostics
LanguageServer-->>LspClient: diagnostic report
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
@prisma-next/extension-author-tools
@prisma-next/mongo-runtime
@prisma-next/family-mongo
@prisma-next/sql-runtime
@prisma-next/family-sql
@prisma-next/extension-arktype-json
@prisma-next/middleware-cache
@prisma-next/mongo
@prisma-next/extension-paradedb
@prisma-next/extension-pgvector
@prisma-next/extension-postgis
@prisma-next/postgres
@prisma-next/sql-orm-client
@prisma-next/sqlite
@prisma-next/extension-supabase
@prisma-next/target-mongo
@prisma-next/adapter-mongo
@prisma-next/driver-mongo
@prisma-next/contract
@prisma-next/utils
@prisma-next/config
@prisma-next/errors
@prisma-next/framework-components
@prisma-next/operations
@prisma-next/ts-render
@prisma-next/contract-authoring
@prisma-next/ids
@prisma-next/psl-parser
@prisma-next/psl-printer
@prisma-next/cli
@prisma-next/cli-telemetry
@prisma-next/config-loader
@prisma-next/emitter
@prisma-next/language-server
@prisma-next/migration-tools
prisma-next
@prisma-next/vite-plugin-contract-emit
@prisma-next/mongo-codec
@prisma-next/mongo-contract
@prisma-next/mongo-value
@prisma-next/mongo-contract-psl
@prisma-next/mongo-contract-ts
@prisma-next/mongo-emitter
@prisma-next/mongo-schema-ir
@prisma-next/mongo-query-ast
@prisma-next/mongo-orm
@prisma-next/mongo-query-builder
@prisma-next/mongo-lowering
@prisma-next/mongo-wire
@prisma-next/sql-contract
@prisma-next/sql-errors
@prisma-next/sql-operations
@prisma-next/sql-schema-ir
@prisma-next/sql-contract-psl
@prisma-next/sql-contract-ts
@prisma-next/sql-contract-emitter
@prisma-next/sql-lane-query-builder
@prisma-next/sql-relational-core
@prisma-next/sql-builder
@prisma-next/target-postgres
@prisma-next/target-sqlite
@prisma-next/adapter-postgres
@prisma-next/adapter-sqlite
@prisma-next/driver-postgres
@prisma-next/driver-sqlite
commit: |
size-limit report 📦
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/1-framework/3-tooling/language-server/src/server.ts (1)
258-275: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winPrefer
ifDefinedover inline ternary spreads.Two new
...(x === undefined ? {} : { key: x })spreads are added here forinterpretation(and theformatterspread is rewritten the same way). Based on learnings, this repo prefersifDefined(key, value)fromprisma-next/utils/definedfor conditional object spreads instead of inline ternary-based spreads.♻️ Proposed refactor
const artifacts = createProjectArtifacts({ inputs: resolution.inputs, controlStack: resolution.controlStack, getText: (uri) => documents.get(uri)?.getText(), - ...(resolution.interpretation === undefined - ? {} - : { interpretation: resolution.interpretation }), + ...ifDefined('interpretation', resolution.interpretation), }); const project: ProjectState = { configPath, inputs: resolution.inputs, controlStack: resolution.controlStack, artifacts, - ...(resolution.formatter === undefined ? {} : { formatter: resolution.formatter }), - ...(resolution.interpretation === undefined - ? {} - : { interpretation: resolution.interpretation }), + ...ifDefined('formatter', resolution.formatter), + ...ifDefined('interpretation', resolution.interpretation), };🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/1-framework/3-tooling/language-server/src/server.ts` around lines 258 - 275, Replace the inline ternary-based conditional spreads for interpretation and formatter in the createProjectArtifacts options and ProjectState construction with the repository’s ifDefined helper from prisma-next/utils/defined. Preserve the existing omission behavior when either value is undefined.Source: Learnings
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/1-framework/3-tooling/language-server/src/project-artifacts.ts`:
- Around line 47-75: Guard the interpretation call inside createInterpretSlot by
catching exceptions from interpretation.source.interpret and falling back to an
empty diagnostic list. Preserve memoization by assigning the fallback or mapped
diagnostics to memo before returning, matching the existing handling pattern for
external calls.
---
Nitpick comments:
In `@packages/1-framework/3-tooling/language-server/src/server.ts`:
- Around line 258-275: Replace the inline ternary-based conditional spreads for
interpretation and formatter in the createProjectArtifacts options and
ProjectState construction with the repository’s ifDefined helper from
prisma-next/utils/defined. Preserve the existing omission behavior when either
value is undefined.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: c0a89109-b2e3-456b-b08d-34d822f5f967
⛔ Files ignored due to path filters (6)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlprojects/lsp-interpreter-diagnostics/plans/plan.mdis excluded by!projects/**projects/lsp-interpreter-diagnostics/slices/05-lsp-interpret/briefs/d1-r1.mdis excluded by!projects/**projects/lsp-interpreter-diagnostics/slices/05-lsp-interpret/briefs/d2-r1.mdis excluded by!projects/**projects/lsp-interpreter-diagnostics/slices/05-lsp-interpret/spec.mdis excluded by!projects/**projects/lsp-interpreter-diagnostics/trace.jsonlis excluded by!projects/**
📒 Files selected for processing (10)
packages/1-framework/2-authoring/psl-parser/src/interpret.tspackages/1-framework/3-tooling/language-server/package.jsonpackages/1-framework/3-tooling/language-server/src/config-resolution.tspackages/1-framework/3-tooling/language-server/src/diagnostic-mapping.tspackages/1-framework/3-tooling/language-server/src/project-artifacts.tspackages/1-framework/3-tooling/language-server/src/server.tspackages/1-framework/3-tooling/language-server/test/config-resolution.test.tspackages/1-framework/3-tooling/language-server/test/diagnostic-mapping.test.tspackages/1-framework/3-tooling/language-server/test/project-artifacts.test.tspackages/1-framework/3-tooling/language-server/test/server.test.ts
| const { inputs, controlStack, getText, interpretation } = options; | ||
| const documents = new Map<string, DocumentArtifacts>(); | ||
| let symbolTable: SymbolTable | undefined; | ||
|
|
||
| function createInterpretSlot( | ||
| uri: string, | ||
| computed: DocumentDiagnostics, | ||
| ): () => readonly LspDiagnostic[] { | ||
| if (interpretation === undefined) { | ||
| return () => []; | ||
| } | ||
| let memo: readonly LspDiagnostic[] | undefined; | ||
| return () => { | ||
| if (memo === undefined) { | ||
| const result = interpretation.source.interpret( | ||
| { | ||
| document: computed.document, | ||
| sourceFile: computed.sourceFile, | ||
| symbolTable: computed.symbolTable, | ||
| sourceId: uri, | ||
| }, | ||
| interpretation.context, | ||
| ); | ||
| memo = mapInterpreterDiagnostics(result.ok ? [] : result.failure.diagnostics); | ||
| } | ||
| return memo; | ||
| }; | ||
| } | ||
|
|
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n## target file outline\n'
ast-grep outline packages/1-framework/3-tooling/language-server/src/project-artifacts.ts --view expanded || true
printf '\n## relevant symbols/search\n'
rg -n "interpretation\.source\.interpret|formatDocument|completeDocument|publishSafely|buildDocumentDiagnosticReport|on\\(" packages/1-framework/3-tooling/language-server/src -S
printf '\n## target file excerpt\n'
nl -ba packages/1-framework/3-tooling/language-server/src/project-artifacts.ts | sed -n '1,220p'Repository: prisma/prisma-next
Length of output: 5793
🏁 Script executed:
#!/bin/bash
set -euo pipefail
nl -ba packages/1-framework/3-tooling/language-server/src/project-artifacts.ts | sed -n '1,140p'Repository: prisma/prisma-next
Length of output: 196
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Minimal inspection for the specific concern
nl -ba packages/1-framework/3-tooling/language-server/src/project-artifacts.ts | sed -n '35,95p'
rg -n "try \\{|catch \\{|interpretation\\.source\\.interpret|buildDocumentDiagnosticReport|publishSafely|combinedDiagnostics" packages/1-framework/3-tooling/language-server/src -SRepository: prisma/prisma-next
Length of output: 196
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n## project-artifacts excerpt\n'
sed -n '1,140p' packages/1-framework/3-tooling/language-server/src/project-artifacts.ts | cat -n
printf '\n## server excerpt around diagnostics/format/completion\n'
sed -n '120,420p' packages/1-framework/3-tooling/language-server/src/server.ts | cat -nRepository: prisma/prisma-next
Length of output: 18630
Guard interpretation.source.interpret(...) here. A throw will skip memoization and fail diagnostics for this document; wrap it like the other external calls and fall back to an empty diagnostic list.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/1-framework/3-tooling/language-server/src/project-artifacts.ts`
around lines 47 - 75, Guard the interpretation call inside createInterpretSlot
by catching exceptions from interpretation.source.interpret and falling back to
an empty diagnostic list. Preserve memoization by assigning the fallback or
mapped diagnostics to memo before returning, matching the existing handling
pattern for external calls.
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…onfigResolution When a psl-input config source passes hasPslInterpreter, the resolution carries the narrowed provider and a ContractSourceContext assembled by property picks off the single createControlStack call the pipeline-input derivation already makes. ProjectState threads the member; nothing consumes it yet. Configs without the capability resolve exactly as before. language-server gains a direct @prisma-next/config dependency for the context type. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
The payoff path: mapInterpreterDiagnostics inverts rangeToPslSpan (1-based span to 0-based LSP range) and anchors span-less findings at document start instead of dropping them; DocumentArtifacts gains a lazily computed interpret slot memoized per artifacts instance (the existing documentChanged/documentClosed drop is the invalidation), invoking the provider capability as a method with the document URI as sourceId; push and pull share one combined-diagnostics assembly, so interpretation runs only when a diagnostics response is built -- semantic tokens, folding, and completion never pay it. Capability-less configs respond byte-for-byte as before. Dead resolveControlStackInputs wrapper folded; its pins ported to resolveConfigInputs level. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…xture stubs The span-to-range mapping now recovers positions from the span offsets through SourceFile.positionAt -- the literal inverse of how rangeToPslSpan derives them -- which also keeps the framework packages family-vocabulary-clean. Test stubs stop borrowing family names for arbitrary strings. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
3e64561 to
054f9fa
Compare
There was a problem hiding this comment.
♻️ Duplicate comments (1)
packages/1-framework/3-tooling/language-server/src/project-artifacts.ts (1)
60-74: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winGuard
interpretation.source.interpret(...)here.A throw will skip memoization and fail diagnostics for this document; wrap it like the other external calls and fall back to an empty diagnostic list.
🛡️ Proposed fix to add try/catch guard
- const result = interpretation.source.interpret( - { - document: computed.document, - sourceFile: computed.sourceFile, - symbolTable: computed.symbolTable, - sourceId: uri, - }, - interpretation.context, - ); - memo = mapInterpreterDiagnostics( - result.ok ? [] : result.failure.diagnostics, - computed.sourceFile, - ); + try { + const result = interpretation.source.interpret( + { + document: computed.document, + sourceFile: computed.sourceFile, + symbolTable: computed.symbolTable, + sourceId: uri, + }, + interpretation.context, + ); + memo = mapInterpreterDiagnostics( + result.ok ? [] : result.failure.diagnostics, + computed.sourceFile, + ); + } catch { + memo = []; + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/1-framework/3-tooling/language-server/src/project-artifacts.ts` around lines 60 - 74, Wrap the interpretation call in the memoization block of project artifact processing with a try/catch, following the existing pattern for guarding external calls. Preserve normal diagnostic mapping for successful results, and set memo to an empty diagnostic list when interpretation throws so the document remains memoized and processing continues.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@packages/1-framework/3-tooling/language-server/src/project-artifacts.ts`:
- Around line 60-74: Wrap the interpretation call in the memoization block of
project artifact processing with a try/catch, following the existing pattern for
guarding external calls. Preserve normal diagnostic mapping for successful
results, and set memo to an empty diagnostic list when interpretation throws so
the document remains memoized and processing continues.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: 77fb5666-bea6-47c8-bac5-d2652456b6bc
⛔ Files ignored due to path filters (6)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlprojects/lsp-interpreter-diagnostics/plans/plan.mdis excluded by!projects/**projects/lsp-interpreter-diagnostics/slices/05-lsp-interpret/briefs/d1-r1.mdis excluded by!projects/**projects/lsp-interpreter-diagnostics/slices/05-lsp-interpret/briefs/d2-r1.mdis excluded by!projects/**projects/lsp-interpreter-diagnostics/slices/05-lsp-interpret/spec.mdis excluded by!projects/**projects/lsp-interpreter-diagnostics/trace.jsonlis excluded by!projects/**
📒 Files selected for processing (10)
packages/1-framework/2-authoring/psl-parser/src/interpret.tspackages/1-framework/3-tooling/language-server/package.jsonpackages/1-framework/3-tooling/language-server/src/config-resolution.tspackages/1-framework/3-tooling/language-server/src/diagnostic-mapping.tspackages/1-framework/3-tooling/language-server/src/project-artifacts.tspackages/1-framework/3-tooling/language-server/src/server.tspackages/1-framework/3-tooling/language-server/test/config-resolution.test.tspackages/1-framework/3-tooling/language-server/test/diagnostic-mapping.test.tspackages/1-framework/3-tooling/language-server/test/project-artifacts.test.tspackages/1-framework/3-tooling/language-server/test/server.test.ts
🚧 Files skipped from review as they are similar to previous changes (7)
- packages/1-framework/2-authoring/psl-parser/src/interpret.ts
- packages/1-framework/3-tooling/language-server/test/diagnostic-mapping.test.ts
- packages/1-framework/3-tooling/language-server/src/server.ts
- packages/1-framework/3-tooling/language-server/test/project-artifacts.test.ts
- packages/1-framework/3-tooling/language-server/test/server.test.ts
- packages/1-framework/3-tooling/language-server/test/config-resolution.test.ts
- packages/1-framework/3-tooling/language-server/src/config-resolution.ts
…last-good project on reload failure (#974) Slice 06 of the lsp-interpreter-diagnostics project ([TML-2984](https://linear.app/prisma-company/issue/TML-2984), follows #939/#948/#971/#972) — the final build slice: config failures become visible and non-destructive. ## Changes - **Config-failure diagnostics** (`language-server/src/server.ts`): any throw from the project-load flow (`loadConfig`, `createControlStack`, input resolution — opaque runtime errors from executed TypeScript) surfaces as one diagnostic on the **config-file URI** at (0,0)–(0,1) (`PRISMA_NEXT_CONFIG_LOAD_FAILED`, message carries the error text). Published via push **unconditionally** — the config file belongs to the TypeScript language service, so pull never reaches this server for it (tsserver `configFileDiag` precedent). Exactly-once per failed load is structural (the diagnostic publishes from the single shared load promise, guarded by `isCurrentLoad` so superseded loads stay silent). Cleared on the next successful load and when the last document under the config closes. - **Last-good retention**: the loading entry now carries the previous `ProjectState`; a failed *reload* resolves with the retained project — schema documents keep serving their full diagnostics (parse, symbol-table, and interpreter findings through the retained context) with the config diagnostic alongside, instead of today's wipe-everything (rust-analyzer `switch_workspaces` precedent: only switch to a broken workspace if you have none at all). A failed *first* load keeps today's behavior exactly. ## Why Before this slice, breaking your config while editing meant every schema diagnostic silently vanished — the server dropped the project and cleared the markers, leaving no clue why. Now the editor tells you two things at once: the config is broken (marker on the config file itself, position (0,0) since no spans exist into executed TS) and your schema work continues against the last working configuration. One deliberate re-pin: the test that encoded destroy-on-reload was rewritten to encode retention — that behavior change is this slice's entire charter. One residual, by design of the lazy load lifecycle: after a failed *first* load with no schema documents open, the config marker persists until a schema document reopens (no load fires for a config with no open inputs). Self-heals with any document open; exercised in the upcoming playground QA. Project spec + plan live under `projects/lsp-interpreter-diagnostics/` (deleted at project close-out). <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Preserve the previously successful (“last-good”) diagnostics when a configuration reload fails, keeping affected schema documents managed until a successful recovery. * Emit `PRISMA_NEXT_CONFIG_LOAD_FAILED` configuration-load-failure diagnostics exactly once per failing config, with deduplication across concurrent loads and suppression for superseded failures. * Clear configuration-failure diagnostics after a successful reload, and when the last related managed document is closed. * **Tests** * Added/expanded end-to-end coverage for config reload failure/success behavior, diagnostic deduplication, and cleanup/suppression. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…facts Six slices merged (#939, #948, #971, #972, #974); pattern doc landed in docs/architecture docs/patterns/; retro lessons in drive/project and drive/calibration; QA 11/11. History preserves spec, plans, briefs, trace, and QA artifacts up to this commit. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…facts Six slices merged (#939, #948, #971, #972, #974); pattern doc landed in docs/architecture docs/patterns/; retro lessons in drive/project and drive/calibration; QA 11/11. History preserves spec, plans, briefs, trace, and QA artifacts up to this commit. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…facts Six slices merged (#939, #948, #971, #972, #974); pattern doc landed in docs/architecture docs/patterns/; retro lessons in drive/project and drive/calibration; QA 11/11. History preserves spec, plans, briefs, trace, and QA artifacts up to this commit. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…of, retro landings, project deletion (#1012) Close-out PR for the lsp-interpreter-diagnostics project ([TML-2984](https://linear.app/prisma-company/issue/TML-2984)). All six build slices are merged (#939, #948, #971, #972, #974); this PR delivers the remaining project-DoD items and burns the scaffolding. ## Changes - **Pattern doc** (`docs/architecture docs/patterns/capability-intersection.md`, linked from the catalogue): the pattern this project proved — higher-layer capability type + structural attachment at the factory + evidence-based guard at the consumer, as the sanctioned alternative to type erasure across layer frontiers. Reference implementations: the psl-parser capability, both `prismaContract` providers, the LSP consumer. - **QA-driven fix** (`language-server`): the headless QA run against the real server binary caught a Blocker every automated gate missed — config-load failures published `"Unexpected error"` instead of the thrown text, because `config-loader` wraps raw throws in a `CliStructuredError` whose `why` carries the detail and the server read only `.message`. Fixed via the errors package's own shape guard (`error.why ?? error.message`), pinned by a unit test that rejects with a real `CliStructuredError` (the exact seam the mocks had hidden), re-verified headless: 11/11 scenarios pass. - **Retro landings**: `drive/calibration/failure-modes.md` gains F18 (CI-red on an unbriefed repo ratchet + misdiagnosed fix-brief); `drive/project/README.md` gains the single-issue auto-close convention and the PR-boundaries-under-scope-shift rule (fold, don't stack, when a scope addition rewrites an unmerged slice). - **Playground comment** corrected: the language server never invokes `load`, but since the interpret capability it does exercise the pipeline for diagnostics. - **`projects/lsp-interpreter-diagnostics/` deleted** — spec, plans, slice specs, briefs, trace, and QA artifacts are preserved in history up to the deletion commit. ## Why The project's purpose is shipped and proven: interpreter diagnostics (relation resolution, type/codec binding, extension-block semantics) appear live in editors from cached artifacts — lazily, memoized, span-mapped, degrading byte-for-byte for capability-less configs — and config failures surface on the config file with last-good retention instead of silent wipes. The close-out QA run existed precisely to exercise the one un-doubled link (real binary, real provider, real protocol) and it earned its keep by finding the message-quality Blocker fixed here. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved language-server config-load diagnostics by extracting structured failure details for clearer, more relevant error messages. * **Documentation** * Clarified default PostgreSQL configuration behavior for interpreter diagnostics, noting that config loading is not invoked. * Updated contribution guidance on PR boundaries during mid-flight scope shifts, including stacked PR handling. * **Tests** * Expanded language-server config failure tests to cover structured unexpected errors and validate published diagnostic messaging. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Slice 05 of the lsp-interpreter-diagnostics project (TML-2984, follows #939/#948/#971) — the payoff slice: interpreter diagnostics (unresolvable relations, type/codec binding, extension-block semantics) now appear live in the editor instead of only at
contract emit.Changes
config-resolution.ts: when the loaded config has PSL inputs and its contract source passeshasPslInterpreter, the resolution additionally carriesinterpretation: { source, context }— the guarded capability plus a fullContractSourceContextassembled once per config (re)load by property picks off a singlecreateControlStackcall (theextensionContractsmachinery from TML-2984: contract spaces declared in core; ControlStack exposes extension contracts; CLI emit context becomes pure property picks #948). Without the capability the member is absent and every code path behaves exactly as before.diagnostic-mapping.ts: mapsContractSourceDiagnosticline/column spans (1-based, perrangeToPslSpan— the new mapper is its verified inverse) to LSP ranges; span-less diagnostics anchor at document start (0,0→0,1, the tsserver convention) rather than being dropped.project-artifacts.ts: each cached document gains a lazily computed, memoized interpret slot —interpretruns only when a diagnostics response is being assembled, at most once per document version, invalidated by the existingdocumentChanged/documentClosedlifecycle.runPipelineis untouched: semantic tokens, folding, and completion never pay interpretation cost (spy-verified through real request paths).server.ts: onecombinedDiagnosticsassembly (parse + symbol-table + mapped interpreter findings) consumed by both the push channel and the pull handler.Why
The whole project existed for this hop: the LSP already parses and builds symbol tables incrementally from editor buffers, and since #971 the providers can interpret exactly those cached artifacts — no re-parse, no disk read, no forked pipeline (
loadandinterpretshare one path by construction). Laziness is the performance contract: interpretation is pull-time-only and memoized. Degradation is byte-for-byte: configs without the capability (typescript sources, third-party providers) keep today's behavior exactly, deep-equal-verified on both channels. Test doubles are typedPslInterpretCapable['interpret']so the compiler enforces fidelity to the real capability; the real-provider integration is pinned in #971's suites, and the playground QA (final slice) exercises the full chain in a live editor.Project spec + plan live under
projects/lsp-interpreter-diagnostics/(deleted at project close-out).Summary by CodeRabbit
New Features
Bug Fixes
Documentation