@mnemonik/shared 7.144.2 → 7.145.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/dist/ast/grammars.d.ts +6 -6
  2. package/dist/ast/grammars.d.ts.map +1 -1
  3. package/dist/ast/grammars.js +51 -10
  4. package/dist/ast/grammars.js.map +1 -1
  5. package/dist/sourceRedaction.d.ts.map +1 -1
  6. package/dist/sourceRedaction.js +743 -64
  7. package/dist/sourceRedaction.js.map +1 -1
  8. package/package.json +2 -1
  9. package/queries/clojure.tags.scm +1 -0
  10. package/queries/dart.tags.scm +3 -0
  11. package/queries/erlang.tags.scm +2 -0
  12. package/queries/graphql.tags.scm +3 -0
  13. package/queries/haskell.tags.scm +3 -0
  14. package/queries/julia.tags.scm +1 -0
  15. package/queries/kotlin.tags.scm +3 -0
  16. package/queries/ocaml.tags.scm +2 -0
  17. package/queries/ocaml_interface.tags.scm +1 -0
  18. package/queries/perl.tags.scm +2 -0
  19. package/queries/protobuf.tags.scm +5 -0
  20. package/queries/r.tags.scm +2 -0
  21. package/queries/zig.tags.scm +2 -0
  22. package/scripts/vendor-grammars.ts +551 -35
  23. package/src/ast/grammars.ts +51 -10
  24. package/src/sourceRedaction.ts +765 -12
  25. package/wasm/bash.wasm.gz +0 -0
  26. package/wasm/c-sharp.wasm.gz +0 -0
  27. package/wasm/c.wasm.gz +0 -0
  28. package/wasm/clojure.LICENSE +21 -0
  29. package/wasm/clojure.tags.scm +1 -0
  30. package/wasm/clojure.wasm.gz +0 -0
  31. package/wasm/cmake.wasm.gz +0 -0
  32. package/wasm/cpp.wasm.gz +0 -0
  33. package/wasm/dart.LICENSE +20 -0
  34. package/wasm/dart.tags.scm +3 -0
  35. package/wasm/dart.wasm.gz +0 -0
  36. package/wasm/elixir.wasm.gz +0 -0
  37. package/wasm/erlang.LICENSE +201 -0
  38. package/wasm/erlang.tags.scm +2 -0
  39. package/wasm/erlang.wasm.gz +0 -0
  40. package/wasm/go.wasm.gz +0 -0
  41. package/wasm/graphql.LICENSE +21 -0
  42. package/wasm/graphql.tags.scm +3 -0
  43. package/wasm/graphql.wasm.gz +0 -0
  44. package/wasm/groovy.wasm.gz +0 -0
  45. package/wasm/haskell.LICENSE +21 -0
  46. package/wasm/haskell.tags.scm +3 -0
  47. package/wasm/haskell.wasm.gz +0 -0
  48. package/wasm/java.wasm.gz +0 -0
  49. package/wasm/javascript.wasm.gz +0 -0
  50. package/wasm/julia.LICENSE +21 -0
  51. package/wasm/julia.tags.scm +1 -0
  52. package/wasm/julia.wasm.gz +0 -0
  53. package/wasm/kotlin.LICENSE +9 -0
  54. package/wasm/kotlin.tags.scm +3 -0
  55. package/wasm/kotlin.wasm.gz +0 -0
  56. package/wasm/lua.wasm.gz +0 -0
  57. package/wasm/nix.wasm.gz +0 -0
  58. package/wasm/objcpp.wasm.gz +0 -0
  59. package/wasm/ocaml.LICENSE +21 -0
  60. package/wasm/ocaml.tags.scm +2 -0
  61. package/wasm/ocaml.wasm.gz +0 -0
  62. package/wasm/ocaml_interface.LICENSE +21 -0
  63. package/wasm/ocaml_interface.tags.scm +1 -0
  64. package/wasm/ocaml_interface.wasm.gz +0 -0
  65. package/wasm/perl.LICENSE +7 -0
  66. package/wasm/perl.tags.scm +2 -0
  67. package/wasm/perl.wasm.gz +0 -0
  68. package/wasm/php.wasm.gz +0 -0
  69. package/wasm/powershell.wasm.gz +0 -0
  70. package/wasm/protobuf.LICENSE +21 -0
  71. package/wasm/protobuf.tags.scm +5 -0
  72. package/wasm/protobuf.wasm.gz +0 -0
  73. package/wasm/python.wasm.gz +0 -0
  74. package/wasm/r.LICENSE +9 -0
  75. package/wasm/r.tags.scm +2 -0
  76. package/wasm/r.wasm.gz +0 -0
  77. package/wasm/ruby.wasm.gz +0 -0
  78. package/wasm/rust.wasm.gz +0 -0
  79. package/wasm/scala.wasm.gz +0 -0
  80. package/wasm/solidity.wasm.gz +0 -0
  81. package/wasm/sql.wasm.gz +0 -0
  82. package/wasm/swift.wasm.gz +0 -0
  83. package/wasm/terraform.wasm.gz +0 -0
  84. package/wasm/tsx.wasm.gz +0 -0
  85. package/wasm/typescript.wasm.gz +0 -0
  86. package/wasm/zig.LICENSE +29 -0
  87. package/wasm/zig.tags.scm +2 -0
  88. package/wasm/zig.wasm.gz +0 -0
  89. package/wasm/bash.wasm +0 -0
  90. package/wasm/c-sharp.wasm +0 -0
  91. package/wasm/c.wasm +0 -0
  92. package/wasm/cmake.wasm +0 -0
  93. package/wasm/cpp.wasm +0 -0
  94. package/wasm/elixir.wasm +0 -0
  95. package/wasm/go.wasm +0 -0
  96. package/wasm/groovy.wasm +0 -0
  97. package/wasm/java.wasm +0 -0
  98. package/wasm/javascript.wasm +0 -0
  99. package/wasm/lua.wasm +0 -0
  100. package/wasm/nix.wasm +0 -0
  101. package/wasm/objcpp.wasm +0 -0
  102. package/wasm/php.wasm +0 -0
  103. package/wasm/powershell.wasm +0 -0
  104. package/wasm/python.wasm +0 -0
  105. package/wasm/ruby.wasm +0 -0
  106. package/wasm/rust.wasm +0 -0
  107. package/wasm/scala.wasm +0 -0
  108. package/wasm/solidity.wasm +0 -0
  109. package/wasm/sql.wasm +0 -0
  110. package/wasm/swift.wasm +0 -0
  111. package/wasm/terraform.wasm +0 -0
  112. package/wasm/tsx.wasm +0 -0
  113. package/wasm/typescript.wasm +0 -0
