@mnemonik/shared 7.144.2 → 7.145.1

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 +586 -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, alias($._tagged_template_string, $.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');
@@ -328,7 +441,36 @@ function rebuildTypescript(dir: string, work: string, only?: string): void {
328
441
  optional(seq('as', field('alias', $._module_export_name))),
329
442
  ),`
330
443
  );
444
+ grammar = grammar.replaceAll(
445
+ "field('arguments', $.template_string)",
446
+ "field('arguments', alias($._tagged_template_string, $.template_string))"
447
+ );
331
448
  writeFileSync(grammarPath, grammar);
449
+ const scannerPath = join(dir, 'common', 'scanner.h');
450
+ let scanner = readFileSync(scannerPath, 'utf8');
451
+ const before = " case '<':\n";
452
+ if (!scanner.includes(before))
453
+ throw new Error('TypeScript generic-overload ASI patch no longer applies');
454
+ scanner = scanner
455
+ .replace(before, '')
456
+ .replace(
457
+ " case '{':",
458
+ " 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 '{':"
459
+ );
460
+ const inBoundary =
461
+ ' if (!iswalpha(lexer->lookahead)) {\n return false;\n }';
462
+ if (!scanner.includes(inBoundary))
463
+ throw new Error('TypeScript in-property ASI patch no longer applies');
464
+ scanner = scanner.replace(
465
+ inBoundary,
466
+ ' if (!iswalpha(lexer->lookahead)) {\n return !valid_symbols[LOGICAL_OR];\n }'
467
+ );
468
+ const optionalTuple =
469
+ "lexer->lookahead == ':' || lexer->lookahead == ')' || lexer->lookahead == ','";
470
+ if (!scanner.includes(optionalTuple))
471
+ throw new Error('TypeScript optional-tuple patch no longer applies');
472
+ scanner = scanner.replace(optionalTuple, optionalTuple + " || lexer->lookahead == ']'");
473
+ writeFileSync(scannerPath, scanner);
332
474
  // Retain our type/enum declaration captures when the vendor loop copies the query.
333
475
  copyFileSync(join(WASM_DIR, 'typescript.tags.scm'), join(dir, 'queries', 'tags.scm'));
334
476
  const tools = join(work, 'typescript-build');
@@ -343,8 +485,10 @@ function rebuildTypescript(dir: string, work: string, only?: string): void {
343
485
  'tree-sitter-javascript@0.23.1',
344
486
  ]);
345
487
  const cli = join(tools, 'node_modules', '.bin', 'tree-sitter');
488
+ // CLI 0.24 mis-emits the complementary character class; spell out raw characters.
489
+ patchTaggedTemplates(join(tools, 'node_modules', 'tree-sitter-javascript', 'grammar.js'), false);
346
490
  for (const dialect of ['typescript', 'tsx']) {
347
- if (only && only !== dialect) continue;
491
+ if (only && !only.split(',').includes(dialect)) continue;
348
492
  // Apply the JSX scanner and attribute repairs only to TSX.
349
493
  if (dialect === 'tsx') {
350
494
  patchJsxScanner(join(dir, 'common', 'scanner.h'));
@@ -364,6 +508,34 @@ function rebuildTypescript(dir: string, work: string, only?: string): void {
364
508
  }
365
509
  }
366
510
 
511
+ /** Tagged templates retain invalid escape spellings as raw text; ordinary literals stay strict. */
512
+ function patchTaggedTemplates(grammarPath: string, complementaryClass = true): void {
513
+ let grammar = readFileSync(grammarPath, 'utf8');
514
+ const argument = "field('arguments', $.template_string)";
515
+ const template = ' template_string: $ => seq(';
516
+ if (!grammar.includes(argument) || !grammar.includes(template))
517
+ throw new Error('Tagged-template escape patch no longer applies');
518
+ const tagged = [
519
+ ' _tagged_template_string: $ => seq(',
520
+ " '`',",
521
+ ' repeat(choice(',
522
+ ' alias($._template_chars, $.string_fragment),',
523
+ ' $.escape_sequence,',
524
+ complementaryClass
525
+ ? " alias(token.immediate(prec(1, seq('\\\\', /[\\s\\S]/))), $.escape_sequence),"
526
+ : " alias(token.immediate(prec(1, seq('\\\\', choice(/./, /[\\r\\n\\u2028\\u2029]/)))), $.escape_sequence),",
527
+ ' $.template_substitution,',
528
+ ' )),',
529
+ " '`',",
530
+ ' ),',
531
+ '',
532
+ ].join('\n');
533
+ grammar = grammar
534
+ .replaceAll(argument, "field('arguments', alias($._tagged_template_string, $.template_string))")
535
+ .replace(template, tagged + template);
536
+ writeFileSync(grammarPath, grammar);
537
+ }
538
+
367
539
  /** Upstream f8637eb: preserve entity boundaries while accepting bare JSX ampersands. */
