import type { ParserRuleContext } from "antlr4ng"; import { TypeInfo } from "../api.js"; import { type LineageHop } from "../lineage/hops.js"; import { type Occurrences } from "../references/references.js"; import type { Dialect } from "../dialect.js"; import type { QueryExpr, PartSpan } from "../ir/ir.js"; import type { StatementCategory } from "../ir/statement.js"; import type { SyntaxDiagnostic } from "../parse-diagnostics.js"; import type { Scope, ScopeTree } from "../scope/scope.js"; import { type Frame } from "../scope/frame.js"; import { type ClauseInfo } from "../scope/clauses.js"; import { type SetOpArms } from "../scope/setop-arms.js"; import type { Qualification, Diagnostic } from "../qualify/qualify.js"; import type { SchemaProvider } from "../qualify/schema-provider.js"; import { type TemplateProvider } from "../qualify/template-provider.js"; import type { Span, Sym } from "../symbols/symbols.js"; import type { Token } from "../token/token.js"; import type { TemplateEngine, TemplatedParseResult, TemplateVariant } from "../template/engine.js"; import { LineIndex } from "./line-index.js"; import { type NodeHit } from "./node-at.js"; import { type StatementCellSpan } from "./split.js"; /** One top-level statement of the document, in DOCUMENT coordinates. The real per-statement surface — * each cell is parsed and scoped independently. `tokens`/`diagnostics` are shifted into doc offsets; * `ast`/`cst`/`scopes` carry cell-relative spans (per-cell position mapping is Task 6). */ export interface StatementCell { /** The cell's [start, end) doc offsets — leading trivia + trailing separator included (tiling). */ readonly span: StatementCellSpan; /** The cell's exact source slice — the content-address key material. */ readonly text: string; /** The statement category from this cell's own lower() — real, never the compound facade. */ readonly category: StatementCategory; /** Per-statement IR (real, not compound-flagged). Spans are cell-relative. */ readonly ast: QueryExpr; /** The per-statement antlr CST root. Spans are cell-relative. */ readonly cst: ParserRuleContext; /** Per-statement scope tree. */ readonly scopes: ScopeTree; /** Tokens with spans in DOC coordinates (shifted from cell-relative). */ readonly tokens: readonly Token[]; /** Syntax-error count for this cell alone. */ readonly errors: number; /** Syntax diagnostics with positions in DOC coordinates (shifted from cell-relative). */ readonly diagnostics: readonly SyntaxDiagnostic[]; } /** The schema-dependent analysis tiers, produced by SqlDocument.analyze(schema). */ export interface DocumentAnalysis { /** The full schema-fed resolution (star expansion + diagnostics). */ qualification: Qualification; /** Per-expression types — `types.typeOf(expr, scope)`. */ types: TypeInfo; /** The kind × modifier symbol model over the scope tree. */ symbols: Sym[]; /** Qualification's semantic diagnostics (unknown table/column/field). */ diagnostics: Diagnostic[]; } /** One coherent arm realization of a templated document. Lazy: nothing parses until doc() or * text() is first touched. A variant IS a document — everything a plain SqlDocument exposes * (ast/tokens/scopes/templated/analyze/cursor members) is available per arm through doc(). */ export interface DocumentVariant { /** The arm this variant activates (undefined = variant 0, all defaults). Mirrors * TemplateVariant.active, including the synthetic-empty marker from Task 1. */ readonly active?: TemplateVariant["active"]; /** The realized text: original text with inactive arms whitespace-blanked — * length- and newline-preserving (spans stay document-true). Memoized. */ text(): string; /** The arm's own SqlDocument: same dialect, SAME engine, SAME provider, SHARING the parent's * content-addressed cell-cache family (an arm whose text an edit didn't change is a cache hit). * Memoized per variant. */ doc(): SqlDocument; } /** One CTE's identity + its output columns unioned across arms (`SqlDocument.unionCtes`). */ export interface UnionCte { /** Fold-normalized, quote-preserving identity form — `foldIdentifier(raw, dialect)`, the same * vocabulary the column entries below (and the rest of the resolved surface) speak. The exact * written form is recoverable by slicing the source at `declarationSpan`. */ name: string; /** Declaration identity: the CTE name's own span (`CteDef.nameCst`, falling back to the whole * `CteDef.cst` when the name itself has no real token). Part of the key: two same-named CTEs * declared at different positions stay distinct entries. */ declarationSpan: PartSpan; /** Output columns unioned by NAME across arms; each column's span is the FIRST LIVE ARM's (arm * iteration order = `SqlDocument.variants` order) — the representative-span rule pinned by the * variant-acceptance brief's A8a-c. Names are fold-normalized like `name` above. */ columns: { name: string; span: Span; }[]; } export declare class SqlDocument { readonly uri?: string; readonly version: number; readonly text: string; readonly dialect: Dialect; /** The per-statement cells — the real surface for statement-scoped work. Use `cellAt(offset)` to * find the cell owning a position. A single-statement document has exactly one cell. */ readonly statements: readonly StatementCell[]; /** Whole-document token stream (concat of the cells' doc-coordinate tokens). Byte-identical to a * single whole-doc parse for a single-cell document. */ readonly tokens: readonly Token[]; /** The whole-document CST root — the escape hatch for precise spans. For a MULTI-cell document * this is the compound facade (the first cell's CST as a placeholder); use `statements`/`cellAt` * for real per-statement spans. */ readonly cst: ParserRuleContext; /** The whole-document IR. For a single-cell document this is the cell's own IR (identical to * today). For a MULTI-cell document it keeps today's compound-flagged shape (`statement: * "compound"`); use `statements`/`cellAt` for the real per-statement IR. */ readonly ast: QueryExpr; /** Total syntax-error count across all cells. */ readonly errors: number; /** Positioned SYNTAX diagnostics (never semantic — those need a schema), concatenated across cells * in doc coordinates. */ readonly diagnostics: readonly SyntaxDiagnostic[]; /** The whole-document scope tree. Single-cell: the cell's own scopes. MULTI-cell: the compound * facade's scopes (`statement: "compound"`); use `statements`/`cellAt` for per-statement scopes. */ readonly scopes: ScopeTree; readonly lines: LineIndex; /** The template artifacts when this document was built with a `templating` engine: * tags/regions/symbols/placeholder/degraded plus tagOf/nodeOf/diagnosticsOf. * Undefined on plain documents. */ readonly templated?: TemplatedParseResult; /** Schema-keyed memo of the MERGED analyze() result (concat + coordinate shift). Rebuilt per doc * version — the merge must redo when an earlier statement's line count changes and shifts later * cells' doc coordinates — while the per-CELL analysis (the expensive qualify/deriveSymbols) is * memoized on the CachedCell and survives edits. Keyed on schema IDENTITY + VERSION (a primed * CallbackSchema bumps its version, invalidating this memo). The Map reference is frozen with the * instance, but its contents stay mutable, so memoization works on a frozen SqlDocument. */ private readonly _analysisCache; /** The CachedCell backing each StatementCell, parallel to `statements`. Holds the cross-edit * per-cell analysis memo; analyze() reads it to merge per-statement results. */ private readonly _cells; /** The content-addressed cross-edit cell cache, carried to withText() children. Its contents stay * mutable (a memo) even though the reference is frozen with the instance. */ private readonly _cellCache; /** The injected template engine + provider, carried to withText() children so an edit keeps * building through the same door its parent used. Undefined on a plain document. */ private readonly _templating?; private readonly _provider?; /** Memo box for the `variants` getter (frozen instance, mutable memo — the `_analysisCache` * precedent: the box reference is frozen with the instance, but its `.value` stays settable). */ private readonly _variantsMemo; /** Schema-keyed memos for the four union views (`unionSymbols`/`unionDiagnostics`/`unionCtes`/ * `unionOutputColumns`) — one WeakMap per view, same identity+version pattern as `_analysisCache`. */ private readonly _unionSymbolsCache; private readonly _unionDiagnosticsCache; private readonly _unionCtesCache; private readonly _unionOutputColumnsCache; private constructor(); /** Build one statement cell for `span`: reuse the cached cell-relative parse if its text is * already known (content addressing), else parse+scope it and cache it; then shift tokens / * diagnostics into document coordinates by the cell's start position. * * `handedOut` dedupes WITHIN one document build: two cells with byte-identical text (e.g. * `SELECT 1;SELECT 1;`) must NOT share one CachedCell — their StatementCells would carry * reference-identical cst/ast/scopes under different spans, and the per-cell semantic passes * (Task 6: references/documentHighlight) walk scope trees by OBJECT IDENTITY, so two positions * resolving through one shared scopes object would cross-contaminate occurrences. On a second * use of the same entry in the same build, that cell is parsed fresh. The fresh product does * NOT replace the cache entry: the first occurrence keeps its stable cross-edit identity (the * common case), and only intra-doc duplicates — rare — pay a re-parse per build. */ private buildCell; /** Build the ONE cell for a TEMPLATED document: span [0, text.length) — the templated build path * bypasses `splitStatements` entirely (one cell, whole text), and its products come from a single * `engine.parse(text, dialect, { provider })` call rather than the plain per-dialect `parse()`. * `r.tokens`/`r.diagnostics` are ALREADY document coordinates (the cell always starts at 0), so — * unlike `buildCell` — nothing is shifted. Mirrors `buildCell`'s CachedCell/StatementCell shapes * so every downstream consumer (analyze(), cellAt(), nodeAt()…) sees the same structure whether * the document is plain or templated. Cached in the SAME cross-edit `_cellCache` as plain cells, * under a prefixed key so a templated cell can never collide with a plain one for the same text. */ private buildTemplatedCell; /** Build a document for `text` in `dialect`. Total: never throws, even on broken / mid-edit input. * Starts a FRESH cell cache — cross-edit reuse comes from withText(), not create(). Pass * `templating` to parse through an injected TemplateEngine (jinja-SQL etc.) instead of the plain * per-dialect parser — absent, this is the exact untouched plain-SQL path (never auto-detected). * `provider` feeds the engine's fills/markers and is ignored without `templating`. */ static create(text: string, dialect: Dialect, opts?: { uri?: string; version?: number; templating?: TemplateEngine; provider?: TemplateProvider; }): SqlDocument; /** An edit: a NEW SqlDocument for the new text. This instance is untouched (immutable). The cell * cache is CARRIED forward, so statements whose text didn't change reuse their parsed cells. The * injected templating engine + provider (if any) ride the instance to this child too. */ withText(text: string, version: number): SqlDocument; /** The statement cell owning `offset` (binary search over the tiling cell spans), or undefined if * there are no cells. An offset at end-of-document resolves to the last cell. */ cellAt(offset: number): StatementCell | undefined; /** The smallest default-channel (channel 0) token whose [start, stop] covers `offset`; if none * covers it, the nearest preceding default-channel token (so a caret at end-of-token or between * tokens still resolves). Hidden-channel trivia is skipped. */ tokenAt(offset: number): Token | undefined; /** The smallest IR Expr whose CST range covers `offset`, with its owning Scope. Cell-aware: the hit * comes from the CELL owning `offset` (with a cell-relative offset), so a node in statement 2 of a * multi-cell document resolves through its own scope tree — NOT the compound facade. The returned * `expr.cst` carries CELL-relative spans; a caller turning it into a document Range shifts it by the * owning cell's start. Single-cell: identical to today. */ nodeAt(offset: number): NodeHit | undefined; /** The declaration + every occurrence of the symbol at `offset` (the references engine, * cell-aware): resolved over the CELL owning the offset — its own scopes/ast, with a * cell-relative offset — then every returned span (occurrences + declaration) shifted from * cell-relative to DOCUMENT coordinates by the cell base. Absorbs the dance the LSP * references/documentHighlight/codeLens features hand-rolled. Single-cell documents: base * 0/0/0, byte-identical to the free referencesAt over doc.scopes. Total: null off-symbol or * with no cells; never throws. */ referencesAt(offset: number, schema?: SchemaProvider): Occurrences | null; /** Widen a per-cell "variable" Occurrences to the whole document: every Sym sharing `occ.symbol`'s * name, across every statement cell, becomes an occurrence (declaration Syms included). Sourced * from `analyze().symbols`, which already carries the cross-cell `definition` links * `buildAnalysis` applies: a re-group over already-linked data, not a fresh resolution pass. */ private escalateVariableOccurrences; /** The per-hop lineage spine anchored at `offset` (cell-aware): resolved over the CELL owning * the offset, with a cell-relative offset. NOTE: on a MULTI-statement document the returned * hop nodes' cst spans are CELL-relative — the spine references the frozen per-cell IR (hops * are references, not copies), so nothing is shifted here; use `cellAt(offset).span.start` as * the base to map them to document coordinates. Single-cell documents (every dbt model) are * identical either way. Total: undefined off-symbol or with no cells; never throws. */ lineageAt(offset: number, schema?: SchemaProvider): LineageHop | undefined; /** The owning frame (the Scope + its `Sym.frame`-matching label) for `offset`, cell-aware, * mirroring `nodeAt`: resolved over the CELL owning the offset, with a cell-relative offset. The * returned `Frame.scope`'s own CST spans stay CELL-relative (same convention as `NodeHit.expr`): * a caller turning it into a document Range shifts by the owning cell's start. Schema-free (frame * identity is structural). Total: undefined off-document / with no cells; never throws. */ frameAt(offset: number): Frame | undefined; /** The ordered clause list for `scope` (typically the `scope` a prior `frameAt` call returned), * shifted to document coordinates. Finds the owning cell by matching `scope`'s tree ROOT against * each cell's own `scopes.root` (a Scope is always reachable from exactly one cell's root): a * `scope` this document didn't produce answers `[]`, never a guess. */ clausesOf(scope: Scope): ClauseInfo[]; /** Set-op arm geometry for `scope` (undefined for a non-setop frame), shifted to document * coordinates the same way `clausesOf` is. */ setOpArmsOf(scope: Scope): SetOpArms | undefined; /** The index into `this._cells`/`this.statements` of the cell whose scope tree ROOT is `scope`'s * own root (walking `scope.parent` up), or -1 when `scope` belongs to no cell of this document. */ private cellIndexOfScope; /** The coherent per-arm variants of a templated document (engine.variants() consumed). `[]` on * plain documents and on templated documents with no control-flow regions. Lazy at every level: * enumeration on first read, realization+parse per variant on first doc()/text(). Memoized on * the instance (frozen instance, mutable memo box — see `_variantsMemo`). */ get variants(): readonly DocumentVariant[]; /** Compute (no memo) the variant list: absent `variants` hook, a plain document, or a templated * document with no control-flow regions all answer `[]`. Otherwise consults the engine, wrapped * defensively (degrade to `[]`, never throw — engine.variants may throw only on engine bugs, the * same posture parseTemplated's own catch uses). */ private buildVariants; /** Wrap one engine-produced TemplateVariant as a DocumentVariant: `text()` delegates straight to * the engine variant's own memoized realization (never re-realized here); `doc()` builds — once, * memoized — the arm's own SqlDocument through the PRIVATE constructor, carrying this document's * `_cellCache` (the exact carry `withText` uses, NOT the public `create`'s fresh cache) plus the * same templating engine + provider, so an unchanged arm across an edit is a cache hit. */ private wrapVariant; /** The variant whose realization has `offset` LIVE (its arm active): an offset inside a * non-default arm routes to that arm's variant; a default-arm or outside-all-regions offset * routes to variant 0. undefined on plain documents and no-region templated documents. * Never throws; out-of-range offsets answer variant 0 (the honest default). */ variantAt(offset: number): DocumentVariant | undefined; /** The schema-dependent tiers, over the cached per-cell scopes/ast (no re-parse). Memoized by * schema IDENTITY + VERSION — a plain Schema (version 0) memoizes exactly as before; a * CallbackSchema that has been prime()d bumps its version and so re-computes with the newly * resolved tables. Every statement cell is qualified/symbol-derived INDEPENDENTLY (a broken or * unknown-column statement never suppresses another), then merged: symbols and semantic * diagnostics from every cell, each shifted from cell-relative to DOCUMENT coordinates. The * expensive per-cell work is memoized on each CachedCell, so an edit that touches only one * statement re-qualifies only that statement; the cheap merge (concat + shift) redoes per version. * With no schema the symbols/scopes still resolve structurally and types come back `unknown` where * a catalog would be needed (the stable OPEN_PROVIDER keeps the memo working). */ analyze(schema?: SchemaProvider): DocumentAnalysis; /** Compute (no memo) the merged document analysis for schema `s`. */ private buildAnalysis; /** The per-cell schema-dependent analysis (cell-relative), memoized on the CachedCell (by schema * identity + version) so it survives edits that don't touch this cell, yet re-runs when a primed * CallbackSchema bumps its version. */ private cellAnalysis; /** Symbols across ALL variants, deduped by span+identity+NAME (`start:end:kind:frame:name` — * NAME is load-bearing: schema-expanded star Syms share one zero-width span by design, see * symbols.ts's emitColumns). Computed over the VARIANT documents only — never the primary * parse, whose all-arms-live SQL can mis-read conflicting arms (e.g. `col_a col_b` reading as * an alias) into junk that must never leak into the union. Equals the plain `analyze(schema)` * symbols when there are no variants (a plain document, or a templated one with no * control-flow regions). Memoized per schema identity+version, like `analyze()`. */ unionSymbols(schema?: SchemaProvider): Sym[]; private buildUnionSymbols; /** Diagnostics across all variants — syntax + semantic, like `session.diagnostics()` — deduped * by position+identity, NOT message text: two arms producing the SAME diagnostic at the SAME * position collapse to one entry, but the same message at TWO DIFFERENT positions stays two * entries (the exact case a message-keyed merge gets wrong). Same variant-only, memoized * semantics as `unionSymbols`. */ unionDiagnostics(schema?: SchemaProvider): (SyntaxDiagnostic | Diagnostic)[]; private buildUnionDiagnostics; /** Per-CTE column unions across all variants, keyed by NAME + declaration span (two same-named * CTEs declared at different positions stay distinct entries; a CTE existing in only one arm * still appears). Columns union by NAME; a column's representative span is the FIRST LIVE ARM's * (arm iteration order = `this.variants` order): the rule the variant-acceptance brief's A8a-c * pin. A CTE whose body is a set operation answers through the qualification (names per SQL * setop semantics, spans from the declaring branch); a PIPE-syntax body answers the derivable * subset, see `scopeOutputColumns`'s pipe doc comment. Falls through to this document's own * (single-arm) answer when there are no variants; there is no pre-existing single-doc * equivalent to delegate to, unlike unionSymbols/unionDiagnostics, so the no-variant case is * just the one-arm instance of the same algorithm. A MULTI-STATEMENT document (no variants: the * templated door always forces exactly one cell, so the two "multi" shapes never overlap) merges * every statement CELL's own CTEs instead, each shifted from cell-relative to DOCUMENT coordinates * (the same shift `analyze()` already applies to symbols/diagnostics for a multi-cell document); * the compound facade itself carries no CTEs, so cells are the real per-statement source. Memoized * like `unionSymbols`. */ unionCtes(schema?: SchemaProvider): UnionCte[]; private buildUnionCtes; /** The document's final-SELECT (root scope) output columns unioned across arms: same NAME * keying, representative-span rule, and multi-statement per-cell merge as `unionCtes` (see its * doc comment). A setop root (the dbt-incremental `… UNION ALL …` arm shape) answers through the * qualification, names per SQL setop semantics (left branch positionally, BY NAME appends * right-only), spans from the declaring branch. A PIPE-syntax root answers the derivable subset, * see `scopeOutputColumns`'s pipe doc comment. Falls through to this document's own root outputs * when there are no variants and only one statement cell. */ unionOutputColumns(schema?: SchemaProvider): { name: string; span: Span; }[]; private buildUnionOutputColumns; /** One "arm" `unionCtes`/`unionOutputColumns` aggregate over, unified across the two shapes that * can each independently make a document "multi" (never both at once: the templated door always * builds exactly one statement cell, see its own comment above): a templated document's real * variants (each a full arm SqlDocument, already in DOCUMENT coordinates, zero shift), or, for a * plain multi-statement document, each statement CELL (cell-relative scopes, shifted to document * coordinates by the cell's start, mirroring `buildAnalysis`'s per-cell shift). A single-cell, * non-templated document is the trivial one-arm case of the same shape (zero shift, this * document's own scopes/qualification). */ private armsData; }