@mnemonik/shared 7.143.0 → 7.144.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 (54) hide show
  1. package/dist/ast/astChunker.d.ts.map +1 -1
  2. package/dist/ast/astChunker.js +6 -1
  3. package/dist/ast/astChunker.js.map +1 -1
  4. package/dist/ast/grammars.d.ts +4 -2
  5. package/dist/ast/grammars.d.ts.map +1 -1
  6. package/dist/ast/grammars.js +37 -0
  7. package/dist/ast/grammars.js.map +1 -1
  8. package/dist/codeScanner.d.ts +3 -0
  9. package/dist/codeScanner.d.ts.map +1 -1
  10. package/dist/codeScanner.js +7 -0
  11. package/dist/codeScanner.js.map +1 -1
  12. package/dist/index.d.ts +3 -2
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +3 -2
  15. package/dist/index.js.map +1 -1
  16. package/dist/sourceRedaction.d.ts.map +1 -1
  17. package/dist/sourceRedaction.js +667 -37
  18. package/dist/sourceRedaction.js.map +1 -1
  19. package/dist/vueSource.d.ts +17 -0
  20. package/dist/vueSource.d.ts.map +1 -0
  21. package/dist/vueSource.js +63 -0
  22. package/dist/vueSource.js.map +1 -0
  23. package/package.json +3 -1
  24. package/queries/cmake.tags.scm +6 -0
  25. package/queries/nix.tags.scm +4 -0
  26. package/queries/objcpp.tags.scm +4 -0
  27. package/queries/terraform.tags.scm +8 -0
  28. package/scripts/vendor-grammars.ts +468 -40
  29. package/src/ast/astChunker.ts +10 -1
  30. package/src/ast/grammars.ts +34 -1
  31. package/src/codeScanner.ts +6 -0
  32. package/src/index.ts +5 -0
  33. package/src/sourceRedaction.ts +726 -37
  34. package/src/vueSource.ts +77 -0
  35. package/wasm/bash.wasm +0 -0
  36. package/wasm/cmake.LICENSE +21 -0
  37. package/wasm/cmake.tags.scm +6 -0
  38. package/wasm/cmake.wasm +0 -0
  39. package/wasm/cpp.LICENSE +21 -0
  40. package/wasm/cpp.wasm +0 -0
  41. package/wasm/groovy.LICENSE +25 -0
  42. package/wasm/groovy.wasm +0 -0
  43. package/wasm/nix.LICENSE +21 -0
  44. package/wasm/nix.tags.scm +4 -0
  45. package/wasm/nix.wasm +0 -0
  46. package/wasm/objcpp.LICENSE +28 -0
  47. package/wasm/objcpp.tags.scm +4 -0
  48. package/wasm/objcpp.wasm +0 -0
  49. package/wasm/sql.wasm +0 -0
  50. package/wasm/terraform.LICENSE +201 -0
  51. package/wasm/terraform.tags.scm +8 -0
  52. package/wasm/terraform.wasm +0 -0
  53. package/wasm/tsx.wasm +0 -0
  54. package/wasm/typescript.wasm +0 -0