368
540
  function patchJsxScanner(scannerPath: string): void {
369
541
  execFileSync('patch', ['--batch', scannerPath], {
@@ -394,6 +566,7 @@ function patchJsxAttributes(grammarPath: string): void {
394
566
 
395
567
  /** 0.25.0's reserved-word DSL needs generator 0.25.8; keep the existing WASM build tool. */
396
568
  function rebuildJavascript(dir: string, work: string): void {
569
+ patchTaggedTemplates(join(dir, 'grammar.js'));
397
570
  patchJsxScanner(join(dir, 'src', 'scanner.c'));
398
571
  patchJsxAttributes(join(dir, 'grammar.js'));
399
572
  const generator = join(work, 'javascript-generator');
@@ -513,6 +686,18 @@ function vendorObjcpp(work: string, repository: string): string {
513
686
  ]);
514
687
  git(['checkout', '--quiet', '--detach', 'FETCH_HEAD']);
515
688
  const dir = join(root, 'packages', 'tree-sitter-objcpp');
689
+ const grammarPath = join(dir, 'grammar.js');
690
+ let grammar = readFileSync(grammarPath, 'utf8');
691
+ const before = ' _objc_implementation_item: $ => choice(\n';
692
+ if (!grammar.includes(before))
693
+ throw new Error('Objective-C++ macro-body patch no longer applies');
694
+ grammar = grammar.replace(
695
+ before,
696
+ " objc_macro_body: $ => seq($.objc_macro_invocation, field('body', $.compound_statement)),\n\n" +
697
+ before +
698
+ ' $.objc_macro_body,\n'
699
+ );
700
+ writeFileSync(grammarPath, grammar);
516
701
  const tools = join(work, 'objcpp-build');
517
702
  execFileSync('npm', [
518
703
  'install',
@@ -522,10 +707,17 @@ function vendorObjcpp(work: string, repository: string): string {
522
707
  '--no-fund',
523
708
  '--no-package-lock',
524
709
  'tree-sitter-cli@0.24.4',
710
+ // Match the pinned source's lockfile; these are grammar sources, never runtime dependencies.
711
+ 'tree-sitter-c@0.23.6',
712
+ 'tree-sitter-cpp@0.23.4',
525
713
  ]);
526
- // Upstream generated this ABI-15 parser with CLI 0.25.10; 0.24.4 builds it with emsdk 3.1.64.
714
+ const cli = join(tools, 'node_modules', '.bin', 'tree-sitter');
715
+ execFileSync(cli, ['generate'], {
716
+ cwd: dir,
717
+ env: { ...process.env, NODE_PATH: join(tools, 'node_modules') },
718
+ });
527
719
  execFileSync(
528
- join(tools, 'node_modules', '.bin', 'tree-sitter'),
720
+ cli,
529
721
  ['build', '--wasm', '--docker', '--output', join(dir, 'tree-sitter-objcpp.wasm')],
530
722
  { cwd: dir }
531
723
  );
@@ -611,12 +803,35 @@ function rebuildSql(dir: string, work: string): void {
611
803
  $.keyword_create, make_keyword("policy"), field('name', $.identifier),
612
804
  $.keyword_on, alias($.policy_relation, $.relation),
613
805
  optional(seq($.keyword_for, choice($.keyword_select, $.keyword_insert, $.keyword_update, $.keyword_delete))),
614
- optional(seq($.keyword_to, $.identifier)),
806
+ optional(seq($.keyword_to, comma_list($.identifier, true))),
615
807
  optional(seq($.keyword_using, wrapped_in_parenthesis($._expression))),
616
808
  optional(seq($.keyword_with, $.keyword_check, wrapped_in_parenthesis($._expression))),
617
809
  ),
618
810
  create_table: $ => prec.left(`
619
811
  );
812
+ // These stored migrations use qualified enum names and table/sequence grants.
813
+ patch(
814
+ ` alter_type: $ => seq(
815
+ $.keyword_alter,
816
+ $.keyword_type,
817
+ $.identifier,`,
818
+ ` alter_type: $ => seq(
819
+ $.keyword_alter,
820
+ $.keyword_type,
821
+ $.object_reference,`
822
+ );
823
+ patch(' $.reset_statement,', ' $.reset_statement,\n $.grant_statement,');
824
+ patch(
825
+ ' _cte: $ => seq(',
826
+ ` grant_statement: $ => seq(
827
+ make_keyword("grant"),
828
+ comma_list(choice($.keyword_select, $.keyword_insert, $.keyword_update, $.keyword_delete, make_keyword("usage")), true),
829
+ $.keyword_on, optional($.keyword_sequence), comma_list($.object_reference, true),
830
+ $.keyword_to, comma_list($.identifier, true),
831
+ ),
832
+
833
+ _cte: $ => seq(`
834
+ );
620
835
  // Dollar-quoted procedural code is one literal, never a parsed SQL statement list.
621
836
  patch(' $.reset_statement,', ' $.reset_statement,\n $.do_statement,');
622
837
  patch(
@@ -639,6 +854,240 @@ function rebuildSql(dir: string, work: string): void {
639
854
  ' conflicts: $ => [',
640
855
  ' conflicts: $ => [\n [$._expression, $._inner_default_expression],\n [$.create_function],'
641
856
  );
857
+ // Fixed R2 cohort: change individual upstream rules, retaining the scanner and AST names.
858
+ const patchRule = (name: string, before: string, after: string): void => {
859
+ const start = grammar.indexOf(` ${name}:`);
860
+ if (start < 0) throw new Error(`SQL rule missing: ${name}`);
861
+ const next = grammar.slice(start + 1).search(/^ [a-zA-Z_]\w*\s*:/m);
862
+ const end = next < 0 ? grammar.length : start + 1 + next;
863
+ const rule = grammar.slice(start, end);
864
+ if (!rule.includes(before)) throw new Error(`SQL ${name} patch no longer applies`);
865
+ grammar = grammar.slice(0, start) + rule.replace(before, after) + grammar.slice(end);
866
+ };
867
+ patchRule('_type', 'prec.left(', 'prec.right(');
868
+ patchRule('array_size_definition', 'prec.left(', 'prec.right(');
869
+ patchRule(
870
+ '_type',
871
+ '$.keyword_geography,',
872
+ `seq($.keyword_geography, optional(wrapped_in_parenthesis(
873
+ seq($.identifier, optional(seq(',', $._natural_number)))
874
+ ))),`
875
+ );
876
+ patchRule(
877
+ '_column_constraint',
878
+ '$.keyword_auto_increment,',
879
+ `choice($.keyword_auto_increment, make_keyword("autoincrement")),`
880
+ );
881
+ patchRule('function_argument', '$.literal,', '$._expression,');
882
+ patchRule(
883
+ '_inner_default_expression',
884
+ '$.keyword_current_timestamp,',
885
+ `$.keyword_current_timestamp,
886
+ make_keyword("current_date"),`
887
+ );
888
+ patchRule('create_policy', '$.keyword_delete))),', '$.keyword_delete, $.keyword_all))),');
889
+ patchRule(
890
+ '_create_statement',
891
+ '$.create_function,',
892
+ 'alias($.create_procedure, $.create_function),\n $.create_function,'
893
+ );
894
+ patchRule(
895
+ '_drop_statement',
896
+ '$.drop_function,',
897
+ '$.drop_function,\n $.drop_procedure,\n $.drop_policy,'
898
+ );
899
+ patchRule('_alter_statement', '$.alter_type,', '$.alter_type,\n $.alter_policy,');
900
+ patchRule(
901
+ 'drop_function',
902
+ '$.object_reference,',
903
+ '$.object_reference,\n optional($.function_arguments),'
904
+ );
905
+ patchRule('drop_index', 'field("name", $.identifier)', 'field("name", $.object_reference)');
906
+ patchRule(
907
+ 'alter_column',
908
+ "field('type', $._type),",
909
+ "field('type', $._type),\n optional(seq($.keyword_using, $._expression)),"
910
+ );
911
+ patchRule(
912
+ 'add_constraint',
913
+ '$.constraint,',
914
+ '$.constraint,\n optional(seq($.keyword_not, make_keyword("valid"))),'
915
+ );
916
+ patchRule(
917
+ '_alter_specifications',
918
+ '$.set_schema,',
919
+ `$.set_schema,
920
+ seq(make_keyword("validate"), $.keyword_constraint, $.identifier),
921
+ seq($.keyword_rename, $.keyword_constraint, $.identifier, $.keyword_to, $.identifier),
922
+ seq($.keyword_set, paren_list(seq($.identifier, '=', $.literal), true)),
923
+ seq($.keyword_reset, paren_list($.identifier, true)),`
924
+ );
925
+ patchRule(
926
+ '_table_settings',
927
+ '$.table_partition,',
928
+ `seq($.keyword_on, $.keyword_commit, $.keyword_drop),
929
+ $.table_partition,`
930
+ );
931
+ patchRule(
932
+ 'create_index',
933
+ '$.index_fields',
934
+ `$.index_fields,
935
+ optional(seq(make_keyword("include"), paren_list($.identifier, true)))`
936
+ );
937
+ patchRule(
938
+ 'transaction',
939
+ " optional(';'),",
940
+ ` optional(choice(
941
+ seq($.keyword_read, $.keyword_only),
942
+ seq($.keyword_isolation, $.keyword_level, $.keyword_repeatable, $.keyword_read)
943
+ )),
944
+ optional(';'),`
945
+ );
946
+ patchRule(
947
+ 'limit',
948
+ '$.literal,',
949
+ 'choice($.literal, $.parameter, alias($._qualified_field, $.field)),'
950
+ );
951
+ patchRule(
952
+ 'offset',
953
+ '$.literal,',
954
+ 'choice($.literal, $.parameter, alias($._qualified_field, $.field)),'
955
+ );
956
+ patchRule(
957
+ 'statement',
958
+ ` optional($.keyword_analyze),
959
+ optional($.keyword_verbose),`,
960
+ ` choice(
961
+ seq(optional($.keyword_analyze), optional($.keyword_verbose)),
962
+ paren_list(seq(choice($.identifier, $.keyword_analyze, $.keyword_verbose),
963
+ optional(choice($.keyword_on, $.keyword_off, $.keyword_true, $.keyword_false))), true)
964
+ ),`
965
+ );
966
+ patchRule(
967
+ '_ddl_statement',
968
+ '$.reset_statement,',
969
+ `$.reset_statement,
970
+ $.revoke_statement,
971
+ $.alter_default_privileges,
972
+ $.analyze_statement,
973
+ $.lock_statement,
974
+ $.prepare_statement,
975
+ $.execute_statement,
976
+ $.call_statement,`
977
+ );
978
+ patchRule(
979
+ '_delete_from',
980
+ '$.object_reference,',
981
+ `$.object_reference,
982
+ optional($._alias),
983
+ optional(seq($.keyword_using, comma_list($.relation, true))),`
984
+ );
985
+ patchRule(
986
+ 'cte',
987
+ 'choice($._dml_read, $._dml_write)',
988
+ 'choice($._dml_read, $._dml_write, $.values)'
989
+ );
990
+ patchRule(
991
+ '_on_conflict',
992
+ 'optional($._column_list),',
993
+ 'optional(seq(paren_list($._expression, true), optional($.where))),'
994
+ );
995
+ patchRule('from', `comma_list($.relation, true),`, '$.relation,');
996
+ patchRule(
997
+ 'from',
998
+ ` $.join,`,
999
+ ` seq(',', $.relation),
1000
+ $.join,`
1001
+ );
1002
+ patchRule('from', ' optional($.order_by),\n optional($.limit),', '');
1003
+ patchRule(
1004
+ '_select_statement',
1005
+ ' optional($.from),',
1006
+ ` optional($.from),
1007
+ optional($.order_by),
1008
+ optional($.limit),`
1009
+ );
1010
+ patchRule(
1011
+ 'set_operation',
1012
+ ` ),\n ),`,
1013
+ ` ),
1014
+ optional($.order_by),
1015
+ optional($.limit),
1016
+ ),`
1017
+ );
1018
+ patchRule('op_other', " '<->',", " '<->',\n '<=>',");
1019
+ patchRule('_expression', '$.interval,', '$.interval,\n $.at_time_zone,');
1020
+ const support = ' $.function_support,\n';
1021
+ if (grammar.split(support).length !== 3) throw new Error('SQL function options changed');
1022
+ grammar = grammar.replaceAll(support, support + ' $.function_configuration,\n');
1023
+ patchRule(
1024
+ 'grant_statement',
1025
+ `comma_list(choice($.keyword_select, $.keyword_insert, $.keyword_update, $.keyword_delete, make_keyword("usage")), true),
1026
+ $.keyword_on, optional($.keyword_sequence), comma_list($.object_reference, true),`,
1027
+ `$._privilege_list,
1028
+ $.keyword_on, $._privilege_target,`
1029
+ );
1030
+ patch(
1031
+ ' _cte: $ => seq(',
1032
+ ` _privilege_list: $ => comma_list(choice(
1033
+ $.keyword_select, $.keyword_insert, $.keyword_update, $.keyword_delete,
1034
+ make_keyword("usage"), make_keyword("execute"), make_keyword("truncate"),
1035
+ seq($.keyword_all, optional(make_keyword("privileges")))
1036
+ ), true),
1037
+ routine_signature: $ => seq($.object_reference, optional($.function_arguments)),
1038
+ _privilege_target: $ => choice(
1039
+ seq($.keyword_function, comma_list($.routine_signature, true)),
1040
+ seq($.keyword_schema, comma_list($.identifier, true)),
1041
+ seq($.keyword_all, choice(make_keyword("tables"), make_keyword("sequences"), make_keyword("functions")),
1042
+ $.keyword_in, $.keyword_schema, comma_list($.identifier, true)),
1043
+ seq(optional(choice($.keyword_table, $.keyword_sequence)), comma_list($.object_reference, true))
1044
+ ),
1045
+ revoke_statement: $ => seq(make_keyword("revoke"), $._privilege_list, $.keyword_on,
1046
+ $._privilege_target, $.keyword_from, comma_list($.identifier, true)),
1047
+ alter_default_privileges: $ => seq(
1048
+ $.keyword_alter, $.keyword_default, make_keyword("privileges"),
1049
+ optional(seq($.keyword_for, $.keyword_role, comma_list($.identifier, true))),
1050
+ optional(seq($.keyword_in, $.keyword_schema, comma_list($.identifier, true))),
1051
+ choice(
1052
+ seq(make_keyword("grant"), $._privilege_list, $.keyword_on,
1053
+ choice(make_keyword("tables"), make_keyword("sequences"), make_keyword("functions")),
1054
+ $.keyword_to, comma_list($.identifier, true)),
1055
+ seq(make_keyword("revoke"), $._privilege_list, $.keyword_on,
1056
+ choice(make_keyword("tables"), make_keyword("sequences"), make_keyword("functions")),
1057
+ $.keyword_from, comma_list($.identifier, true))
1058
+ )
1059
+ ),
1060
+ drop_policy: $ => seq($.keyword_drop, make_keyword("policy"), optional($._if_exists),
1061
+ $.identifier, $.keyword_on, alias($.policy_relation, $.relation)),
1062
+ alter_policy: $ => seq($.keyword_alter, make_keyword("policy"), $.identifier,
1063
+ $.keyword_on, alias($.policy_relation, $.relation), $.keyword_rename, $.keyword_to, $.identifier),
1064
+ function_configuration: $ => seq($.keyword_set, $.object_reference,
1065
+ choice(seq($.keyword_from, make_keyword("current")),
1066
+ seq(choice('=', $.keyword_to), comma_list(choice($.literal, $.identifier), true)))),
1067
+ _procedure_option: $ => choice($.function_security, $.function_configuration),
1068
+ create_procedure: $ => seq(
1069
+ $.keyword_create, optional($._or_replace), $.keyword_procedure,
1070
+ $.object_reference, $.function_arguments, repeat($._procedure_option),
1071
+ choice(
1072
+ seq($.function_language, repeat($._procedure_option), alias($.opaque_function_body, $.function_body)),
1073
+ seq(alias($.opaque_function_body, $.function_body), repeat($._procedure_option), $.function_language)
1074
+ ), repeat($._procedure_option)
1075
+ ),
1076
+ drop_procedure: $ => seq($.keyword_drop, $.keyword_procedure, optional($._if_exists),
1077
+ $.routine_signature, optional($._drop_behavior)),
1078
+ call_statement: $ => seq(make_keyword("call"), $.invocation),
1079
+ analyze_statement: $ => seq($.keyword_analyze, $.object_reference, optional(paren_list($.identifier, true))),
1080
+ lock_statement: $ => seq(make_keyword("lock"), $.keyword_table, comma_list($.object_reference, true),
1081
+ $.keyword_in, choice(seq(make_keyword("share"), $.keyword_row, make_keyword("exclusive")),
1082
+ seq(make_keyword("access"), make_keyword("exclusive"))), make_keyword("mode")),
1083
+ prepare_statement: $ => seq(make_keyword("prepare"), $.identifier,
1084
+ optional(paren_list($._type, true)), $.keyword_as, choice($._dml_read, $._dml_write)),
1085
+ execute_statement: $ => seq(make_keyword("execute"), $.identifier, optional(paren_list($._expression))),
1086
+ at_time_zone: $ => prec.left('binary_times', seq($._expression,
1087
+ make_keyword("at"), make_keyword("time"), make_keyword("zone"), $._expression)),
1088
+
1089
+ _cte: $ => seq(`
1090
+ );
642
1091
  const functionStart = grammar.indexOf(' create_function: $ => seq(');
643
1092
  const functionEnd = grammar.indexOf(' _function_return:', functionStart);
644
1093
  if (functionStart < 0 || functionEnd < 0) throw new Error('SQL function patch no longer applies');
@@ -952,9 +1401,95 @@ function rebuildBash(dir: string, work: string): void {
952
1401
  );
953
1402
  }
954
1403
 
1404
+ /** Reuse the existing offline WASM toolchain; never install native grammar bindings. */
1405
+ function buildPrerequisite(dir: string, work: string, spec: GrammarSpec): void {
1406
+ const config = join(dir, 'tree-sitter.json');
1407
+ const buildRepositories: Record<string, string> = {
1408
+ clojure: 'https://github.com/oakmac/tree-sitter-clojure',
1409
+ zig: 'https://github.com/GrayJack/tree-sitter-zig',
1410
+ graphql: 'https://github.com/bkegley/tree-sitter-graphql',
1411
+ };
1412
+ if (!existsSync(config) && buildRepositories[spec.id]) {
1413
+ // Old packages omit the build metadata required by the current WASM builder.
1414
+ writeFileSync(
1415
+ config,
1416
+ JSON.stringify({
1417
+ grammars: [
1418
+ {
1419
+ name: spec.id === 'protobuf' ? 'proto' : spec.id,
1420
+ scope: `source.${spec.id}`,
1421
+ path: '.',
1422
+ 'file-types': [],
1423
+ },
1424
+ ],
1425
+ metadata: {
1426
+ version: (
1427
+ JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')) as { version: string }
1428
+ ).version,
1429
+ license: spec.id === 'zig' ? 'BSD-3-Clause' : 'MIT',
1430
+ description: 'Pinned parser WASM build',
1431
+ authors: [],
1432
+ links: { repository: buildRepositories[spec.id] },
1433
+ },
1434
+ bindings: {},
1435
+ })
1436
+ );
1437
+ }
1438
+ if (spec.id === 'graphql') {
1439
+ // The pinned quoted-string rule rejects every escape, including valid GraphQL \n.
1440
+ // Keep the upstream block-string rule and all source offsets unchanged.
1441
+ const path = join(dir, 'grammar.js');
1442
+ const grammar = readFileSync(path, 'utf8');
1443
+ const before = String.raw`seq('"', /[^"\\\n]*/, '"')`;
1444
+ const after = String.raw`seq('"', /([^"\\\r\n]|\\(["\\/bfnrt]|u[0-9a-fA-F]{4}))*/, '"')`;
1445
+ if (!grammar.includes(before)) throw new Error('GraphQL string escape patch no longer applies');
1446
+ writeFileSync(path, grammar.replace(before, after));
1447
+ }
1448
+ if (spec.id === 'zig') {
1449
+ const path = join(dir, 'grammar.js');
1450
+ const grammar = readFileSync(path, 'utf8');
1451
+ const before = '/u{[0-9a-fA-F]+}/';
1452
+ if (!grammar.includes(before))
1453
+ throw new Error('Zig Unicode escape compatibility patch no longer applies');
1454
+ writeFileSync(path, grammar.replace(before, String.raw`/u\{[0-9a-fA-F]+\}/`));
1455
+ }
1456
+ const tools = join(work, 'prerequisite-build');
1457
+ const cli = join(tools, 'node_modules', '.bin', 'tree-sitter');
1458
+ if (!existsSync(cli)) {
1459
+ execFileSync('npm', [
1460
+ 'install',
1461
+ '--prefix',
1462
+ tools,
1463
+ '--no-audit',
1464
+ '--no-fund',
1465
+ '--no-package-lock',
1466
+ 'tree-sitter-cli@0.24.4',
1467
+ ]);
1468
+ }
1469
+ const deadline = Date.now() + 300_000;
1470
+ if (spec.build === 'regenerate') {
1471
+ execFileSync(cli, ['generate'], { cwd: dir, timeout: 300_000 });
1472
+ }
1473
+ execFileSync(
1474
+ cli,
1475
+ [
1476
+ 'build',
1477
+ '--wasm',
1478
+ '--docker',
1479
+ '--output',
1480
+ join(dir, spec.wasmFile ?? `tree-sitter-${spec.id}.wasm`),
1481
+ ],
1482
+ {
1483
+ cwd: dir,
1484
+ timeout: Math.max(1, deadline - Date.now()),
1485
+ }
1486
+ );
1487
+ }
1488
+
955
1489
  function vendor(): Problem[] {
956
1490
  const only = process.argv.find((arg) => arg.startsWith('--only='))?.slice(7);
957
- if (only && !GRAMMARS.some((spec) => spec.id === only))
1491
+ const selected = only?.split(',');
1492
+ if (selected?.some((id) => !GRAMMARS.some((spec) => spec.id === id)))
958
1493
  throw new Error(`Unknown grammar: ${only}`);
959
1494
  mkdirSync(WASM_DIR, { recursive: true });
960
1495
  const work = mkdtempSync(join(tmpdir(), 'ts-grammars-'));
@@ -965,7 +1500,7 @@ function vendor(): Problem[] {
965
1500
 
966
1501
  try {
967
1502
  for (const spec of GRAMMARS) {
968
- if (only && spec.id !== only) continue;
1503
+ if (selected && !selected.includes(spec.id)) continue;
969
1504
  let dir = extracted.get(spec.pkg);
970
1505
  if (!dir) {
971
1506
  try {
@@ -998,6 +1533,7 @@ function vendor(): Problem[] {
998
1533
  if (spec.id === 'cpp') rebuildCpp(dir, work);
999
1534
  if (spec.id === 'bash') rebuildBash(dir, work);
1000
1535
  if (spec.id === 'nix' || spec.id === 'cmake') rebuildNixOrCmake(dir, work, spec.id);
1536
+ if (spec.build) buildPrerequisite(dir, work, spec);
1001
1537
  }
1002
1538
  extracted.set(spec.pkg, dir);
1003
1539
  } catch (err) {
@@ -1015,7 +1551,18 @@ function vendor(): Problem[] {
1015
1551
  if (!existsSync(wasmSrc)) {
1016
1552
  problems.push({ id: spec.id, detail: `${spec.pkg} ships no ${wasmName}` });
1017
1553
  } else {
1018
- copyFileSync(wasmSrc, join(WASM_DIR, `${spec.id}.wasm`));
1554
+ const compressed = gzipSync(readFileSync(wasmSrc), { level: 9 });
1555
+ // Timestamp is zero in Node gzip; normalize the OS byte across build hosts too.
1556
+ compressed[9] = 255;
1557
+ writeFileSync(join(WASM_DIR, `${spec.id}.wasm.gz`), compressed, { mode: 0o644 });
1558
+ rmSync(join(WASM_DIR, `${spec.id}.wasm`), { force: true });
1559
+ if (spec.licenseFile)
1560
+ writeFileSync(
1561
+ join(WASM_DIR, `${spec.id}.LICENSE`),
1562
+ readFileSync(join(dir, spec.licenseFile), 'utf8')
1563
+ .replace(/\r\n/g, '\n')
1564
+ .replace(/[\u201c\u201d]/g, '"')
1565
+ );
1019
1566
  if (spec.id === 'lua') copyFileSync(join(dir, 'LICENSE.md'), join(WASM_DIR, 'lua.LICENSE'));
1020
1567
  if (spec.id === 'terraform')
1021
1568
  copyFileSync(join(dir, 'LICENSE'), join(WASM_DIR, 'terraform.LICENSE'));
@@ -1029,7 +1576,7 @@ function vendor(): Problem[] {
1029
1576
  spec.id === 'nix' ||
1030
1577
  spec.id === 'cmake'
1031
1578
  )
1032
- chmodSync(join(WASM_DIR, `${spec.id}.wasm`), 0o644);
1579
+ chmodSync(join(WASM_DIR, `${spec.id}.wasm.gz`), 0o644);
1033
1580
  }
1034
1581
 
1035
1582
  // A targeted JS scanner rebuild must retain our module-scope binding query additions.
@@ -1072,11 +1619,15 @@ if (problems.length > 0) {
1072
1619
  process.exit(1);
1073
1620
  }
1074
1621
 
1075
- const sizes = GRAMMARS.map((g) => statSync(join(WASM_DIR, `${g.id}.wasm`)).size);
1622
+ const sizes = GRAMMARS.map((g) => statSync(join(WASM_DIR, `${g.id}.wasm.gz`)).size);
1076
1623
  const totalBytes = sizes.reduce((a, b) => a + b, 0);
1624
+ const inflatedBytes = GRAMMARS.reduce(
1625
+ (total, g) => total + gunzipSync(readFileSync(join(WASM_DIR, `${g.id}.wasm.gz`))).length,
1626
+ 0
1627
+ );
1077
1628
  const totalMB = +(totalBytes / 1048576).toFixed(1);
1078
1629
  console.log(
1079
- `VENDOR_GRAMMARS_OK ${JSON.stringify({ grammars: GRAMMARS.length, totalWasmMB: totalMB })}`
1630
+ `VENDOR_GRAMMARS_OK ${JSON.stringify({ grammars: GRAMMARS.length, shippedBytes: totalBytes, inflatedBytes, totalWasmMB: totalMB })}`
1080
1631
  );
1081
1632
  // Owner-approved Objective-C++ cost: repaired bundle 43.58 MiB (43.67 with Terraform 667475e93), bounded at 45 MiB.
1082
1633
  if (totalBytes > 45 * 1048576) {