Skip to content

Commit ef47c7f

Browse files
authored
refactor(vnext): share bounded SQL lexical primitives
## Summary - extract dialect lexical profiles and shared UTF-16 identifier, quote, block-comment, and dollar-quote scanners from the statement index - preserve statement-index behavior while adding explicit upper bounds needed by the parser-independent query-site recognizer - make bounded dollar-quote opener/closer classification prefix-deterministic and keep partial close search bounded - add direct boundary regressions for quotes, block comments, dollar quotes, UTF-16 identifiers, and SQL whitespace This is a private prerequisite for ADR 0005 step 3; it adds no public package export. ## Validation - `pnpm run typecheck` (strict and loose optional-property modes) - `pnpm exec oxlint` - `pnpm run test:integrity` - 1,272 Node tests passed + 1 expected failure - 7 Chromium tests passed - packed-package smoke passed - worker-placement/runtime evidence passed - demo build passed - changed coverage: 98.51% statements, 96.75% branches, 100% functions, 98.50% lines - three independent exact-head adversarial approvals at `652b108` ## Review history Adversarial review found a dollar-opener read beyond an explicit scan limit. The final head guards the opener before inspecting a suffix and includes bounded-prefix equivalence coverage. No additional Copilot request will be made after the one request for this PR. <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Shares SQL lexical primitives and adds explicit scan limits to prevent over-reads while keeping statement-index behavior. Also preserves fast, bounded ASCII word scans and fixes dollar-quote opener detection. - **Refactors** - Moved scanners for quotes, block comments, dollar quotes, UTF‑16 identifiers, and SQL whitespace into `src/vnext/lexical.ts` with explicit `limit` handling; preserved fast ASCII word scanning in `statement-index.ts` for performance. - Centralized dialect profiles and helpers; `statement-index.ts` now imports these and drops duplicate logic. - **Bug Fixes** - Dollar-quote opener classification is now prefix-bounded; no suffix reads beyond the scan limit. - Quote and block comment scanners respect limits, including line-break and backslash cases. - Dollar-quote tags over 256 chars return `delimiterTooLong` instead of scanning unbounded. <sup>Written for commit 3600269. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/marimo-team/codemirror-sql/pull/185?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 1ca5751 commit ef47c7f

3 files changed

Lines changed: 474 additions & 330 deletions

File tree

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { describe, expect, it } from "vitest";
2+
import {
3+
isSqlWhitespace,
4+
scanSqlBlockComment,
5+
scanSqlDollarQuote,
6+
scanSqlQuoted,
7+
sqlIdentifierContinueLengthAt,
8+
sqlIdentifierStartLengthAt,
9+
} from "../lexical.js";
10+
11+
describe("shared SQL lexical primitives", () => {
12+
it("uses UTF-16 identifier lengths without splitting valid pairs", () => {
13+
expect(sqlIdentifierStartLengthAt("a", 0)).toBe(1);
14+
expect(sqlIdentifierContinueLengthAt("1", 0)).toBe(1);
15+
expect(sqlIdentifierStartLengthAt("😀", 0)).toBe(2);
16+
expect(sqlIdentifierContinueLengthAt("\uD800", 0)).toBe(1);
17+
expect(sqlIdentifierStartLengthAt("1", 0)).toBe(0);
18+
});
19+
20+
it("recognizes only the SQL whitespace set", () => {
21+
for (const code of [9, 10, 11, 12, 13, 32]) {
22+
expect(isSqlWhitespace(code)).toBe(true);
23+
}
24+
expect(isSqlWhitespace(0xa0)).toBe(false);
25+
});
26+
27+
it("never scans a quote beyond its explicit limit", () => {
28+
expect(scanSqlQuoted("'abc'x", 0, 4, 39, 1, false, true, false)).toEqual({
29+
closed: false,
30+
to: 4,
31+
});
32+
expect(scanSqlQuoted("'abc'x", 0, 5, 39, 1, false, true, false)).toEqual({
33+
closed: true,
34+
to: 5,
35+
});
36+
expect(scanSqlQuoted("'a\nfar away", 0, 4, 39, 1, false, true, true)).toEqual({
37+
closed: false,
38+
to: 4,
39+
});
40+
expect(scanSqlQuoted("'\\\nfar away", 0, 4, 39, 1, true, true, true)).toEqual({
41+
closed: false,
42+
to: 4,
43+
});
44+
});
45+
46+
it("never scans a block comment beyond its explicit limit", () => {
47+
expect(scanSqlBlockComment("/*x*/y", 0, 4, false)).toEqual({
48+
closed: false,
49+
to: 4,
50+
});
51+
expect(scanSqlBlockComment("/*x*/y", 0, 5, false)).toEqual({
52+
closed: true,
53+
to: 5,
54+
});
55+
});
56+
57+
it("never accepts a dollar-quote close beyond its explicit limit", () => {
58+
expect(scanSqlDollarQuote("$$x$$y", 0, 4)).toEqual({
59+
closed: false,
60+
delimiterTooLong: false,
61+
to: 4,
62+
});
63+
expect(scanSqlDollarQuote("$$x$$y", 0, 5)).toEqual({
64+
closed: true,
65+
delimiterTooLong: false,
66+
to: 5,
67+
});
68+
expect(scanSqlDollarQuote("$tag$x$tag$far$tag$", 0, 10)).toEqual({
69+
closed: false,
70+
delimiterTooLong: false,
71+
to: 10,
72+
});
73+
});
74+
75+
it("classifies dollar-quote openers only from the bounded prefix", () => {
76+
for (const text of ["$", "$$", "$x"]) {
77+
expect(scanSqlDollarQuote(text, 0, 1)).toBeNull();
78+
}
79+
});
80+
});

0 commit comments

Comments
 (0)