Skip to content

Commit b0a76da

Browse files
authored
feat(vnext): expose statement boundaries (#201)
## Summary - expose synchronous, revision-stamped `statementBoundaryAt` and `statementBoundariesIntersecting` session queries - preserve exact/no-code/opaque lexical states without leaking the private statement index - add scanner-owned `code` spans that exclude separator trivia and form a strict `hasCode` discriminated union - validate request envelopes as own data properties without invoking host accessors - retain incremental index reuse and shift code spans with exact UTF-16 coordinates - add public type, package smoke, hostile-input, affinity, opacity, masking, intersection, and documentation coverage ## Verification - 43 test files; 2,045 passed and 1 expected failure - changed coverage: 97.92% statements, 96.12% branches, 100% functions, 98.07% lines - all source/test/strict/loose/demo TypeScript configurations pass - oxlint, test integrity, build, and runtime export smoke pass - cold point lookup measured 3.59 ms at 1 MiB and 42.67 ms at the 16 MiB source ceiling; warm viewport intersection measured 0.075–0.096 ms - two independent exact-head adversarial reviews approve `c901544ff41480bb1e7ea2ddcab3af477dc8103f` <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Expose synchronous, revision-stamped statement boundary queries in the vNext session API so editors and runners can locate executable SQL without re-lexing. Adds an exact/opaque boundary model with precise UTF-16 ranges and a `code` span for execution. - **New Features** - Added `session.statementBoundaryAt({ affinity, position })` and `session.statementBoundariesIntersecting({ from, to })`; results are frozen and include the current `revision`. - Exact boundaries expose `extent`, `source`, optional `terminator`, lexical `endState`, and a `code` span that excludes separator trivia; discriminated by `hasCode`. - Opaque boundaries report a `reason` for non-executable regions (procedural blocks, custom delimiters, resource limits). - All ranges map to the original document’s UTF-16 positions and respect masked embedded regions. - Inputs are strictly validated (own data properties only); invalid requests throw `SqlSessionError` with `invalid-statement-boundary-request`. - Exported new types via `@marimo-team/codemirror-sql/vnext`: `SqlStatementBoundary*`, `SqlStatementAffinity`, `SqlStatementLexicalEnd`, and related request/result shapes. <sup>Written for commit c901544. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/marimo-team/codemirror-sql/pull/201?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
1 parent e052af6 commit b0a76da

10 files changed

Lines changed: 838 additions & 12 deletions

File tree

docs/vnext/session-primitives.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,47 @@ CodeMirror consumers should use the separate
1414
See [source coordinates](./source-coordinates.md) for the shared UTF-16 range
1515
contract and the internal immutable source-snapshot model.
1616

17+
Statement boundaries are available as a synchronous structural query:
18+
19+
```ts
20+
const result = session.statementBoundaryAt({
21+
affinity: "left",
22+
position: cursorOffset,
23+
});
24+
25+
if (
26+
session.isCurrent(result.revision) &&
27+
result.boundary.boundaryQuality === "exact" &&
28+
result.boundary.hasCode
29+
) {
30+
executeRange(result.boundary.source);
31+
}
32+
```
33+
34+
The immutable result carries the current session revision. Exact boundaries
35+
expose their full extent, source range, optional terminator, lexical end state,
36+
and a nullable range from the first through last SQL code token. Source ranges
37+
retain attached
38+
whitespace and comments; they are factual lexical boundaries, not pre-trimmed
39+
visual selections. The `code` range excludes leading and trailing separator
40+
trivia so presentation layers do not need to re-lex the document. Opaque
41+
procedural, custom-delimiter, and resource-limited
42+
regions expose only their extent and reason, so consumers cannot mistake them
43+
for safely executable SQL.
44+
45+
Viewport consumers can retrieve every structural boundary in one half-open
46+
range without probing or re-lexing the document:
47+
48+
```ts
49+
const visible = session.statementBoundariesIntersecting({
50+
from: viewport.from,
51+
to: viewport.to,
52+
});
53+
```
54+
55+
The query runs in logarithmic lookup time plus the number of intersecting
56+
boundaries and returns a frozen, revision-stamped array.
57+
1758
## Example
1859

1960
```ts

scripts/package-smoke.mjs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,13 @@ session.update({
207207
baseRevision: session.revision,
208208
document: { kind: "replace", text: "SELECT * FROM {next_df}" },
209209
});
210+
const statement = session.statementBoundaryAt({ affinity: "left", position: 0 });
211+
if (
212+
statement.boundary.boundaryQuality !== "exact" ||
213+
!statement.boundary.hasCode
214+
) {
215+
throw new Error("The packaged vNext statement boundary was unavailable");
216+
}
210217
service.dispose();
211218
`,
212219
);
@@ -229,6 +236,7 @@ import {
229236
duckdbDialect,
230237
type SqlDocumentContext,
231238
type SqlEmbeddedRegion,
239+
type SqlStatementBoundaryAtResult,
232240
type SqlTextRange,
233241
} from "@marimo-team/codemirror-sql/vnext";
234242
import { sqlEditor } from "@marimo-team/codemirror-sql/vnext/codemirror";
@@ -265,12 +273,17 @@ session.update({
265273
baseRevision: session.revision,
266274
document: { kind: "changes", changes: [] },
267275
});
276+
const statement: SqlStatementBoundaryAtResult = session.statementBoundaryAt({
277+
affinity: "left",
278+
position: 0,
279+
});
268280
269281
void extensions;
270282
void editorSupport.extension;
271283
void parser;
272284
void range;
273285
void session;
286+
void statement;
274287
void BigQueryDialect;
275288
void DremioDialect;
276289
void commonKeywords;
@@ -349,6 +362,20 @@ const updatedRevision = session.update({
349362
if (session.isCurrent(originalRevision) || !session.isCurrent(updatedRevision)) {
350363
throw new Error("The packaged vNext session violated revision identity");
351364
}
365+
const statement = session.statementBoundaryAt({ affinity: "left", position: 0 });
366+
const visible = session.statementBoundariesIntersecting({
367+
from: 0,
368+
to: 23,
369+
});
370+
if (
371+
statement.revision !== updatedRevision ||
372+
statement.boundary.boundaryQuality !== "exact" ||
373+
!statement.boundary.hasCode ||
374+
visible.revision !== updatedRevision ||
375+
visible.boundaries.length !== 1
376+
) {
377+
throw new Error("The packaged vNext statement boundary is invalid");
378+
}
352379
service.dispose();
353380
`,
354381
);

0 commit comments

Comments
 (0)