@@ -11,13 +11,13 @@
11
11
  * so there is no peer graph to satisfy, `npm ci` stays fast for every developer
12
12
  * and every CI run, and nobody downloads ~283 MB of grammars to build the repo.
13
13
  *
14
- * What ships to a user: the extracted `.wasm` and `.tags.scm` files, committed
14
+ * What ships to a user: deterministic `.wasm.gz` and `.tags.scm` files, committed
15
15
  * here and published inside @mnemonik/shared. A user's install therefore pulls
16
16
  * no grammar package, runs no postinstall, needs no compiler, and fetches
17
17
  * nothing at runtime - which is the whole reason this plan chose WebAssembly
18
18
  * over the native bindings.
19
19
  *
20
- * Run: npx tsx packages/shared/scripts/vendor-grammars.ts [--check | --only=<id>]
20
+ * Run: npx tsx packages/shared/scripts/vendor-grammars.ts [--check | --only=<id>[,<id>...]]
21
21
  * JSX repair: run --only=javascript and --only=tsx; other assets stay untouched.
22
22
  * --check verify every manifest entry is present and non-empty; write nothing.
23
23
  * This is what CI should run to catch a grammar that silently stopped
@@ -40,6 +40,7 @@ import {
40
40
  import { tmpdir } from 'node:os';
41
41
  import { basename, join } from 'node:path';
42
42
  import { fileURLToPath } from 'node:url';
43
+ import { gzipSync, gunzipSync } from 'node:zlib';
43
44
  import { Language, Parser, Query } from 'web-tree-sitter';
44
45
  import { AST_LANGUAGE_IDS, QUERY_CHAIN, grammarArtifactBasename } from '../src/ast/grammars.js';
45
46
 
@@ -64,6 +65,10 @@ interface GrammarSpec {
64
65
  * maintain when the upstream query is absent or captures unsuitable ranges.
65
66
  */
66
67
  tags: 'upstream' | 'ours' | 'inherited';
68
+ /** Compile committed source, regenerating only obsolete parser ABIs. */
69
+ build?: 'source' | 'regenerate';
70
+ /** Copy the source license alongside new grammar artifacts. */
71
+ licenseFile?: string;
67
72
  }
68
73
 
69
74
  /**
@@ -72,23 +77,103 @@ interface GrammarSpec {
72
77
  * and losing a language is exactly the silent degradation this plan exists to
73
78
  * remove.
74
79
  *
75
- * Absent on purpose:
76
- * julia, haskell - large grammars that ALSO ship no tag query, so each
77
- * costs both its size (6.1/3.7 MB) and a hand-written query. Dropped to
78
- * bound the payload; still indexed via the
79
- * allowlist, still chunked heuristically.
80
- * dart - its bundled .wasm (package v1.0.0, 2023) fails Language.load under
81
- * web-tree-sitter 0.26.11 with a dylink-metadata error: the artifact
82
- * predates the current wasm ABI. Presence is not loadability, which is why
83
- * --check verifies both. Flutter matters, so this is worth revisiting by
84
- * compiling from source with tree-sitter-cli.
85
- * kotlin, perl, zig - bundle no .wasm, so they need a build step
86
- * with tree-sitter-cli. Deferred until the grammars above are proven; they
87
- * keep the heuristic chunker meanwhile.
88
- * markdown - keeps chunkMarkdown. Headers already are a document's semantic
89
- * unit; an AST would not improve on them.
80
+ * Markdown retains its document-header chunker. Svelte component parsing is
81
+ * owned by its adapter; it does not need another tree-sitter WASM.
90
82
  */
91
83
  const GRAMMARS: GrammarSpec[] = [
84
+ // fwcd MIT 0.3.8, e1a2d5ad1f61f5740677183cd4125bb071cd2f30, ABI 14.
85
+ {
86
+ id: 'kotlin',
87
+ pkg: 'tree-sitter-kotlin@0.3.8',
88
+ tags: 'ours',
89
+ build: 'source',
90
+ licenseFile: 'LICENSE',
91
+ },
92
+ // UserNobody14 ISC 1.0.0, 53485a8f301254e19c518aa20c80f1bcf7cf5c62.
93
+ // Rebuild ABI-14 source: its published WASM has obsolete dynamic-link metadata.
94
+ {
95
+ id: 'dart',
96
+ pkg: 'tree-sitter-dart@1.0.0',
97
+ tags: 'ours',
98
+ build: 'source',
99
+ licenseFile: 'LICENSE',
100
+ },
101
+ // WhatsApp Apache-2.0, committed ABI 15. The unscoped npm name is a security holder.
102
+ {
103
+ id: 'erlang',
104
+ pkg: 'https://codeload.github.com/WhatsApp/tree-sitter-erlang/tar.gz/62c82e67b73e13f3ef06e4c8c07ff035a8235d30',
105
+ tags: 'ours',
106
+ build: 'source',
107
+ licenseFile: 'LICENSE',
108
+ },
109
+ // MIT 0.23.1, c30d812bc90827f1a54106a25bc9a6307f5cdcec, packaged ABI 14.
110
+ { id: 'haskell', pkg: 'tree-sitter-haskell@0.23.1', tags: 'ours', licenseFile: 'LICENSE' },
111
+ // MIT 0.24.2, 0cc270ff90ca09c29d0f2f9dec69ddfef55a3eff, packaged ABI 14.
112
+ {
113
+ id: 'ocaml',
114
+ pkg: 'tree-sitter-ocaml@0.24.2',
115
+ wasmFile: 'tree-sitter-ocaml.wasm',
116
+ tags: 'ours',
117
+ licenseFile: 'LICENSE',
118
+ },
119
+ {
120
+ id: 'ocaml_interface',
121
+ pkg: 'tree-sitter-ocaml@0.24.2',
122
+ wasmFile: 'tree-sitter-ocaml_interface.wasm',
123
+ tags: 'ours',
124
+ licenseFile: 'LICENSE',
125
+ },
126
+ // oakmac MIT 0.4.0, 290a67a3368be3847b1dd15327bd3a63f2f671ad; regenerate ABI 9.
127
+ {
128
+ id: 'clojure',
129
+ pkg: 'tree-sitter-clojure@0.4.0',
130
+ tags: 'ours',
131
+ build: 'regenerate',
132
+ licenseFile: 'LICENSE.md',
133
+ },
134
+ // MIT 0.23.1, a8e1262997d5a45520a06cbe1b86c0737d507054, packaged ABI 14.
135
+ { id: 'julia', pkg: 'tree-sitter-julia@0.23.1', tags: 'ours', licenseFile: 'LICENSE' },
136
+ // MIT 2.0.0, 50904961d6a87c5191e611276aa2ecb9d66ca4ff, committed ABI 15.
137
+ {
138
+ id: 'perl',
139
+ pkg: 'tree-sitter-perl@2.0.0',
140
+ tags: 'ours',
141
+ build: 'source',
142
+ licenseFile: 'LICENSE',
143
+ },
144
+ // r-lib MIT 1.3.0, committed ABI 14. The unscoped npm name is a security holder.
145
+ {
146
+ id: 'r',
147
+ pkg: 'https://codeload.github.com/r-lib/tree-sitter-r/tar.gz/58a22794466c0fc15b0d3b40531db751593721e8',
148
+ tags: 'ours',
149
+ build: 'source',
150
+ licenseFile: 'LICENSE',
151
+ },
152
+ // GrayJack BSD-3-Clause 0.2.0, fb8e54448f19f5d78d7cdc664b35bffa8ec48876; regenerate ABI 10.
153
+ {
154
+ id: 'zig',
155
+ pkg: 'tree-sitter-zig@0.2.0',
156
+ tags: 'ours',
157
+ build: 'regenerate',
158
+ licenseFile: 'LICENSE',
159
+ },
160
+ // treywood MIT 0.1.0, committed ABI 14; verified upstream source and license.
161
+ {
162
+ id: 'protobuf',
163
+ pkg: 'https://codeload.github.com/treywood/tree-sitter-proto/tar.gz/e9f6b43f6844bd2189b50a422d4e2094313f6aa3',
164
+ wasmFile: 'tree-sitter-proto.wasm',
165
+ tags: 'ours',
166
+ build: 'source',
167
+ licenseFile: 'LICENSE',
168
+ },
169
+ // bkegley MIT 0.0.1, committed ABI 13; avoid the unrelated ISC npm package.
170
+ {
171
+ id: 'graphql',
172
+ pkg: 'https://codeload.github.com/bkegley/tree-sitter-graphql/tar.gz/5e66e961eee421786bdda8495ed1db045e06b5fe',
173
+ tags: 'ours',
174
+ build: 'regenerate',
175
+ licenseFile: 'LICENSE',
176
+ },
92
177
  {
93
178
  // Uy Ha 0.7.5, MIT; build the committed ABI-14 source without native dependencies.
94
179
  id: 'cmake',
@@ -123,7 +208,7 @@ const GRAMMARS: GrammarSpec[] = [
123
208
  // MIT 0.23.4, gitHead f41e1a044c8a84ea9fa8577fdd2eab92ec96de02.
124
209
  { id: 'cpp', pkg: 'tree-sitter-cpp@0.23.4', tags: 'upstream' },
125
210
  {
126
- // MIT Objective-C++ 0.1.0; committed ABI-15 parser, not the pure Objective-C npm grammar.
211
+ // MIT Objective-C++ 0.1.0; pinned source, not the pure Objective-C npm grammar.
127
212
  id: 'objcpp',
128
213
  pkg: 'https://tangled.org/dunkirk.sh/dots',
129
214
  wasmFile: 'tree-sitter-objcpp.wasm',
@@ -215,7 +300,7 @@ async function checkOnly(): Promise<Problem[]> {
215
300
  await Parser.init();
216
301
 
217
302
  for (const spec of GRAMMARS) {
218
- const wasm = join(WASM_DIR, `${spec.id}.wasm`);
303
+ const wasm = join(WASM_DIR, `${spec.id}.wasm.gz`);
219
304
  if (!existsSync(wasm) || statSync(wasm).size === 0) {
220
305
  problems.push({ id: spec.id, detail: `missing or empty ${spec.id}.wasm` });
221
306
  continue;
@@ -223,7 +308,7 @@ async function checkOnly(): Promise<Problem[]> {
223
308
 
224
309
  let language: Language;
225
310
  try {
226
- language = await Language.load(wasm);
311
+ language = await Language.load(gunzipSync(readFileSync(wasm)));
227
312
  } catch (err) {
228
313
  problems.push({
229
314
  id: spec.id,
@@ -277,9 +362,9 @@ async function checkOnly(): Promise<Problem[]> {
277
362
  }
278
363
 
279
364
  /**
280
- * 0.23.2's published WASMs and generated C share these two grammar defects.
281
- * Keep `unique` contextual, and let GLR distinguish typeof-import type queries
282
- * from runtime calls instead of discarding the type reading on static precedence.
365
+ * Repair demonstrated syntax omissions in pinned 0.23.2 without changing source positions.
366
+ * Keep contextual names and type-only forms distinct from runtime calls; retain
367
+ * expression ambiguity through explicit GLR conflicts instead of guessing.
283
368
  * Source: https://github.com/tree-sitter/tree-sitter-typescript/tree/v0.23.2
284
369
  * MIT; CLI 0.24.4 uses emscripten/emsdk:3.1.64 for the WASM build.
285
370
  */
@@ -289,12 +374,40 @@ function rebuildTypescript(dir: string, work: string, only?: string): void {
289
374
  for (const [before, after] of [
290
375
  [
291
376
  ' _reserved_identifier: (_, previous) => choice(',
292
- " _reserved_identifier: (_, previous) => choice(\n 'unique',",
377
+ " _reserved_identifier: (_, previous) => choice(\n 'unique',\n 'as',",
293
378
  ],
294
379
  [' [$.call_expression, $._type_query_call_expression],\n', ''],
295
380
  [
296
381
  ' conflicts: ($, previous) => previous.concat([',
297
- ' conflicts: ($, previous) => previous.concat([\n [$.call_expression, $._type_query_call_expression],',
382
+ ' conflicts: ($, previous) => previous.concat([\n [$.call_expression, $._type_query_call_expression],\n [$.primary_expression, $.arrow_function, $.method_definition],',
383
+ ],
384
+ [
385
+ " field('type_arguments', optional($.type_arguments)),\n field('arguments', $.arguments),",
386
+ " field('type_arguments', optional($.type_arguments)),\n field('arguments', choice($.arguments, $.template_string)),",
387
+ ],
388
+ [
389
+ " type_parameter: $ => seq(\n optional('const'),\n field('name', $._type_identifier),",
390
+ " type_parameter: $ => seq(\n optional(choice('const', 'in', 'out', seq('in', 'out'))),\n field('name', choice($._type_identifier, alias('out', $.type_identifier))),",
391
+ ],
392
+ [
393
+ ' $.export_clause,\n optional($._from_clause),',
394
+ " choice(\n seq($.export_clause, optional($._from_clause)),\n seq(choice('*', $.namespace_export), $._from_clause),\n ),",
395
+ ],
396
+ [
397
+ " choice(',', $._semicolon),\n choice(",
398
+ " choice(',', $._semicolon, $._function_signature_automatic_semicolon),\n choice(",
399
+ ],
400
+ [
401
+ " mapped_type_clause: $ => seq(\n field('name', $._type_identifier),",
402
+ " mapped_type_clause: $ => seq(\n field('name', choice($._type_identifier, alias('type', $.type_identifier))),",
403
+ ],
404
+ [
405
+ ' alias($._template_chars, $.string_fragment),\n $.template_type,',
406
+ ' alias($._template_chars, $.string_fragment),\n $.escape_sequence,\n $.template_type,',
407
+ ],
408
+ [
409
+ " field('name', choice($.identifier, $._destructuring_pattern)),",
410
+ " field('name', choice($.identifier, alias('as', $.identifier), $._destructuring_pattern)),",
298
411
  ],
299
412
  ] as const) {
300
413
  if (!grammar.includes(before)) throw new Error('TypeScript grammar patch no longer applies');
@@ -329,6 +442,31 @@ function rebuildTypescript(dir: string, work: string, only?: string): void {
329
442
  ),`
330
443
  );
331
444
  writeFileSync(grammarPath, grammar);
445
+ const scannerPath = join(dir, 'common', 'scanner.h');
446
+ let scanner = readFileSync(scannerPath, 'utf8');
447
+ const before = " case '<':\n";
448
+ if (!scanner.includes(before))
449
+ throw new Error('TypeScript generic-overload ASI patch no longer applies');
450
+ scanner = scanner
451
+ .replace(before, '')
452
+ .replace(
453
+ " case '{':",
454
+ " case '<':\n // Only type-member/signature boundaries may precede a new generic overload.\n return valid_symbols[FUNCTION_SIGNATURE_AUTOMATIC_SEMICOLON] && !valid_symbols[LOGICAL_OR];\n\n case '{':"
455
+ );
456
+ const inBoundary =
457
+ ' if (!iswalpha(lexer->lookahead)) {\n return false;\n }';
458
+ if (!scanner.includes(inBoundary))
459
+ throw new Error('TypeScript in-property ASI patch no longer applies');
460
+ scanner = scanner.replace(
461
+ inBoundary,
462
+ ' if (!iswalpha(lexer->lookahead)) {\n return !valid_symbols[LOGICAL_OR];\n }'
463
+ );
464
+ const optionalTuple =
465
+ "lexer->lookahead == ':' || lexer->lookahead == ')' || lexer->lookahead == ','";
466
+ if (!scanner.includes(optionalTuple))
467
+ throw new Error('TypeScript optional-tuple patch no longer applies');
468
+ scanner = scanner.replace(optionalTuple, optionalTuple + " || lexer->lookahead == ']'");
469
+ writeFileSync(scannerPath, scanner);
332
470
  // Retain our type/enum declaration captures when the vendor loop copies the query.
333
471
  copyFileSync(join(WASM_DIR, 'typescript.tags.scm'), join(dir, 'queries', 'tags.scm'));
334
472
  const tools = join(work, 'typescript-build');
@@ -344,7 +482,7 @@ function rebuildTypescript(dir: string, work: string, only?: string): void {
344
482
  ]);
345
483
  const cli = join(tools, 'node_modules', '.bin', 'tree-sitter');
346
484
  for (const dialect of ['typescript', 'tsx']) {
347
- if (only && only !== dialect) continue;
485
+ if (only && !only.split(',').includes(dialect)) continue;
348
486
  // Apply the JSX scanner and attribute repairs only to TSX.
349
487
  if (dialect === 'tsx') {
350
488
  patchJsxScanner(join(dir, 'common', 'scanner.h'));
@@ -513,6 +651,18 @@ function vendorObjcpp(work: string, repository: string): string {
513
651
  ]);
514
652
  git(['checkout', '--quiet', '--detach', 'FETCH_HEAD']);
515
653
  const dir = join(root, 'packages', 'tree-sitter-objcpp');
654
+ const grammarPath = join(dir, 'grammar.js');
655
+ let grammar = readFileSync(grammarPath, 'utf8');
656
+ const before = ' _objc_implementation_item: $ => choice(\n';
657
+ if (!grammar.includes(before))
658
+ throw new Error('Objective-C++ macro-body patch no longer applies');
659
+ grammar = grammar.replace(
660
+ before,
661
+ " objc_macro_body: $ => seq($.objc_macro_invocation, field('body', $.compound_statement)),\n\n" +
662
+ before +
663
+ ' $.objc_macro_body,\n'
664
+ );
665
+ writeFileSync(grammarPath, grammar);
516
666
  const tools = join(work, 'objcpp-build');
517
667
  execFileSync('npm', [
518
668
  'install',
@@ -522,10 +672,17 @@ function vendorObjcpp(work: string, repository: string): string {
522
672
  '--no-fund',
523
673
  '--no-package-lock',
524
674
  'tree-sitter-cli@0.24.4',
675
+ // Match the pinned source's lockfile; these are grammar sources, never runtime dependencies.
676
+ 'tree-sitter-c@0.23.6',
677
+ 'tree-sitter-cpp@0.23.4',
525
678
  ]);
526
- // Upstream generated this ABI-15 parser with CLI 0.25.10; 0.24.4 builds it with emsdk 3.1.64.
679
+ const cli = join(tools, 'node_modules', '.bin', 'tree-sitter');
680
+ execFileSync(cli, ['generate'], {
681
+ cwd: dir,
682
+ env: { ...process.env, NODE_PATH: join(tools, 'node_modules') },
683
+ });
527
684
  execFileSync(
528
- join(tools, 'node_modules', '.bin', 'tree-sitter'),
685
+ cli,
529
686
  ['build', '--wasm', '--docker', '--output', join(dir, 'tree-sitter-objcpp.wasm')],
530
687
  { cwd: dir }
531
688
  );
@@ -611,12 +768,35 @@ function rebuildSql(dir: string, work: string): void {
611
768
  $.keyword_create, make_keyword("policy"), field('name', $.identifier),
612
769
  $.keyword_on, alias($.policy_relation, $.relation),
613
770
  optional(seq($.keyword_for, choice($.keyword_select, $.keyword_insert, $.keyword_update, $.keyword_delete))),
614
- optional(seq($.keyword_to, $.identifier)),
771
+ optional(seq($.keyword_to, comma_list($.identifier, true))),
615
772
  optional(seq($.keyword_using, wrapped_in_parenthesis($._expression))),
616
773
  optional(seq($.keyword_with, $.keyword_check, wrapped_in_parenthesis($._expression))),
617
774
  ),
618
775
  create_table: $ => prec.left(`
619
776
  );
777
+ // These stored migrations use qualified enum names and table/sequence grants.
778
+ patch(
779
+ ` alter_type: $ => seq(
780
+ $.keyword_alter,
781
+ $.keyword_type,
782
+ $.identifier,`,
783
+ ` alter_type: $ => seq(
784
+ $.keyword_alter,
785
+ $.keyword_type,
786
+ $.object_reference,`
787
+ );
788
+ patch(' $.reset_statement,', ' $.reset_statement,\n $.grant_statement,');
789
+ patch(
790
+ ' _cte: $ => seq(',
791
+ ` grant_statement: $ => seq(
792
+ make_keyword("grant"),
793
+ comma_list(choice($.keyword_select, $.keyword_insert, $.keyword_update, $.keyword_delete, make_keyword("usage")), true),
794
+ $.keyword_on, optional($.keyword_sequence), comma_list($.object_reference, true),
795
+ $.keyword_to, comma_list($.identifier, true),
796
+ ),
797
+
798
+ _cte: $ => seq(`
799
+ );
620
800
  // Dollar-quoted procedural code is one literal, never a parsed SQL statement list.
621
801
  patch(' $.reset_statement,', ' $.reset_statement,\n $.do_statement,');
622
802
  patch(
@@ -639,6 +819,240 @@ function rebuildSql(dir: string, work: string): void {
639
819
  ' conflicts: $ => [',
640
820
  ' conflicts: $ => [\n [$._expression, $._inner_default_expression],\n [$.create_function],'
641
821
  );
822
+ // Fixed R2 cohort: change individual upstream rules, retaining the scanner and AST names.
823
+ const patchRule = (name: string, before: string, after: string): void => {
824
+ const start = grammar.indexOf(` ${name}:`);
825
+ if (start < 0) throw new Error(`SQL rule missing: ${name}`);
826
+ const next = grammar.slice(start + 1).search(/^ [a-zA-Z_]\w*\s*:/m);
827
+ const end = next < 0 ? grammar.length : start + 1 + next;
828
+ const rule = grammar.slice(start, end);
829
+ if (!rule.includes(before)) throw new Error(`SQL ${name} patch no longer applies`);
830
+ grammar = grammar.slice(0, start) + rule.replace(before, after) + grammar.slice(end);
831
+ };
832
+ patchRule('_type', 'prec.left(', 'prec.right(');
833
+ patchRule('array_size_definition', 'prec.left(', 'prec.right(');
834
+ patchRule(
835
+ '_type',
836
+ '$.keyword_geography,',
837
+ `seq($.keyword_geography, optional(wrapped_in_parenthesis(
838
+ seq($.identifier, optional(seq(',', $._natural_number)))
839
+ ))),`
840
+ );
841
+ patchRule(
842
+ '_column_constraint',
843
+ '$.keyword_auto_increment,',
844
+ `choice($.keyword_auto_increment, make_keyword("autoincrement")),`
845
+ );
846
+ patchRule('function_argument', '$.literal,', '$._expression,');
847
+ patchRule(
848
+ '_inner_default_expression',
849
+ '$.keyword_current_timestamp,',
850
+ `$.keyword_current_timestamp,
851
+ make_keyword("current_date"),`
852
+ );
853
+ patchRule('create_policy', '$.keyword_delete))),', '$.keyword_delete, $.keyword_all))),');
854
+ patchRule(
855
+ '_create_statement',
856
+ '$.create_function,',
857
+ 'alias($.create_procedure, $.create_function),\n $.create_function,'
858
+ );
859
+ patchRule(
860
+ '_drop_statement',
861
+ '$.drop_function,',
862
+ '$.drop_function,\n $.drop_procedure,\n $.drop_policy,'
863
+ );
864
+ patchRule('_alter_statement', '$.alter_type,', '$.alter_type,\n $.alter_policy,');
865
+ patchRule(
866
+ 'drop_function',
867
+ '$.object_reference,',
868
+ '$.object_reference,\n optional($.function_arguments),'
869
+ );
870
+ patchRule('drop_index', 'field("name", $.identifier)', 'field("name", $.object_reference)');
871
+ patchRule(
872
+ 'alter_column',
873
+ "field('type', $._type),",
874
+ "field('type', $._type),\n optional(seq($.keyword_using, $._expression)),"
875
+ );
876
+ patchRule(
877
+ 'add_constraint',
878
+ '$.constraint,',
879
+ '$.constraint,\n optional(seq($.keyword_not, make_keyword("valid"))),'
880
+ );
881
+ patchRule(
882
+ '_alter_specifications',
883
+ '$.set_schema,',
884
+ `$.set_schema,
885
+ seq(make_keyword("validate"), $.keyword_constraint, $.identifier),
886
+ seq($.keyword_rename, $.keyword_constraint, $.identifier, $.keyword_to, $.identifier),
887
+ seq($.keyword_set, paren_list(seq($.identifier, '=', $.literal), true)),
888
+ seq($.keyword_reset, paren_list($.identifier, true)),`
889
+ );
890
+ patchRule(
891
+ '_table_settings',
892
+ '$.table_partition,',
893
+ `seq($.keyword_on, $.keyword_commit, $.keyword_drop),
894
+ $.table_partition,`
895
+ );
896
+ patchRule(
897
+ 'create_index',
898
+ '$.index_fields',
899
+ `$.index_fields,
900
+ optional(seq(make_keyword("include"), paren_list($.identifier, true)))`
901
+ );
902
+ patchRule(
903
+ 'transaction',
904
+ " optional(';'),",
905
+ ` optional(choice(
906
+ seq($.keyword_read, $.keyword_only),
907
+ seq($.keyword_isolation, $.keyword_level, $.keyword_repeatable, $.keyword_read)
908
+ )),
909
+ optional(';'),`
910
+ );
911
+ patchRule(
912
+ 'limit',
913
+ '$.literal,',
914
+ 'choice($.literal, $.parameter, alias($._qualified_field, $.field)),'
915
+ );
916
+ patchRule(
917
+ 'offset',
918
+ '$.literal,',
919
+ 'choice($.literal, $.parameter, alias($._qualified_field, $.field)),'
920
+ );
921
+ patchRule(
922
+ 'statement',
923
+ ` optional($.keyword_analyze),
924
+ optional($.keyword_verbose),`,
925
+ ` choice(
926
+ seq(optional($.keyword_analyze), optional($.keyword_verbose)),
927
+ paren_list(seq(choice($.identifier, $.keyword_analyze, $.keyword_verbose),
928
+ optional(choice($.keyword_on, $.keyword_off, $.keyword_true, $.keyword_false))), true)
929
+ ),`
930
+ );
931
+ patchRule(
932
+ '_ddl_statement',
933
+ '$.reset_statement,',
934
+ `$.reset_statement,
935
+ $.revoke_statement,
936
+ $.alter_default_privileges,
937
+ $.analyze_statement,
938
+ $.lock_statement,
939
+ $.prepare_statement,
940
+ $.execute_statement,
941
+ $.call_statement,`
942
+ );
943
+ patchRule(
944
+ '_delete_from',
945
+ '$.object_reference,',
946
+ `$.object_reference,
947
+ optional($._alias),
948
+ optional(seq($.keyword_using, comma_list($.relation, true))),`
949
+ );
950
+ patchRule(
951
+ 'cte',
952
+ 'choice($._dml_read, $._dml_write)',
953
+ 'choice($._dml_read, $._dml_write, $.values)'
954
+ );
955
+ patchRule(
956
+ '_on_conflict',
957
+ 'optional($._column_list),',
958
+ 'optional(seq(paren_list($._expression, true), optional($.where))),'
959
+ );
960
+ patchRule('from', `comma_list($.relation, true),`, '$.relation,');
961
+ patchRule(
962
+ 'from',
963
+ ` $.join,`,
964
+ ` seq(',', $.relation),
965
+ $.join,`
966
+ );
967
+ patchRule('from', ' optional($.order_by),\n optional($.limit),', '');
968
+ patchRule(
969
+ '_select_statement',
970
+ ' optional($.from),',
971
+ ` optional($.from),
972
+ optional($.order_by),
973
+ optional($.limit),`
974
+ );
975
+ patchRule(
976
+ 'set_operation',
977
+ ` ),\n ),`,
978
+ ` ),
979
+ optional($.order_by),
980
+ optional($.limit),
981
+ ),`
982
+ );
983
+ patchRule('op_other', " '<->',", " '<->',\n '<=>',");
984
+ patchRule('_expression', '$.interval,', '$.interval,\n $.at_time_zone,');
985
+ const support = ' $.function_support,\n';
986
+ if (grammar.split(support).length !== 3) throw new Error('SQL function options changed');
987
+ grammar = grammar.replaceAll(support, support + ' $.function_configuration,\n');
988
+ patchRule(
989
+ 'grant_statement',
990
+ `comma_list(choice($.keyword_select, $.keyword_insert, $.keyword_update, $.keyword_delete, make_keyword("usage")), true),
991
+ $.keyword_on, optional($.keyword_sequence), comma_list($.object_reference, true),`,
992
+ `$._privilege_list,
993
+ $.keyword_on, $._privilege_target,`
994
+ );
995
+ patch(
996
+ ' _cte: $ => seq(',
997
+ ` _privilege_list: $ => comma_list(choice(
998
+ $.keyword_select, $.keyword_insert, $.keyword_update, $.keyword_delete,
999
+ make_keyword("usage"), make_keyword("execute"), make_keyword("truncate"),
1000
+ seq($.keyword_all, optional(make_keyword("privileges")))
1001
+ ), true),
1002
+ routine_signature: $ => seq($.object_reference, optional($.function_arguments)),
1003
+ _privilege_target: $ => choice(
1004
+ seq($.keyword_function, comma_list($.routine_signature, true)),
1005
+ seq($.keyword_schema, comma_list($.identifier, true)),
1006
+ seq($.keyword_all, choice(make_keyword("tables"), make_keyword("sequences"), make_keyword("functions")),
1007
+ $.keyword_in, $.keyword_schema, comma_list($.identifier, true)),
1008
+ seq(optional(choice($.keyword_table, $.keyword_sequence)), comma_list($.object_reference, true))
1009
+ ),
1010
+ revoke_statement: $ => seq(make_keyword("revoke"), $._privilege_list, $.keyword_on,
1011
+ $._privilege_target, $.keyword_from, comma_list($.identifier, true)),
1012
+ alter_default_privileges: $ => seq(
1013
+ $.keyword_alter, $.keyword_default, make_keyword("privileges"),
1014
+ optional(seq($.keyword_for, $.keyword_role, comma_list($.identifier, true))),
1015
+ optional(seq($.keyword_in, $.keyword_schema, comma_list($.identifier, true))),
1016
+ choice(
1017
+ seq(make_keyword("grant"), $._privilege_list, $.keyword_on,
1018
+ choice(make_keyword("tables"), make_keyword("sequences"), make_keyword("functions")),
1019
+ $.keyword_to, comma_list($.identifier, true)),
1020
+ seq(make_keyword("revoke"), $._privilege_list, $.keyword_on,
1021
+ choice(make_keyword("tables"), make_keyword("sequences"), make_keyword("functions")),
1022
+ $.keyword_from, comma_list($.identifier, true))
1023
+ )
1024
+ ),
1025
+ drop_policy: $ => seq($.keyword_drop, make_keyword("policy"), optional($._if_exists),
1026
+ $.identifier, $.keyword_on, alias($.policy_relation, $.relation)),
1027
+ alter_policy: $ => seq($.keyword_alter, make_keyword("policy"), $.identifier,
1028
+ $.keyword_on, alias($.policy_relation, $.relation), $.keyword_rename, $.keyword_to, $.identifier),
1029
+ function_configuration: $ => seq($.keyword_set, $.object_reference,
1030
+ choice(seq($.keyword_from, make_keyword("current")),
1031
+ seq(choice('=', $.keyword_to), comma_list(choice($.literal, $.identifier), true)))),
1032
+ _procedure_option: $ => choice($.function_security, $.function_configuration),
1033
+ create_procedure: $ => seq(
1034
+ $.keyword_create, optional($._or_replace), $.keyword_procedure,
1035
+ $.object_reference, $.function_arguments, repeat($._procedure_option),
1036
+ choice(
1037
+ seq($.function_language, repeat($._procedure_option), alias($.opaque_function_body, $.function_body)),
1038
+ seq(alias($.opaque_function_body, $.function_body), repeat($._procedure_option), $.function_language)
1039
+ ), repeat($._procedure_option)
1040
+ ),
1041
+ drop_procedure: $ => seq($.keyword_drop, $.keyword_procedure, optional($._if_exists),
1042
+ $.routine_signature, optional($._drop_behavior)),
1043
+ call_statement: $ => seq(make_keyword("call"), $.invocation),
1044
+ analyze_statement: $ => seq($.keyword_analyze, $.object_reference, optional(paren_list($.identifier, true))),
1045
+ lock_statement: $ => seq(make_keyword("lock"), $.keyword_table, comma_list($.object_reference, true),
1046
+ $.keyword_in, choice(seq(make_keyword("share"), $.keyword_row, make_keyword("exclusive")),
1047
+ seq(make_keyword("access"), make_keyword("exclusive"))), make_keyword("mode")),
1048
+ prepare_statement: $ => seq(make_keyword("prepare"), $.identifier,
1049
+ optional(paren_list($._type, true)), $.keyword_as, choice($._dml_read, $._dml_write)),
1050
+ execute_statement: $ => seq(make_keyword("execute"), $.identifier, optional(paren_list($._expression))),
1051
+ at_time_zone: $ => prec.left('binary_times', seq($._expression,
1052
+ make_keyword("at"), make_keyword("time"), make_keyword("zone"), $._expression)),
1053
+
1054
+ _cte: $ => seq(`
1055
+ );
642
1056
  const functionStart = grammar.indexOf(' create_function: $ => seq(');
643
1057
  const functionEnd = grammar.indexOf(' _function_return:', functionStart);
644
1058
  if (functionStart < 0 || functionEnd < 0) throw new Error('SQL function patch no longer applies');
@@ -952,9 +1366,95 @@ function rebuildBash(dir: string, work: string): void {
952
1366
  );
953
1367
  }
954
1368
 
1369
+ /** Reuse the existing offline WASM toolchain; never install native grammar bindings. */
1370
+ function buildPrerequisite(dir: string, work: string, spec: GrammarSpec): void {
1371
+ const config = join(dir, 'tree-sitter.json');
1372
+ const buildRepositories: Record<string, string> = {
1373
+ clojure: 'https://github.com/oakmac/tree-sitter-clojure',
1374
+ zig: 'https://github.com/GrayJack/tree-sitter-zig',
1375
+ graphql: 'https://github.com/bkegley/tree-sitter-graphql',
1376
+ };
1377
+ if (!existsSync(config) && buildRepositories[spec.id]) {
1378
+ // Old packages omit the build metadata required by the current WASM builder.
1379
+ writeFileSync(
1380
+ config,
1381
+ JSON.stringify({
1382
+ grammars: [
1383
+ {
1384
+ name: spec.id === 'protobuf' ? 'proto' : spec.id,
1385
+ scope: `source.${spec.id}`,
1386
+ path: '.',
1387
+ 'file-types': [],
1388
+ },
1389
+ ],
1390
+ metadata: {
1391
+ version: (
1392
+ JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')) as { version: string }
1393
+ ).version,
1394
+ license: spec.id === 'zig' ? 'BSD-3-Clause' : 'MIT',
1395
+ description: 'Pinned parser WASM build',
1396
+ authors: [],
1397
+ links: { repository: buildRepositories[spec.id] },
1398
+ },
1399
+ bindings: {},
1400
+ })
1401
+ );
1402
+ }
1403
+ if (spec.id === 'graphql') {
1404
+ // The pinned quoted-string rule rejects every escape, including valid GraphQL \n.
1405
+ // Keep the upstream block-string rule and all source offsets unchanged.
1406
+ const path = join(dir, 'grammar.js');
1407
+ const grammar = readFileSync(path, 'utf8');
1408
+ const before = String.raw`seq('"', /[^"\\\n]*/, '"')`;
1409
+ const after = String.raw`seq('"', /([^"\\\r\n]|\\(["\\/bfnrt]|u[0-9a-fA-F]{4}))*/, '"')`;
1410
+ if (!grammar.includes(before)) throw new Error('GraphQL string escape patch no longer applies');
1411
+ writeFileSync(path, grammar.replace(before, after));
1412
+ }
1413
+ if (spec.id === 'zig') {
1414
+ const path = join(dir, 'grammar.js');
1415
+ const grammar = readFileSync(path, 'utf8');
1416
+ const before = '/u{[0-9a-fA-F]+}/';
1417
+ if (!grammar.includes(before))
1418
+ throw new Error('Zig Unicode escape compatibility patch no longer applies');
1419
+ writeFileSync(path, grammar.replace(before, String.raw`/u\{[0-9a-fA-F]+\}/`));
1420
+ }
1421
+ const tools = join(work, 'prerequisite-build');
1422
+ const cli = join(tools, 'node_modules', '.bin', 'tree-sitter');
1423
+ if (!existsSync(cli)) {
1424
+ execFileSync('npm', [
1425
+ 'install',
1426
+ '--prefix',
1427
+ tools,
1428
+ '--no-audit',
1429
+ '--no-fund',
1430
+ '--no-package-lock',
1431
+ 'tree-sitter-cli@0.24.4',
1432
+ ]);
1433
+ }
1434
+ const deadline = Date.now() + 300_000;
1435
+ if (spec.build === 'regenerate') {
1436
+ execFileSync(cli, ['generate'], { cwd: dir, timeout: 300_000 });
1437
+ }
1438
+ execFileSync(
1439
+ cli,
1440
+ [
1441
+ 'build',
1442
+ '--wasm',
1443
+ '--docker',
1444
+ '--output',
1445
+ join(dir, spec.wasmFile ?? `tree-sitter-${spec.id}.wasm`),
1446
+ ],
1447
+ {
1448
+ cwd: dir,
1449
+ timeout: Math.max(1, deadline - Date.now()),
1450
+ }
1451
+ );
1452
+ }
1453
+
955
1454
  function vendor(): Problem[] {
956
1455
  const only = process.argv.find((arg) => arg.startsWith('--only='))?.slice(7);
957
- if (only && !GRAMMARS.some((spec) => spec.id === only))
1456
+ const selected = only?.split(',');
1457
+ if (selected?.some((id) => !GRAMMARS.some((spec) => spec.id === id)))
958
1458
  throw new Error(`Unknown grammar: ${only}`);
959
1459
  mkdirSync(WASM_DIR, { recursive: true });
960
1460
  const work = mkdtempSync(join(tmpdir(), 'ts-grammars-'));
@@ -965,7 +1465,7 @@ function vendor(): Problem[] {
965
1465
 
966
1466
  try {
967
1467
  for (const spec of GRAMMARS) {
968
- if (only && spec.id !== only) continue;
1468
+ if (selected && !selected.includes(spec.id)) continue;
969
1469
  let dir = extracted.get(spec.pkg);
970
1470
  if (!dir) {
971
1471
  try {
@@ -998,6 +1498,7 @@ function vendor(): Problem[] {
998
1498
  if (spec.id === 'cpp') rebuildCpp(dir, work);
999
1499
  if (spec.id === 'bash') rebuildBash(dir, work);
1000
1500
  if (spec.id === 'nix' || spec.id === 'cmake') rebuildNixOrCmake(dir, work, spec.id);
1501
+ if (spec.build) buildPrerequisite(dir, work, spec);
1001
1502
  }
1002
1503
  extracted.set(spec.pkg, dir);
1003
1504
  } catch (err) {
@@ -1015,7 +1516,18 @@ function vendor(): Problem[] {
1015
1516
  if (!existsSync(wasmSrc)) {
1016
1517
  problems.push({ id: spec.id, detail: `${spec.pkg} ships no ${wasmName}` });
1017
1518
  } else {
1018
- copyFileSync(wasmSrc, join(WASM_DIR, `${spec.id}.wasm`));
1519
+ const compressed = gzipSync(readFileSync(wasmSrc), { level: 9 });
1520
+ // Timestamp is zero in Node gzip; normalize the OS byte across build hosts too.
1521
+ compressed[9] = 255;
1522
+ writeFileSync(join(WASM_DIR, `${spec.id}.wasm.gz`), compressed, { mode: 0o644 });
1523
+ rmSync(join(WASM_DIR, `${spec.id}.wasm`), { force: true });
1524
+ if (spec.licenseFile)
1525
+ writeFileSync(
1526
+ join(WASM_DIR, `${spec.id}.LICENSE`),
1527
+ readFileSync(join(dir, spec.licenseFile), 'utf8')
1528
+ .replace(/\r\n/g, '\n')
1529
+ .replace(/[\u201c\u201d]/g, '"')
1530
+ );
1019
1531
  if (spec.id === 'lua') copyFileSync(join(dir, 'LICENSE.md'), join(WASM_DIR, 'lua.LICENSE'));
1020
1532
  if (spec.id === 'terraform')
1021
1533
  copyFileSync(join(dir, 'LICENSE'), join(WASM_DIR, 'terraform.LICENSE'));
@@ -1029,7 +1541,7 @@ function vendor(): Problem[] {
1029
1541
  spec.id === 'nix' ||
1030
1542
  spec.id === 'cmake'
1031
1543
  )
1032
- chmodSync(join(WASM_DIR, `${spec.id}.wasm`), 0o644);
1544
+ chmodSync(join(WASM_DIR, `${spec.id}.wasm.gz`), 0o644);
1033
1545
  }
1034
1546
 
1035
1547
  // A targeted JS scanner rebuild must retain our module-scope binding query additions.
@@ -1072,11 +1584,15 @@ if (problems.length > 0) {
1072
1584
  process.exit(1);
1073
1585
  }
1074
1586
 
1075
- const sizes = GRAMMARS.map((g) => statSync(join(WASM_DIR, `${g.id}.wasm`)).size);
1587
+ const sizes = GRAMMARS.map((g) => statSync(join(WASM_DIR, `${g.id}.wasm.gz`)).size);
1076
1588
  const totalBytes = sizes.reduce((a, b) => a + b, 0);
1589
+ const inflatedBytes = GRAMMARS.reduce(
1590
+ (total, g) => total + gunzipSync(readFileSync(join(WASM_DIR, `${g.id}.wasm.gz`))).length,
1591
+ 0
1592
+ );
1077
1593
  const totalMB = +(totalBytes / 1048576).toFixed(1);
1078
1594
  console.log(
1079
- `VENDOR_GRAMMARS_OK ${JSON.stringify({ grammars: GRAMMARS.length, totalWasmMB: totalMB })}`
1595
+ `VENDOR_GRAMMARS_OK ${JSON.stringify({ grammars: GRAMMARS.length, shippedBytes: totalBytes, inflatedBytes, totalWasmMB: totalMB })}`
1080
1596
  );
1081
1597
  // Owner-approved Objective-C++ cost: repaired bundle 43.58 MiB (43.67 with Terraform 667475e93), bounded at 45 MiB.
1082
1598
  if (totalBytes > 45 * 1048576) {