@@ -50,7 +50,7 @@ const OURS_DIR = fileURLToPath(new URL('../queries/', import.meta.url));
50
50
  interface GrammarSpec {
51
51
  /** Vendored artifact basename, and the id the registry keys on. */
52
52
  id: string;
53
- /** npm package or pinned source archive to fetch with npm pack. */
53
+ /** npm package/source archive, or the pinned ObjC++ source repository. */
54
54
  pkg: string;
55
55
  /**
56
56
  * `.wasm` basename inside the tarball when it differs from the package's own
@@ -73,9 +73,9 @@ interface GrammarSpec {
73
73
  * remove.
74
74
  *
75
75
  * Absent on purpose:
76
- * julia, objc, haskell - the three largest grammars that ALSO ship no tag
77
- * query, so each costs both its size (6.1/5.2/3.7 MB) and a hand-written
78
- * query. Dropped to keep the payload under 40 MB; still indexed via the
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
79
  * allowlist, still chunked heuristically.
80
80
  * dart - its bundled .wasm (package v1.0.0, 2023) fails Language.load under
81
81
  * web-tree-sitter 0.26.11 with a dylink-metadata error: the artifact
@@ -89,6 +89,20 @@ interface GrammarSpec {
89
89
  * unit; an AST would not improve on them.
90
90
  */
91
91
  const GRAMMARS: GrammarSpec[] = [
92
+ {
93
+ // Uy Ha 0.7.5, MIT; build the committed ABI-14 source without native dependencies.
94
+ id: 'cmake',
95
+ pkg: 'https://codeload.github.com/uyha/tree-sitter-cmake/tar.gz/58993af75218bc99a1f5a04c832a5937e7c422cb',
96
+ wasmFile: 'tree-sitter-cmake.wasm',
97
+ tags: 'ours',
98
+ },
99
+ {
100
+ // nix-community 0.3.0, MIT; use its committed generated ABI-15 parser unchanged.
101
+ id: 'nix',
102
+ pkg: 'https://codeload.github.com/nix-community/tree-sitter-nix/tar.gz/32c3ce98afdafc2ec9aedf0280a678750f76254c',
103
+ wasmFile: 'tree-sitter-nix.wasm',
104
+ tags: 'ours',
105
+ },
92
106
  {
93
107
  id: 'typescript',
94
108
  pkg: 'tree-sitter-typescript@0.23.2',
@@ -106,7 +120,15 @@ const GRAMMARS: GrammarSpec[] = [
106
120
  { id: 'go', pkg: 'tree-sitter-go', tags: 'upstream' },
107
121
  { id: 'java', pkg: 'tree-sitter-java', tags: 'upstream' },
108
122
  { id: 'c', pkg: 'tree-sitter-c', tags: 'upstream' },
109
- { id: 'cpp', pkg: 'tree-sitter-cpp', tags: 'upstream' },
123
+ // MIT 0.23.4, gitHead f41e1a044c8a84ea9fa8577fdd2eab92ec96de02.
124
+ { id: 'cpp', pkg: 'tree-sitter-cpp@0.23.4', tags: 'upstream' },
125
+ {
126
+ // MIT Objective-C++ 0.1.0; committed ABI-15 parser, not the pure Objective-C npm grammar.
127
+ id: 'objcpp',
128
+ pkg: 'https://tangled.org/dunkirk.sh/dots',
129
+ wasmFile: 'tree-sitter-objcpp.wasm',
130
+ tags: 'ours',
131
+ },
110
132
  {
111
133
  id: 'c-sharp',
112
134
  pkg: 'tree-sitter-c-sharp',
@@ -119,7 +141,8 @@ const GRAMMARS: GrammarSpec[] = [
119
141
  { id: 'scala', pkg: 'tree-sitter-scala', tags: 'upstream' },
120
142
  { id: 'elixir', pkg: 'tree-sitter-elixir', tags: 'upstream' },
121
143
  { id: 'solidity', pkg: 'tree-sitter-solidity', tags: 'upstream' },
122
- { id: 'bash', pkg: 'tree-sitter-bash', tags: 'ours' },
144
+ // MIT 0.25.1, gitHead 801326684a26ffc4e749bb016c50c6c30bdfa345.
145
+ { id: 'bash', pkg: 'tree-sitter-bash@0.25.1', tags: 'ours' },
123
146
  {
124
147
  // Upstream MIT 0.4.1, gitHead 816840c592ab973500ae9750763c707b447e7fef; shipped ABI-15 WASM.
125
148
  id: 'lua',
@@ -127,7 +150,15 @@ const GRAMMARS: GrammarSpec[] = [
127
150
  wasmFile: 'tree-sitter-lua.wasm',
128
151
  tags: 'upstream',
129
152
  },
130
- { id: 'groovy', pkg: 'tree-sitter-groovy', tags: 'ours' },
153
+ {
154
+ // Apache-2.0 v1.2.0, gitHead fad991865fee927dd1de5e172fb3f08ac674d914; packaged ABI-14 WASM.
155
+ id: 'terraform',
156
+ pkg: '@tree-sitter-grammars/tree-sitter-hcl@1.2.0',
157
+ wasmFile: 'tree-sitter-terraform.wasm',
158
+ tags: 'ours',
159
+ },
160
+ // MIT 0.1.2, gitHead 70efb0b9b50f95bcbd89dcfd42b275e0304e10cf.
161
+ { id: 'groovy', pkg: 'tree-sitter-groovy@0.1.2', tags: 'ours' },
131
162
  { id: 'powershell', pkg: 'tree-sitter-powershell', tags: 'ours' },
132
163
  {
133
164
  id: 'swift',
@@ -269,7 +300,37 @@ function rebuildTypescript(dir: string, work: string, only?: string): void {
269
300
  if (!grammar.includes(before)) throw new Error('TypeScript grammar patch no longer applies');
270
301
  grammar = grammar.replace(before, after);
271
302
  }
303
+ // Import types are primary types, so postfix [] applies without changing their expression aliases.
304
+ const importTypes = ` prec(-1, alias($._type_query_member_expression_in_type_annotation, $.member_expression)),
305
+ prec(-1, alias($._type_query_call_expression_in_type_annotation, $.call_expression)),
306
+ `;
307
+ const exportSpecifier = ` export_specifier: (_, previous) => seq(
308
+ optional(choice('type', 'typeof')),
309
+ previous,
310
+ ),`;
311
+ if (
312
+ !grammar.includes(importTypes) ||
313
+ !grammar.includes(exportSpecifier) ||
314
+ !grammar.includes(' primary_type: $ => choice(')
315
+ )
316
+ throw new Error('TypeScript import-type/export patch no longer applies');
317
+ grammar = grammar
318
+ .replace(importTypes, '')
319
+ .replace(
320
+ ' primary_type: $ => choice(',
321
+ ' primary_type: $ => choice(\n' + importTypes.trimEnd()
322
+ );
323
+ grammar = grammar.replace(
324
+ exportSpecifier,
325
+ ` export_specifier: $ => seq(
326
+ optional(choice('type', 'typeof')),
327
+ field('name', choice($._module_export_name, alias('type', $.identifier))),
328
+ optional(seq('as', field('alias', $._module_export_name))),
329
+ ),`
330
+ );
272
331
  writeFileSync(grammarPath, grammar);
332
+ // Retain our type/enum declaration captures when the vendor loop copies the query.
333
+ copyFileSync(join(WASM_DIR, 'typescript.tags.scm'), join(dir, 'queries', 'tags.scm'));
273
334
  const tools = join(work, 'typescript-build');
274
335
  execFileSync('npm', [
275
336
  'install',
@@ -284,7 +345,7 @@ function rebuildTypescript(dir: string, work: string, only?: string): void {
284
345
  const cli = join(tools, 'node_modules', '.bin', 'tree-sitter');
285
346
  for (const dialect of ['typescript', 'tsx']) {
286
347
  if (only && only !== dialect) continue;
287
- // TypeScript has no JSX; keep its existing WASM bytes unchanged.
348
+ // Apply the JSX scanner and attribute repairs only to TSX.
288
349
  if (dialect === 'tsx') {
289
350
  patchJsxScanner(join(dir, 'common', 'scanner.h'));
290
351
  patchJsxAttributes(join(tools, 'node_modules', 'tree-sitter-javascript', 'grammar.js'));
@@ -299,6 +360,7 @@ function rebuildTypescript(dir: string, work: string, only?: string): void {
299
360
  ['build', '--wasm', '--docker', '--output', join(dir, `tree-sitter-${dialect}.wasm`)],
300
361
  options
301
362
  );
363
+ chmodSync(join(dir, `tree-sitter-${dialect}.wasm`), 0o644);
302
364
  }
303
365
  }
304
366
 
@@ -413,6 +475,52 @@ ${continuation}`
413
475
  copyFileSync(join(dir, 'LICENSE'), join(WASM_DIR, 'swift.LICENSE'));
414
476
  }
415
477
 
478
+ /** Build committed generated C without installing the dots repository or its grammar ancestors. */
479
+ function vendorObjcpp(work: string, repository: string): string {
480
+ const root = join(work, 'objcpp-source');
481
+ mkdirSync(root);
482
+ const git = (args: string[]): void => {
483
+ execFileSync('git', args, { cwd: root });
484
+ };
485
+ git(['init', '--quiet']);
486
+ git(['remote', 'add', 'origin', repository]);
487
+ git(['config', 'remote.origin.promisor', 'true']);
488
+ git(['config', 'remote.origin.partialclonefilter', 'blob:none']);
489
+ git(['sparse-checkout', 'set', '--no-cone', '/LICENSE.md', '/packages/tree-sitter-objcpp/']);
490
+ git([
491
+ 'fetch',
492
+ '--quiet',
493
+ '--depth=1',
494
+ '--filter=blob:none',
495
+ 'origin',
496
+ '8c861a98d84c6325a0300ec88dfa334aa6ab150e',
497
+ ]);
498
+ git(['checkout', '--quiet', '--detach', 'FETCH_HEAD']);
499
+ const dir = join(root, 'packages', 'tree-sitter-objcpp');
500
+ const tools = join(work, 'objcpp-build');
501
+ execFileSync('npm', [
502
+ 'install',
503
+ '--prefix',
504
+ tools,
505
+ '--no-audit',
506
+ '--no-fund',
507
+ '--no-package-lock',
508
+ 'tree-sitter-cli@0.24.4',
509
+ ]);
510
+ // Upstream generated this ABI-15 parser with CLI 0.25.10; 0.24.4 builds it with emsdk 3.1.64.
511
+ execFileSync(
512
+ join(tools, 'node_modules', '.bin', 'tree-sitter'),
513
+ ['build', '--wasm', '--docker', '--output', join(dir, 'tree-sitter-objcpp.wasm')],
514
+ { cwd: dir }
515
+ );
516
+ writeFileSync(
517
+ join(WASM_DIR, 'objcpp.LICENSE'),
518
+ readFileSync(join(root, 'LICENSE.md'), 'utf8').replace(/[\u201c\u201d]/g, '"') +
519
+ '\nInherited C/C++ parser and scanner portions: Copyright (c) 2014 Max Brunsfeld.\nThe MIT grant and conditions above apply to these portions too.\n'
520
+ );
521
+ return dir;
522
+ }
523
+
416
524
  /** Upstream package.json pins CLI 0.24.7. SQL function bodies only, not general PL/pgSQL. */
417
525
  function rebuildSql(dir: string, work: string): void {
418
526
  const grammarPath = join(dir, 'grammar.js');
@@ -473,6 +581,26 @@ function rebuildSql(dir: string, work: string): void {
473
581
 
474
582
  drop_table: $ => seq(`
475
583
  );
584
+ // Only the policy clauses and RLS action observed in the four fixed PostgreSQL migrations.
585
+ patch(' $.create_trigger,', ' $.create_trigger,\n $.create_policy,');
586
+ patch(
587
+ ' _alter_specifications: $ => choice(',
588
+ ` _alter_specifications: $ => choice(
589
+ seq(make_keyword("enable"), $.keyword_row, $.keyword_level, $.keyword_security),`
590
+ );
591
+ patch(
592
+ ' create_table: $ => prec.left(',
593
+ ` policy_relation: $ => $.object_reference,
594
+ create_policy: $ => seq(
595
+ $.keyword_create, make_keyword("policy"), field('name', $.identifier),
596
+ $.keyword_on, alias($.policy_relation, $.relation),
597
+ optional(seq($.keyword_for, choice($.keyword_select, $.keyword_insert, $.keyword_update, $.keyword_delete))),
598
+ optional(seq($.keyword_to, $.identifier)),
599
+ optional(seq($.keyword_using, wrapped_in_parenthesis($._expression))),
600
+ optional(seq($.keyword_with, $.keyword_check, wrapped_in_parenthesis($._expression))),
601
+ ),
602
+ create_table: $ => prec.left(`
603
+ );
476
604
  // Dollar-quoted procedural code is one literal, never a parsed SQL statement list.
477
605
  patch(' $.reset_statement,', ' $.reset_statement,\n $.do_statement,');
478
606
  patch(
@@ -481,7 +609,20 @@ function rebuildSql(dir: string, work: string): void {
481
609
 
482
610
  _cte: $ => seq(`
483
611
  );
484
- patch(' conflicts: $ => [', ' conflicts: $ => [\n [$.create_function],');
612
+ // Keep both parses until NOT NULL (constraint) or NOT LIKE (expression) is known.
613
+ // Match _expression's precedence so it cannot discard the completed default first.
614
+ patch(
615
+ ' _inner_default_expression: $ => choice(',
616
+ ' _inner_default_expression: $ => prec(1, choice('
617
+ );
618
+ patch(
619
+ 'alias($.implicit_cast, $.cast),\n ),\n\n constraints:',
620
+ 'alias($.implicit_cast, $.cast),\n )),\n\n constraints:'
621
+ );
622
+ patch(
623
+ ' conflicts: $ => [',
624
+ ' conflicts: $ => [\n [$._expression, $._inner_default_expression],\n [$.create_function],'
625
+ );
485
626
  const functionStart = grammar.indexOf(' create_function: $ => seq(');
486
627
  const functionEnd = grammar.indexOf(' _function_return:', functionStart);
487
628
  if (functionStart < 0 || functionEnd < 0) throw new Error('SQL function patch no longer applies');
@@ -490,13 +631,12 @@ function rebuildSql(dir: string, work: string): void {
490
631
  const optionsStart = original.indexOf(' repeat(');
491
632
  const optionsEnd = original.indexOf(' // ensure');
492
633
  const options = original.slice(optionsStart, optionsEnd).replace(languageOption, '');
493
- // A dedicated keyword selects the opaque branch; ordinary LANGUAGE SQL keeps its AST.
494
634
  const procedural =
495
635
  original.slice(0, optionsStart) +
496
636
  options +
497
- ` alias(seq($.keyword_language, alias(make_keyword("plpgsql"), $.identifier)), $.function_language),\n` +
637
+ ' alias($.plpgsql_function_language, $.function_language),\n' +
498
638
  options +
499
- ` alias(seq($.keyword_as, alias(choice($._dollar_quoted_string, $._single_quote_string, $._double_quote_string), $.literal)), $.function_body),\n` +
639
+ ' alias($.opaque_function_body, $.function_body),\n' +
500
640
  options +
501
641
  ' )';
502
642
  patch(
@@ -506,7 +646,94 @@ function rebuildSql(dir: string, work: string): void {
506
646
  original.slice(original.indexOf('seq('), -1) +
507
647
  '),'
508
648
  );
649
+ patch(
650
+ ' $._dollar_quoted_string,\n ],',
651
+ ' $._dollar_quoted_string,\n $._trailing_plpgsql_body,\n ],'
652
+ );
653
+ // Named rules retain complete body/language ranges; aliasing a sequence only names its first token.
654
+ patch(
655
+ ' function_body: $ => choice(',
656
+ ` opaque_function_body: $ => seq(
657
+ $.keyword_as,
658
+ alias(choice($._dollar_quoted_string, $._single_quote_string, $._double_quote_string), $.literal),
659
+ ),
660
+ plpgsql_function_language: $ => seq(
661
+ $.keyword_language, alias(make_keyword("plpgsql"), $.identifier),
662
+ ),
663
+ function_body: $ => choice(
664
+ seq(
665
+ $.keyword_as,
666
+ alias($._dollar_quoted_string_start_tag, $.dollar_quote),
667
+ alias($._trailing_plpgsql_body, $.literal),
668
+ alias($._dollar_quoted_string_end_tag, $.dollar_quote),
669
+ ),`
670
+ );
671
+ const languageStart = grammar.indexOf(' function_language: $ => seq(');
672
+ const languageEnd = grammar.indexOf(' function_volatility:', languageStart);
673
+ if (languageStart < 0 || languageEnd < 0) throw new Error('SQL language patch no longer applies');
674
+ const language = grammar.slice(languageStart, languageEnd);
675
+ patch(
676
+ language,
677
+ language.replace(
678
+ ' $.identifier',
679
+ ` choice(
680
+ $.identifier,
681
+ alias(token(prec(1, seq("'", make_keyword("plpgsql"), "'"))), $.identifier),
682
+ )`
683
+ )
684
+ );
509
685
  writeFileSync(grammarPath, grammar);
686
+ const scannerPath = join(dir, 'src', 'scanner.c');
687
+ let scanner = readFileSync(scannerPath, 'utf8');
688
+ const token = ' DOLLAR_QUOTED_STRING\n';
689
+ const start =
690
+ ' if (valid_symbols[DOLLAR_QUOTED_STRING_START_TAG] && state->start_tag == NULL) {';
691
+ if (!scanner.includes(token) || !scanner.includes(start))
692
+ throw new Error('SQL procedural scanner patch no longer applies');
693
+ scanner = scanner.replace(token, ' DOLLAR_QUOTED_STRING,\n TRAILING_PLPGSQL_BODY\n');
694
+ scanner = scanner.replace(
695
+ start,
696
+ () =>
697
+ ` if (valid_symbols[TRAILING_PLPGSQL_BODY] && state->start_tag != NULL) {
698
+ bool consumed = false;
699
+ for (;;) {
700
+ if (lexer->eof(lexer)) return false;
701
+ if (lexer->lookahead != '$') {
702
+ lexer->advance(lexer, false);
703
+ consumed = true;
704
+ continue;
705
+ }
706
+ lexer->mark_end(lexer);
707
+ char *tag = scan_dollar_string_tag(lexer);
708
+ bool closing = tag != NULL && strcmp(tag, state->start_tag) == 0;
709
+ free(tag);
710
+ if (closing) break;
711
+ consumed = true;
712
+ }
713
+ if (!consumed) return false;
714
+ // Look ahead without moving the marked body end; SQL bodies keep their normal AST.
715
+ while (iswspace(lexer->lookahead)) lexer->advance(lexer, false);
716
+ const char *keyword = "language";
717
+ for (unsigned i = 0; keyword[i]; i++) {
718
+ if (towlower(lexer->lookahead) != keyword[i]) return false;
719
+ lexer->advance(lexer, false);
720
+ }
721
+ while (iswspace(lexer->lookahead)) lexer->advance(lexer, false);
722
+ bool quoted = lexer->lookahead == 39;
723
+ if (quoted) lexer->advance(lexer, false);
724
+ const char *language = "plpgsql";
725
+ for (unsigned i = 0; language[i]; i++) {
726
+ if (towlower(lexer->lookahead) != language[i]) return false;
727
+ lexer->advance(lexer, false);
728
+ }
729
+ if (quoted ? lexer->lookahead != 39 : (iswalnum(lexer->lookahead) || lexer->lookahead == '_' || lexer->lookahead == '$')) return false;
730
+ lexer->result_symbol = TRAILING_PLPGSQL_BODY;
731
+ return true;
732
+ }
733
+
734
+ ` + start
735
+ );
736
+ writeFileSync(scannerPath, scanner);
510
737
  const tools = join(work, 'sql-build');
511
738
  execFileSync('npm', [
512
739
  'install',
@@ -527,6 +754,188 @@ function rebuildSql(dir: string, work: string): void {
527
754
  copyFileSync(join(dir, 'LICENSE'), join(WASM_DIR, 'sql.LICENSE'));
528
755
  }
529
756
 
757
+ /** Permit existing dotted property expressions as command-call arguments. */
758
+ function rebuildGroovy(dir: string, work: string): void {
759
+ const path = join(dir, 'grammar.js');
760
+ let source = readFileSync(path, 'utf8');
761
+ const before =
762
+ ' _juxt_argument_list: $ => commaSep1(choice($._literal, $.map_item, $.identifier)),';
763
+ if (!source.includes(before)) throw new Error('Groovy argument patch no longer applies');
764
+ source = source.replace(before, before.replace('$.identifier)', '$.identifier, $.field_access)'));
765
+ const conflicts = ' conflicts: ($, original) => original.concat([';
766
+ if (!source.includes(conflicts)) throw new Error('Groovy conflict patch no longer applies');
767
+ source = source.replace(
768
+ conflicts,
769
+ conflicts +
770
+ `
771
+ [$.method_invocation, $._juxt_argument_list],
772
+ [$.generic_type, $._juxt_argument_list],
773
+ [$.primary_expression, $._unannotated_type, $._juxt_argument_list],
774
+ [$._unannotated_type, $._juxt_argument_list],
775
+ [$.primary_expression, $._juxt_argument_list],`
776
+ );
777
+ writeFileSync(path, source);
778
+ const tools = join(work, 'groovy-build');
779
+ execFileSync('npm', [
780
+ 'install',
781
+ '--prefix',
782
+ tools,
783
+ '--no-audit',
784
+ '--no-fund',
785
+ '--no-package-lock',
786
+ 'tree-sitter-cli@0.24.4',
787
+ 'tree-sitter-java@0.23.4',
788
+ ]);
789
+ const cli = join(tools, 'node_modules', '.bin', 'tree-sitter');
790
+ const options = { cwd: dir, env: { ...process.env, NODE_PATH: join(tools, 'node_modules') } };
791
+ execFileSync(cli, ['generate'], options);
792
+ execFileSync(
793
+ cli,
794
+ ['build', '--wasm', '--docker', '--output', join(dir, 'tree-sitter-groovy.wasm')],
795
+ options
796
+ );
797
+ // Upstream publishes its MIT declaration in package.json/grammar.js, with no LICENSE file.
798
+ writeFileSync(
799
+ join(WASM_DIR, 'groovy.LICENSE'),
800
+ 'tree-sitter-groovy 0.1.2, by Amaan Qureshi <amaanq12@gmail.com>.\n' +
801
+ 'Upstream package.json and grammar.js declare MIT. Source: https://github.com/amaanq/tree-sitter-groovy/tree/70efb0b9b50f95bcbd89dcfd42b275e0304e10cf\n\n' +
802
+ 'Inherited tree-sitter-java 0.23.4 (MIT):\n' +
803
+ readFileSync(join(tools, 'node_modules', 'tree-sitter-java', 'LICENSE'), 'utf8')
804
+ );
805
+ }
806
+
807
+ /** Build the official committed Nix parser without installing its native Node binding. */
808
+ function rebuildNixOrCmake(dir: string, work: string, id: 'nix' | 'cmake'): void {
809
+ const tools = join(work, `${id}-build`);
810
+ execFileSync('npm', [
811
+ 'install',
812
+ '--prefix',
813
+ tools,
814
+ '--no-audit',
815
+ '--no-fund',
816
+ '--no-package-lock',
817
+ 'tree-sitter-cli@0.24.4',
818
+ ]);
819
+ // Build the pinned generated source; CLI 0.24.4 pins emscripten/emsdk:3.1.64.
820
+ execFileSync(
821
+ join(tools, 'node_modules', '.bin', 'tree-sitter'),
822
+ ['build', '--wasm', '--docker', '--output', join(dir, `tree-sitter-${id}.wasm`)],
823
+ { cwd: dir }
824
+ );
825
+ copyFileSync(join(dir, 'LICENSE'), join(WASM_DIR, `${id}.LICENSE`));
826
+ }
827
+
828
+ /** The published grammar JSON freezes its C ancestor; add only the two JNI modifier spellings. */
829
+ function rebuildCpp(dir: string, work: string): void {
830
+ const path = join(dir, 'src', 'grammar.json');
831
+ const grammar = JSON.parse(readFileSync(path, 'utf8')) as {
832
+ rules: Record<string, { type: string; members: unknown[] }>;
833
+ };
834
+ for (const [name, value] of [
835
+ ['type_qualifier', 'JNIEXPORT'],
836
+ ['ms_call_modifier', 'JNICALL'],
837
+ ] as const) {
838
+ const rule = grammar.rules[name];
839
+ if (!rule || rule.type !== 'CHOICE' || !Array.isArray(rule.members))
840
+ throw new Error(`C++ JNI patch no longer applies: ${name}`);
841
+ rule.members.push({ type: 'STRING', value });
842
+ }
843
+ writeFileSync(path, JSON.stringify(grammar));
844
+ const tools = join(work, 'cpp-build');
845
+ execFileSync('npm', [
846
+ 'install',
847
+ '--prefix',
848
+ tools,
849
+ '--no-audit',
850
+ '--no-fund',
851
+ '--no-package-lock',
852
+ 'tree-sitter-cli@0.24.4',
853
+ ]);
854
+ const cli = join(tools, 'node_modules', '.bin', 'tree-sitter');
855
+ execFileSync(cli, ['generate', 'src/grammar.json'], { cwd: dir });
856
+ execFileSync(
857
+ cli,
858
+ ['build', '--wasm', '--docker', '--output', join(dir, 'tree-sitter-cpp.wasm')],
859
+ { cwd: dir }
860
+ );
861
+ copyFileSync(join(dir, 'LICENSE'), join(WASM_DIR, 'cpp.LICENSE'));
862
+ }
863
+
864
+ /** A radix value may use $name as well as ${name}; keep the source and expansion node intact. */
865
+ function rebuildBash(dir: string, work: string): void {
866
+ const path = join(dir, 'grammar.js');
867
+ const grammar = readFileSync(path, 'utf8');
868
+ const before = 'seq(/-?(0x)?[0-9]+#/, choice($.expansion, $.command_substitution))';
869
+ if (!grammar.includes(before)) throw new Error('Bash radix patch no longer applies');
870
+ writeFileSync(
871
+ path,
872
+ grammar.replace(
873
+ before,
874
+ 'seq(/-?(0x)?[0-9]+#/, choice($.simple_expansion, $.expansion, $.command_substitution))'
875
+ )
876
+ );
877
+ const scannerPath = join(dir, 'src', 'scanner.c');
878
+ const scanner = readFileSync(scannerPath, 'utf8');
879
+ const variableScan = ` return false;
880
+ }
881
+
882
+ if (valid_symbols[BARE_DOLLAR] && !in_error_recovery(valid_symbols) && scan_bare_dollar(lexer)) {`;
883
+ if (!scanner.includes(variableScan))
884
+ throw new Error('Bash expansion-word patch no longer applies');
885
+ const parenStart = scanner.indexOf(
886
+ " if (lexer->lookahead == '(' && !(advanced_once || advance_once_space)) {"
887
+ );
888
+ const parenEnd = scanner.indexOf(
889
+ String.raw` if (lexer->lookahead == '\'') {`,
890
+ parenStart
891
+ );
892
+ const concatParens = "lexer->lookahead == ')' || lexer->lookahead == '('";
893
+ if (parenStart < 0 || parenEnd <= parenStart || !scanner.includes(concatParens))
894
+ throw new Error('Bash literal-parenthesis patch no longer applies');
895
+ // The variable-name scan consumes a default's prefix but rejects | before reaching expansion_word.
896
+ // Inside ${...}, parentheses are word pieces around expansions, not shell groups.
897
+ writeFileSync(
898
+ scannerPath,
899
+ (scanner.slice(0, parenStart) + scanner.slice(parenEnd))
900
+ .replace(
901
+ variableScan,
902
+ " if (valid_symbols[EXPANSION_WORD] && lexer->lookahead == '|') goto expansion_word;\n\n" +
903
+ variableScan
904
+ )
905
+ .replace(
906
+ concatParens,
907
+ "((lexer->lookahead == ')' || lexer->lookahead == '(') && !valid_symbols[CLOSING_BRACE])"
908
+ )
909
+ );
910
+ const generator = join(work, 'bash-generator');
911
+ execFileSync('npm', [
912
+ 'install',
913
+ '--prefix',
914
+ generator,
915
+ '--no-audit',
916
+ '--no-fund',
917
+ '--no-package-lock',
918
+ 'tree-sitter-cli@0.25.8',
919
+ ]);
920
+ execFileSync(join(generator, 'node_modules', '.bin', 'tree-sitter'), ['generate'], { cwd: dir });
921
+ const tools = join(work, 'bash-build');
922
+ execFileSync('npm', [
923
+ 'install',
924
+ '--prefix',
925
+ tools,
926
+ '--no-audit',
927
+ '--no-fund',
928
+ '--no-package-lock',
929
+ 'tree-sitter-cli@0.24.4',
930
+ ]);
931
+ // CLI 0.24.4 pins emscripten/emsdk:3.1.64 for --docker.
932
+ execFileSync(
933
+ join(tools, 'node_modules', '.bin', 'tree-sitter'),
934
+ ['build', '--wasm', '--docker', '--output', join(dir, 'tree-sitter-bash.wasm')],
935
+ { cwd: dir }
936
+ );
937
+ }
938
+
530
939
  function vendor(): Problem[] {
531
940
  const only = process.argv.find((arg) => arg.startsWith('--only='))?.slice(7);
532
941
  if (only && !GRAMMARS.some((spec) => spec.id === only))
@@ -544,28 +953,36 @@ function vendor(): Problem[] {
544
953
  let dir = extracted.get(spec.pkg);
545
954
  if (!dir) {
546
955
  try {
547
- const out = execFileSync(
548
- 'npm',
549
- ['pack', spec.pkg, '--silent', '--pack-destination', work],
550
- {
551
- cwd: work,
552
- encoding: 'utf8',
553
- }
554
- );
555
- const tarball = out.trim().split('\n').pop() ?? '';
556
- dir = join(work, spec.pkg.replace(/[^a-z0-9-]/gi, '_'));
557
- mkdirSync(dir, { recursive: true });
558
- execFileSync('tar', [
559
- 'xzf',
560
- join(work, basename(tarball)),
561
- '-C',
562
- dir,
563
- '--strip-components=1',
564
- ]);
565
- if (spec.id === 'typescript' || spec.id === 'tsx') rebuildTypescript(dir, work, only);
566
- if (spec.id === 'javascript') rebuildJavascript(dir, work);
567
- if (spec.id === 'swift') rebuildSwift(dir, work);
568
- if (spec.id === 'sql') rebuildSql(dir, work);
956
+ if (spec.id === 'objcpp') {
957
+ dir = vendorObjcpp(work, spec.pkg);
958
+ } else {
959
+ const out = execFileSync(
960
+ 'npm',
961
+ ['pack', spec.pkg, '--silent', '--pack-destination', work],
962
+ {
963
+ cwd: work,
964
+ encoding: 'utf8',
965
+ }
966
+ );
967
+ const tarball = out.trim().split('\n').pop() ?? '';
968
+ dir = join(work, spec.pkg.replace(/[^a-z0-9-]/gi, '_'));
969
+ mkdirSync(dir, { recursive: true });
970
+ execFileSync('tar', [
971
+ 'xzf',
972
+ join(work, basename(tarball)),
973
+ '-C',
974
+ dir,
975
+ '--strip-components=1',
976
+ ]);
977
+ if (spec.id === 'typescript' || spec.id === 'tsx') rebuildTypescript(dir, work, only);
978
+ if (spec.id === 'javascript') rebuildJavascript(dir, work);
979
+ if (spec.id === 'swift') rebuildSwift(dir, work);
980
+ if (spec.id === 'sql') rebuildSql(dir, work);
981
+ if (spec.id === 'groovy') rebuildGroovy(dir, work);
982
+ if (spec.id === 'cpp') rebuildCpp(dir, work);
983
+ if (spec.id === 'bash') rebuildBash(dir, work);
984
+ if (spec.id === 'nix' || spec.id === 'cmake') rebuildNixOrCmake(dir, work, spec.id);
985
+ }
569
986
  extracted.set(spec.pkg, dir);
570
987
  } catch (err) {
571
988
  problems.push({
@@ -584,7 +1001,18 @@ function vendor(): Problem[] {
584
1001
  } else {
585
1002
  copyFileSync(wasmSrc, join(WASM_DIR, `${spec.id}.wasm`));
586
1003
  if (spec.id === 'lua') copyFileSync(join(dir, 'LICENSE.md'), join(WASM_DIR, 'lua.LICENSE'));
587
- if (spec.id === 'javascript' || spec.id === 'tsx')
1004
+ if (spec.id === 'terraform')
1005
+ copyFileSync(join(dir, 'LICENSE'), join(WASM_DIR, 'terraform.LICENSE'));
1006
+ if (
1007
+ spec.id === 'javascript' ||
1008
+ spec.id === 'tsx' ||
1009
+ spec.id === 'objcpp' ||
1010
+ spec.id === 'groovy' ||
1011
+ spec.id === 'cpp' ||
1012
+ spec.id === 'bash' ||
1013
+ spec.id === 'nix' ||
1014
+ spec.id === 'cmake'
1015
+ )
588
1016
  chmodSync(join(WASM_DIR, `${spec.id}.wasm`), 0o644);
589
1017
  }
590
1018
 
@@ -629,13 +1057,13 @@ if (problems.length > 0) {
629
1057
  }
630
1058
 
631
1059
  const sizes = GRAMMARS.map((g) => statSync(join(WASM_DIR, `${g.id}.wasm`)).size);
632
- const totalMB = +(sizes.reduce((a, b) => a + b, 0) / 1048576).toFixed(1);
1060
+ const totalBytes = sizes.reduce((a, b) => a + b, 0);
1061
+ const totalMB = +(totalBytes / 1048576).toFixed(1);
633
1062
  console.log(
634
1063
  `VENDOR_GRAMMARS_OK ${JSON.stringify({ grammars: GRAMMARS.length, totalWasmMB: totalMB })}`
635
1064
  );
636
- // The plan's ceiling. Install size is a cost every user pays, so it fails the
637
- // build rather than drifting upward one grammar at a time.
638
- if (totalMB > 40) {
639
- console.error(`VENDOR_GRAMMARS_TOO_LARGE ${totalMB}MB exceeds the 40MB ceiling`);
1065
+ // Owner-approved Objective-C++ cost: repaired bundle 43.58 MiB (43.67 with Terraform 667475e93), bounded at 45 MiB.
1066
+ if (totalBytes > 45 * 1048576) {
1067
+ console.error(`VENDOR_GRAMMARS_TOO_LARGE ${totalMB}MB exceeds the 45MB ceiling`);
640
1068
  process.exit(1);
641
1069
  }
@@ -57,7 +57,12 @@
57
57
 
58
58
  import type { Node, Query, Tree } from 'web-tree-sitter';
59
59
  import { Parser } from 'web-tree-sitter';
60
- import { loadGrammar, type AstLanguageId, type GrammarBinding } from './grammars.js';
60
+ import {
61
+ loadGrammar,
62
+ objcppSyntaxIssue,
63
+ type AstLanguageId,
64
+ type GrammarBinding,
65
+ } from './grammars.js';
61
66
 
62
67
  export interface AstChunk {
63
68
  /** Verbatim contiguous source: exactly the lines `startLine..endLine`. */
@@ -298,6 +303,10 @@ export async function chunkWithAst(
298
303
  const parseMs = performance.now() - startedAt;
299
304
 
300
305
  try {
306
+ if (languageId === 'objcpp') {
307
+ const issue = objcppSyntaxIssue(tree.rootNode);
308
+ if (issue) return { unsupported: 'parse_failed', detail: issue };
309
+ }
301
310
  const lines = content.split('\n');
302
311
  // A trailing newline TERMINATES the last line, it does not open a new one,
303
312
  // but `split` yields a phantom '' for it. Dropping it here - before