@appthreat/atom-parsetools 1.2.1 → 1.3.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.
- package/README.md +14 -0
- package/astgen.js +558 -107
- package/package.json +10 -7
- package/plugins/composer/installed.php +6 -6
- package/plugins/rubyastgen/bundle/ruby/4.0.0/extensions/x86_64-linux/4.0.0/prism-1.9.0/gem_make.out +5 -5
- package/plugins/rubyastgen/bundle/ruby/4.0.0/extensions/x86_64-linux/4.0.0/racc-1.8.1/gem_make.out +5 -5
package/README.md
CHANGED
|
@@ -7,6 +7,10 @@ This package hosts a collection of parsing tools that complement the `@appthreat
|
|
|
7
7
|
- rbastgen - Generates AST for Ruby projects using the AppThreat's `ruby_ast_gen` gem
|
|
8
8
|
- scalasem - Generates a custom semantics slice for Scala Projects by utilising scalac command.
|
|
9
9
|
|
|
10
|
+
## Runtime support
|
|
11
|
+
|
|
12
|
+
These tools run on both [Node.js](https://nodejs.org) (>= 22, required by `@babel/parser` 8) and [Bun](https://bun.sh). All commands and the accompanying regression test-suite are exercised under both runtimes in CI, so the commands below can be invoked with either `node` or `bun` interchangeably (for example `bun astgen.js -i .`).
|
|
13
|
+
|
|
10
14
|
## Command usages
|
|
11
15
|
|
|
12
16
|
### astgen
|
|
@@ -25,6 +29,16 @@ Options:
|
|
|
25
29
|
-h Show help [boolean]
|
|
26
30
|
```
|
|
27
31
|
|
|
32
|
+
#### Environment variables
|
|
33
|
+
|
|
34
|
+
| Variable | Default | Purpose |
|
|
35
|
+
| --------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
36
|
+
| `ASTGEN_TYPE_WORKERS` | `1` (off) | Number of worker threads for the TypeScript type-generation phase, or `auto` to derive it from the available CPUs. The TypeScript checker is single-threaded, so parallelism comes from sharding files across workers, each building its own program. **Opt-in:** sharding changes TypeScript's internal type-id ordering, which reorders the members of a small number of inferred union types (e.g. `A \| B` → `B \| A`; semantically identical). Leave unset for byte-identical output; set it (e.g. `auto` or `8`) to trade that cosmetic reordering for a large speedup on big projects. |
|
|
37
|
+
| `ASTGEN_INCLUDE_TEST_FILES` | `false` | When `true`, do not exclude test files (`*.poku.js`, `*.test.*`, `*.spec.*`, `*.e2e.*`, `__tests__/`, `__mocks__/`) from AST and type generation. They are excluded by default because they are typically the heaviest, lowest-value inputs for type generation. |
|
|
38
|
+
| `ASTGEN_CONCURRENCY` | `10` | Chunk size for the in-thread file loop (bounds peak memory between `gc()` passes). |
|
|
39
|
+
| `ASTGEN_INCLUDE_NODE_MODULES_BUNDLES` | `false` | When `true`, also parse bundled entrypoints inside `node_modules` (files matching `*.(bundle\|dist\|index\|min\|app).(js\|cjs\|mjs)`). Off by default; `node_modules` is otherwise skipped entirely. |
|
|
40
|
+
| `ASTGEN_IGNORE_DIRS` | unset | Comma/space-separated list of directories to ignore. As a side effect, when it is set and does **not** contain `node_modules`, the `node_modules` bundle entrypoints above are included (equivalent to `ASTGEN_INCLUDE_NODE_MODULES_BUNDLES=true`). |
|
|
41
|
+
|
|
28
42
|
### phpastgen
|
|
29
43
|
|
|
30
44
|
```text
|
package/astgen.js
CHANGED
|
@@ -1,21 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { join, dirname, relative, resolve } from "path";
|
|
3
|
+
import { join, dirname, relative, resolve, basename } from "path";
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
5
|
import { parse } from "@babel/parser";
|
|
6
6
|
import { parse as parseHermes } from "hermes-parser";
|
|
7
|
-
|
|
7
|
+
// TypeScript 7.0 (the native Go port) ships without a programmatic compiler
|
|
8
|
+
// API, which astgen relies on heavily (createProgram, getTypeChecker,
|
|
9
|
+
// forEachChild, SyntaxKind, TypeFormatFlags, ...). The officially supported
|
|
10
|
+
// bridge until the API returns in TS 7.1 is the @typescript/typescript6
|
|
11
|
+
// package, which is the same TS 6 engine and therefore keeps AST shapes and
|
|
12
|
+
// type-inference accuracy identical.
|
|
13
|
+
import tsc from "@typescript/typescript6";
|
|
14
|
+
import { tmpdir, cpus, availableParallelism } from "os";
|
|
15
|
+
import { Worker, isMainThread, parentPort, workerData } from "worker_threads";
|
|
8
16
|
import {
|
|
9
17
|
readFileSync,
|
|
10
18
|
mkdirSync,
|
|
11
19
|
writeFileSync,
|
|
12
20
|
accessSync,
|
|
13
21
|
constants,
|
|
14
|
-
existsSync
|
|
22
|
+
existsSync,
|
|
23
|
+
mkdtempSync,
|
|
24
|
+
rmSync
|
|
15
25
|
} from "fs";
|
|
16
26
|
import { getAllFiles } from "@appthreat/atom-common";
|
|
17
27
|
|
|
18
|
-
|
|
28
|
+
// Printed by `astgen --version`. Downstream frontends (e.g. chen's jssrc2cpg)
|
|
29
|
+
// fold this into their parse-cache fingerprint, so it MUST be bumped whenever
|
|
30
|
+
// the emitted AST/type shape changes — otherwise stale cached parses from an
|
|
31
|
+
// older astgen are silently reused. Bumped for the Babel 8 AST-shape change.
|
|
32
|
+
const ASTGEN_VERSION = "4.1.0";
|
|
19
33
|
|
|
20
34
|
const HELP_TEXT = `Options:
|
|
21
35
|
-i, --src Source directory [default: "."]
|
|
@@ -294,7 +308,10 @@ const makeBabelOptions = (
|
|
|
294
308
|
baseOptions,
|
|
295
309
|
file,
|
|
296
310
|
extraPlugins = [],
|
|
297
|
-
{
|
|
311
|
+
{
|
|
312
|
+
enableJsxSyntax = shouldEnableJsxSyntax(file),
|
|
313
|
+
disallowAmbiguousJSXLike
|
|
314
|
+
} = {}
|
|
298
315
|
) => ({
|
|
299
316
|
...baseOptions,
|
|
300
317
|
plugins: mergeBabelPlugins(
|
|
@@ -375,6 +392,25 @@ const babelSafeFlowParserOptions = {
|
|
|
375
392
|
]
|
|
376
393
|
};
|
|
377
394
|
|
|
395
|
+
// Test files (e.g. *.poku.js, *.test.ts, *.spec.js, __tests__/*) are
|
|
396
|
+
// test-runner artifacts that are typically the heaviest, lowest-value inputs
|
|
397
|
+
// for type generation (each is often a full twin of a source module wrapped in
|
|
398
|
+
// test scaffolding). They are excluded by default to keep the type-generation
|
|
399
|
+
// phase scalable. Set ASTGEN_INCLUDE_TEST_FILES=true to restore them (e.g. when
|
|
400
|
+
// a downstream consumer wants test files analysed).
|
|
401
|
+
const shouldIncludeTestFiles =
|
|
402
|
+
process.env?.ASTGEN_INCLUDE_TEST_FILES === "true";
|
|
403
|
+
|
|
404
|
+
const TEST_FILE_EXT = "(?:js|jsx|cjs|mjs|ts|tsx|mts|cts)";
|
|
405
|
+
const TEST_FILE_PATTERN = new RegExp(
|
|
406
|
+
`(?:\\.(?:poku|test|spec|e2e|integration|it)\\.${TEST_FILE_EXT}$` +
|
|
407
|
+
`|[\\\\/]__(?:tests|mocks)__[\\\\/])`,
|
|
408
|
+
"i"
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
const isExcludedTestFile = (file) =>
|
|
412
|
+
!shouldIncludeTestFiles && TEST_FILE_PATTERN.test(file);
|
|
413
|
+
|
|
378
414
|
const shouldIncludeNodeModulesBundles =
|
|
379
415
|
process.env?.ASTGEN_INCLUDE_NODE_MODULES_BUNDLES === "true" ||
|
|
380
416
|
(process.env?.ASTGEN_IGNORE_DIRS &&
|
|
@@ -420,16 +456,18 @@ const getAllSrcJSAndTSFiles = (src) => {
|
|
|
420
456
|
// Step 2: Combine both lists
|
|
421
457
|
return Promise.all([allFilesPromise, bundledFilesPromise]).then(
|
|
422
458
|
([allFiles, bundledFiles]) =>
|
|
423
|
-
[...new Set([...allFiles, ...bundledFiles])]
|
|
459
|
+
[...new Set([...allFiles, ...bundledFiles])]
|
|
460
|
+
.filter((file) => !isExcludedTestFile(file))
|
|
461
|
+
.sort()
|
|
424
462
|
);
|
|
425
463
|
};
|
|
426
464
|
|
|
427
465
|
/**
|
|
428
466
|
* Convert a single JS/TS file to AST
|
|
429
467
|
*/
|
|
430
|
-
const fileToJsAst = (file, projectType) => {
|
|
468
|
+
const fileToJsAst = (file, projectType, tsInstance) => {
|
|
431
469
|
if (file.endsWith(".vue") || file.endsWith(".svelte")) {
|
|
432
|
-
return toVueAst(file);
|
|
470
|
+
return toVueAst(file, tsInstance);
|
|
433
471
|
}
|
|
434
472
|
if (file.endsWith(".ejs")) {
|
|
435
473
|
return toEjsAst(file);
|
|
@@ -509,12 +547,209 @@ const codeToJsAst = (file, code, projectType) => {
|
|
|
509
547
|
}
|
|
510
548
|
};
|
|
511
549
|
|
|
550
|
+
const vueScriptTagOnlyRegex = /<\/?script[^>]*>/gi;
|
|
551
|
+
const vueScriptBlockRegex = /<script\b[^>]*>[\s\S]*?<\/script>/gi;
|
|
552
|
+
const vueStyleBlockRegex = /<style\b[^>]*>[\s\S]*?<\/style>/gi;
|
|
553
|
+
const vueBrTagRegex = /<\/?br>/gi;
|
|
512
554
|
const vueCleaningRegex = /<\/*script.*>|<style[\s\S]*style>|<\/*br>/gi;
|
|
513
555
|
const vueTemplateRegex = /(<template.*>)([\s\S]*)(<\/template>)/gi;
|
|
514
556
|
const vueCommentRegex = /<!--[\s\S]*?-->/gi;
|
|
515
557
|
const vueBindRegex = /(:\[)([\s\S]*?)(\])/gi;
|
|
516
558
|
const vuePropRegex = /\s([.:@])([a-zA-Z]*?=)/gi;
|
|
517
559
|
const vueOpenImgTag = /(<img)((?!>)[\s\S]+?)( [^/]>)/gi;
|
|
560
|
+
const vueScriptTagRegex = /<script\b[^>]*>([\s\S]*?)<\/script>/gi;
|
|
561
|
+
|
|
562
|
+
const VUE_COMPILER_MACRO_SHIMS = `
|
|
563
|
+
declare function defineProps<T = any>(): T;
|
|
564
|
+
declare function defineEmits<T = any>(): T;
|
|
565
|
+
declare function defineExpose<T = any>(value?: T): void;
|
|
566
|
+
declare function defineSlots<T = any>(): T;
|
|
567
|
+
declare function defineModel<T = any>(
|
|
568
|
+
options?: { required?: boolean; default?: T }
|
|
569
|
+
): import("vue").Ref<T>;
|
|
570
|
+
declare function defineModel<T = any>(
|
|
571
|
+
name: string,
|
|
572
|
+
options?: { required?: boolean; default?: T }
|
|
573
|
+
): import("vue").Ref<T>;
|
|
574
|
+
declare function withDefaults<T, D>(props: T, defaults: D): T & D;
|
|
575
|
+
|
|
576
|
+
declare module "vue" {
|
|
577
|
+
export type Ref<T = any> = { value: T };
|
|
578
|
+
export type ComputedRef<T = any> = { readonly value: T };
|
|
579
|
+
export type InjectionKey<T> = symbol & { __type?: T };
|
|
580
|
+
export function ref<T>(value: T): Ref<T>;
|
|
581
|
+
export function ref<T = any>(): Ref<T | undefined>;
|
|
582
|
+
export function shallowRef<T>(value: T): Ref<T>;
|
|
583
|
+
export function computed<T>(getter: () => T): ComputedRef<T>;
|
|
584
|
+
export function inject<T>(key: any, defaultValue?: T): T;
|
|
585
|
+
export function provide<T>(key: any, value: T): void;
|
|
586
|
+
export function watch(...args: any[]): void;
|
|
587
|
+
export function watchEffect(effect: () => void): void;
|
|
588
|
+
export function onMounted(cb: () => void): void;
|
|
589
|
+
export function onUnmounted(cb: () => void): void;
|
|
590
|
+
}
|
|
591
|
+
`;
|
|
592
|
+
|
|
593
|
+
const maskNonNewlineChars = (value) => value.replace(/[^\r\n]/g, " ");
|
|
594
|
+
|
|
595
|
+
const cleanVueCodeForParsing = (code, { includeScripts = true } = {}) => {
|
|
596
|
+
let cleanedCode = code
|
|
597
|
+
.replace(vueCommentRegex, (match) => maskNonNewlineChars(match))
|
|
598
|
+
.replace(vueStyleBlockRegex, (match) => maskNonNewlineChars(match));
|
|
599
|
+
|
|
600
|
+
if (includeScripts) {
|
|
601
|
+
cleanedCode = cleanedCode.replace(vueScriptTagOnlyRegex, (match) => {
|
|
602
|
+
const masked = maskNonNewlineChars(match);
|
|
603
|
+
return masked.length > 0 ? `${masked.slice(1)};` : masked;
|
|
604
|
+
});
|
|
605
|
+
} else {
|
|
606
|
+
cleanedCode = cleanedCode.replace(vueScriptBlockRegex, (match) =>
|
|
607
|
+
maskNonNewlineChars(match)
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
return cleanedCode
|
|
612
|
+
.replace(vueBrTagRegex, (match) => {
|
|
613
|
+
const masked = maskNonNewlineChars(match);
|
|
614
|
+
return masked.length > 0 ? `${masked.slice(1)};` : masked;
|
|
615
|
+
})
|
|
616
|
+
.replace(vueBindRegex, (match, grA, grB, grC) => {
|
|
617
|
+
return maskNonNewlineChars(grA) + grB + maskNonNewlineChars(grC);
|
|
618
|
+
})
|
|
619
|
+
.replace(vuePropRegex, (match, grA, grB) => {
|
|
620
|
+
return " " + grA.replace(/[.:@]/g, " ") + grB.replaceAll(".", "-");
|
|
621
|
+
})
|
|
622
|
+
.replace(vueOpenImgTag, (match, grA, grB, grC) => {
|
|
623
|
+
return grA + grB + grC.replace(" >", "/>");
|
|
624
|
+
})
|
|
625
|
+
.replace(vueTemplateRegex, (match, grA, grB, grC) => {
|
|
626
|
+
return grA + grB.replaceAll("{{", "{ ").replaceAll("}}", " }") + grC;
|
|
627
|
+
});
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
const extractVueScriptContent = (code) => {
|
|
631
|
+
const scriptChunks = [];
|
|
632
|
+
let scriptMatch;
|
|
633
|
+
vueScriptBlockRegex.lastIndex = 0;
|
|
634
|
+
while ((scriptMatch = vueScriptBlockRegex.exec(code)) !== null) {
|
|
635
|
+
const fullMatch = scriptMatch[0] || "";
|
|
636
|
+
const content = fullMatch
|
|
637
|
+
.replace(/^<script\b[^>]*>/i, "")
|
|
638
|
+
.replace(/<\/script>$/i, "");
|
|
639
|
+
if (content.trim().length > 0) {
|
|
640
|
+
scriptChunks.push(content);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return scriptChunks.join("\n");
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
const buildVueParseCandidates = (code) => {
|
|
647
|
+
const fullCandidate = cleanVueCodeForParsing(code, { includeScripts: true });
|
|
648
|
+
const templateOnlyCandidate = cleanVueCodeForParsing(code, {
|
|
649
|
+
includeScripts: false
|
|
650
|
+
});
|
|
651
|
+
const scriptOnlyCandidate = extractVueScriptContent(code);
|
|
652
|
+
const combinedCandidate = scriptOnlyCandidate
|
|
653
|
+
? `${scriptOnlyCandidate}\n${templateOnlyCandidate}`
|
|
654
|
+
: templateOnlyCandidate;
|
|
655
|
+
|
|
656
|
+
const candidates = [
|
|
657
|
+
{ name: "full", code: fullCandidate },
|
|
658
|
+
{ name: "combined", code: combinedCandidate },
|
|
659
|
+
{ name: "template-only", code: templateOnlyCandidate },
|
|
660
|
+
{ name: "script-only", code: scriptOnlyCandidate }
|
|
661
|
+
];
|
|
662
|
+
|
|
663
|
+
const seenCandidateCode = new Set();
|
|
664
|
+
return candidates.filter((candidate) => {
|
|
665
|
+
if (!candidate.code || !candidate.code.trim()) {
|
|
666
|
+
return false;
|
|
667
|
+
}
|
|
668
|
+
if (seenCandidateCode.has(candidate.code)) {
|
|
669
|
+
return false;
|
|
670
|
+
}
|
|
671
|
+
seenCandidateCode.add(candidate.code);
|
|
672
|
+
return true;
|
|
673
|
+
});
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
const parseVueAstWithFallback = (file, code) => {
|
|
677
|
+
const candidates = buildVueParseCandidates(code);
|
|
678
|
+
let lastError;
|
|
679
|
+
for (const candidate of candidates) {
|
|
680
|
+
try {
|
|
681
|
+
return codeToJsAst(file, candidate.code, "ts");
|
|
682
|
+
} catch (err) {
|
|
683
|
+
lastError = err;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
throw lastError || new Error(`Unable to parse Vue file: ${file}`);
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
const createVueVirtualTypeSource = (code) => {
|
|
690
|
+
const output = maskNonNewlineChars(code).split("");
|
|
691
|
+
let hasScriptContent = false;
|
|
692
|
+
let scriptMatch;
|
|
693
|
+
vueScriptTagRegex.lastIndex = 0;
|
|
694
|
+
while ((scriptMatch = vueScriptTagRegex.exec(code)) !== null) {
|
|
695
|
+
const fullMatch = scriptMatch[0];
|
|
696
|
+
const scriptContent = scriptMatch[1] || "";
|
|
697
|
+
const contentStart = scriptMatch.index + fullMatch.indexOf(scriptContent);
|
|
698
|
+
if (scriptContent.trim().length > 0) {
|
|
699
|
+
hasScriptContent = true;
|
|
700
|
+
}
|
|
701
|
+
for (let index = 0; index < scriptContent.length; index++) {
|
|
702
|
+
output[contentStart + index] = scriptContent[index];
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return {
|
|
706
|
+
source: output.join(""),
|
|
707
|
+
hasScriptContent
|
|
708
|
+
};
|
|
709
|
+
};
|
|
710
|
+
|
|
711
|
+
const collectVueTypesWithVirtualProgram = (file, virtualSource) => {
|
|
712
|
+
const tempDir = mkdtempSync(join(tmpdir(), "atom-parsetools-vue-"));
|
|
713
|
+
const virtualFile = join(tempDir, `${basename(file)}.ts`);
|
|
714
|
+
const shimFile = join(tempDir, "vue-shims.d.ts");
|
|
715
|
+
try {
|
|
716
|
+
writeFileSync(virtualFile, virtualSource, "utf8");
|
|
717
|
+
writeFileSync(shimFile, VUE_COMPILER_MACRO_SHIMS, "utf8");
|
|
718
|
+
const virtualTs = createTsc([virtualFile, shimFile], tempDir);
|
|
719
|
+
const sourceFile = virtualTs?.program?.getSourceFile(virtualFile);
|
|
720
|
+
if (!virtualTs || !sourceFile) {
|
|
721
|
+
return new Map();
|
|
722
|
+
}
|
|
723
|
+
return virtualTs.collectTypes(sourceFile);
|
|
724
|
+
} catch {
|
|
725
|
+
return new Map();
|
|
726
|
+
} finally {
|
|
727
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
728
|
+
}
|
|
729
|
+
};
|
|
730
|
+
|
|
731
|
+
const collectVueSeenTypes = (file, code, tsInstance) => {
|
|
732
|
+
let seenTypes;
|
|
733
|
+
if (tsInstance?.program) {
|
|
734
|
+
try {
|
|
735
|
+
const tsSrc = tsInstance.program.getSourceFile(file);
|
|
736
|
+
if (tsSrc) {
|
|
737
|
+
seenTypes = tsInstance.collectTypes(tsSrc);
|
|
738
|
+
}
|
|
739
|
+
} catch {
|
|
740
|
+
// Ignore and continue with virtual source fallback below.
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
if (!seenTypes || seenTypes.size === 0) {
|
|
745
|
+
const virtualSource = createVueVirtualTypeSource(code);
|
|
746
|
+
if (virtualSource.hasScriptContent) {
|
|
747
|
+
seenTypes = collectVueTypesWithVirtualProgram(file, virtualSource.source);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
return seenTypes;
|
|
752
|
+
};
|
|
518
753
|
|
|
519
754
|
const TSC_FLAGS =
|
|
520
755
|
tsc.TypeFormatFlags.NoTruncation |
|
|
@@ -526,30 +761,43 @@ const TSC_FLAGS =
|
|
|
526
761
|
tsc.TypeFormatFlags.NoTypeReduction;
|
|
527
762
|
|
|
528
763
|
/**
|
|
529
|
-
* Convert a single vue file to AST
|
|
764
|
+
* Convert a single vue file to AST.
|
|
765
|
+
* When `tsInstance` is present also collect type inference from TSC and return both AST & types.
|
|
530
766
|
*/
|
|
531
|
-
const toVueAst = (file) => {
|
|
767
|
+
const toVueAst = (file, tsInstance) => {
|
|
532
768
|
const code = readFileSync(file, "utf-8");
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
769
|
+
return parseVueAstWithFallback(file, code);
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
const collectSeenTypesForFile = (file, ts, options) => {
|
|
773
|
+
if (!options?.tsTypes) {
|
|
774
|
+
return undefined;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
if (file.endsWith(".vue")) {
|
|
778
|
+
return collectVueSeenTypes(file, readFileSync(file, "utf-8"), ts);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
if (!ts?.program) {
|
|
782
|
+
return undefined;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
try {
|
|
786
|
+
const tsAst = ts.program.getSourceFile(file);
|
|
787
|
+
if (!tsAst) {
|
|
788
|
+
return undefined;
|
|
789
|
+
}
|
|
790
|
+
return ts.collectTypes
|
|
791
|
+
? ts.collectTypes(tsAst)
|
|
792
|
+
: (() => {
|
|
793
|
+
tsc.forEachChild(tsAst, ts.addType);
|
|
794
|
+
const collectedTypes = new Map(ts.seenTypes);
|
|
795
|
+
ts.seenTypes.clear();
|
|
796
|
+
return collectedTypes;
|
|
797
|
+
})();
|
|
798
|
+
} catch {
|
|
799
|
+
return undefined;
|
|
800
|
+
}
|
|
553
801
|
};
|
|
554
802
|
|
|
555
803
|
/**
|
|
@@ -707,7 +955,7 @@ const DEFAULT_TSC_OPTIONS = {
|
|
|
707
955
|
|
|
708
956
|
const readJsonFileIfExists = (file) => {
|
|
709
957
|
try {
|
|
710
|
-
if (!existsSync(file)) {
|
|
958
|
+
if (!file || !existsSync(file)) {
|
|
711
959
|
return undefined;
|
|
712
960
|
}
|
|
713
961
|
return JSON.parse(readFileSync(file, "utf8"));
|
|
@@ -732,7 +980,10 @@ const findNearestPackageJson = (src) => {
|
|
|
732
980
|
};
|
|
733
981
|
|
|
734
982
|
const detectDefaultTscOptions = (srcFiles, src) => {
|
|
735
|
-
const
|
|
983
|
+
const nearPackageJson = findNearestPackageJson(src);
|
|
984
|
+
const packageJson = nearPackageJson
|
|
985
|
+
? readJsonFileIfExists(nearPackageJson)
|
|
986
|
+
: undefined;
|
|
736
987
|
const usesNodeNextModuleResolution =
|
|
737
988
|
packageJson?.type === "module" ||
|
|
738
989
|
Boolean(packageJson?.exports) ||
|
|
@@ -868,6 +1119,33 @@ function createTsc(srcFiles, src) {
|
|
|
868
1119
|
const typeChecker = program.getTypeChecker();
|
|
869
1120
|
const seenTypes = new Map();
|
|
870
1121
|
|
|
1122
|
+
// Return-type inference below can re-walk the body of the *same* function
|
|
1123
|
+
// declaration once per referencing call site / initializer (e.g. a helper
|
|
1124
|
+
// called 50 times triggers 50 identical body walks). Since the checker
|
|
1125
|
+
// state is fixed for the lifetime of this program, the inferred string for
|
|
1126
|
+
// a given node is deterministic, so we memoize by node to remove the
|
|
1127
|
+
// redundant re-walks without changing any output. A sentinel distinguishes
|
|
1128
|
+
// "computed and produced undefined" from "not yet computed".
|
|
1129
|
+
const RETURN_TYPE_NOT_COMPUTED = Symbol("returnTypeNotComputed");
|
|
1130
|
+
const memoizeByNode = (fn) => {
|
|
1131
|
+
const cache = new WeakMap();
|
|
1132
|
+
return (node, ...rest) => {
|
|
1133
|
+
if (!node || typeof node !== "object" || rest.length > 0) {
|
|
1134
|
+
return fn(node, ...rest);
|
|
1135
|
+
}
|
|
1136
|
+
const cached = cache.get(node);
|
|
1137
|
+
if (cached !== undefined) {
|
|
1138
|
+
return cached === RETURN_TYPE_NOT_COMPUTED ? undefined : cached;
|
|
1139
|
+
}
|
|
1140
|
+
const result = fn(node);
|
|
1141
|
+
cache.set(
|
|
1142
|
+
node,
|
|
1143
|
+
result === undefined ? RETURN_TYPE_NOT_COMPUTED : result
|
|
1144
|
+
);
|
|
1145
|
+
return result;
|
|
1146
|
+
};
|
|
1147
|
+
};
|
|
1148
|
+
|
|
871
1149
|
const safeTypeToString = (type, context) => {
|
|
872
1150
|
try {
|
|
873
1151
|
return normalizeTypeString(
|
|
@@ -895,7 +1173,10 @@ function createTsc(srcFiles, src) {
|
|
|
895
1173
|
return undefined;
|
|
896
1174
|
}
|
|
897
1175
|
try {
|
|
898
|
-
return safeTypeToString(
|
|
1176
|
+
return safeTypeToString(
|
|
1177
|
+
typeChecker.getTypeFromTypeNode(typeNode),
|
|
1178
|
+
typeNode
|
|
1179
|
+
);
|
|
899
1180
|
} catch {
|
|
900
1181
|
return undefined;
|
|
901
1182
|
}
|
|
@@ -984,7 +1265,6 @@ function createTsc(srcFiles, src) {
|
|
|
984
1265
|
return collectedTypes;
|
|
985
1266
|
}
|
|
986
1267
|
if (
|
|
987
|
-
node !== undefined &&
|
|
988
1268
|
!tsc.isFunctionDeclaration(node) &&
|
|
989
1269
|
!tsc.isFunctionExpression(node) &&
|
|
990
1270
|
!tsc.isArrowFunction(node) &&
|
|
@@ -997,7 +1277,7 @@ function createTsc(srcFiles, src) {
|
|
|
997
1277
|
return collectedTypes;
|
|
998
1278
|
};
|
|
999
1279
|
|
|
1000
|
-
const
|
|
1280
|
+
const inferAsyncReturnTypeFromBodyImpl = (node) => {
|
|
1001
1281
|
if (!node.body) {
|
|
1002
1282
|
return undefined;
|
|
1003
1283
|
}
|
|
@@ -1019,6 +1299,9 @@ function createTsc(srcFiles, src) {
|
|
|
1019
1299
|
? unionType
|
|
1020
1300
|
: `Promise<${unionType}>`;
|
|
1021
1301
|
};
|
|
1302
|
+
const inferAsyncReturnTypeFromBody = memoizeByNode(
|
|
1303
|
+
inferAsyncReturnTypeFromBodyImpl
|
|
1304
|
+
);
|
|
1022
1305
|
|
|
1023
1306
|
const buildFunctionSignatureType = (node, returnTypeStr) => {
|
|
1024
1307
|
if (!node.parameters) {
|
|
@@ -1074,7 +1357,7 @@ function createTsc(srcFiles, src) {
|
|
|
1074
1357
|
return undefined;
|
|
1075
1358
|
};
|
|
1076
1359
|
|
|
1077
|
-
const
|
|
1360
|
+
const inferFunctionDeclarationReturnTypeImpl = (declaration) => {
|
|
1078
1361
|
const signature = typeChecker.getSignatureFromDeclaration(declaration);
|
|
1079
1362
|
if (!signature) {
|
|
1080
1363
|
return undefined;
|
|
@@ -1096,8 +1379,11 @@ function createTsc(srcFiles, src) {
|
|
|
1096
1379
|
}
|
|
1097
1380
|
return inferredType;
|
|
1098
1381
|
};
|
|
1382
|
+
const inferFunctionDeclarationReturnType = memoizeByNode(
|
|
1383
|
+
inferFunctionDeclarationReturnTypeImpl
|
|
1384
|
+
);
|
|
1099
1385
|
|
|
1100
|
-
const
|
|
1386
|
+
const inferAsyncReturnTypeFromSyntaxBodyImpl = (node) => {
|
|
1101
1387
|
if (!node?.body) {
|
|
1102
1388
|
return undefined;
|
|
1103
1389
|
}
|
|
@@ -1140,6 +1426,9 @@ function createTsc(srcFiles, src) {
|
|
|
1140
1426
|
? unionType
|
|
1141
1427
|
: `Promise<${unionType}>`;
|
|
1142
1428
|
};
|
|
1429
|
+
const inferAsyncReturnTypeFromSyntaxBody = memoizeByNode(
|
|
1430
|
+
inferAsyncReturnTypeFromSyntaxBodyImpl
|
|
1431
|
+
);
|
|
1143
1432
|
|
|
1144
1433
|
const addType = (node, currentSeenTypes = seenTypes) => {
|
|
1145
1434
|
// STRUCTURAL/CONTAINER NODES
|
|
@@ -1256,10 +1545,13 @@ function createTsc(srcFiles, src) {
|
|
|
1256
1545
|
node.kind === tsc.SyntaxKind.VariableDeclaration &&
|
|
1257
1546
|
node.name
|
|
1258
1547
|
) {
|
|
1259
|
-
const explicitDeclaredType = getExplicitTypeAnnotationString(
|
|
1548
|
+
const explicitDeclaredType = getExplicitTypeAnnotationString(
|
|
1549
|
+
node.type
|
|
1550
|
+
);
|
|
1260
1551
|
const varType = typeChecker.getTypeAtLocation(node.name);
|
|
1261
1552
|
typeStr =
|
|
1262
|
-
explicitDeclaredType &&
|
|
1553
|
+
explicitDeclaredType &&
|
|
1554
|
+
!isUnresolvedTypeString(explicitDeclaredType)
|
|
1263
1555
|
? explicitDeclaredType
|
|
1264
1556
|
: safeTypeWithContextToString(varType, node.name);
|
|
1265
1557
|
if (node.initializer && !explicitDeclaredType) {
|
|
@@ -1537,64 +1829,209 @@ function createTsc(srcFiles, src) {
|
|
|
1537
1829
|
}
|
|
1538
1830
|
}
|
|
1539
1831
|
|
|
1832
|
+
/**
|
|
1833
|
+
* Expand the set of output files to include the source files that tsconfig
|
|
1834
|
+
* pulls in (that live under the source root), mirroring the program root names.
|
|
1835
|
+
* Computed from the parsed tsconfig alone so no TypeScript program has to be
|
|
1836
|
+
* built on the main thread.
|
|
1837
|
+
*/
|
|
1838
|
+
const expandSrcFilesWithRootNames = (srcFiles, rootNames, src) => {
|
|
1839
|
+
if (!rootNames) {
|
|
1840
|
+
return srcFiles;
|
|
1841
|
+
}
|
|
1842
|
+
const srcRoot = resolve(src);
|
|
1843
|
+
const srcFileByResolvedPath = new Map(
|
|
1844
|
+
srcFiles.map((file) => [resolve(file), file])
|
|
1845
|
+
);
|
|
1846
|
+
for (const file of rootNames) {
|
|
1847
|
+
const resolvedFile = resolve(file);
|
|
1848
|
+
if (
|
|
1849
|
+
resolvedFile.startsWith(srcRoot) &&
|
|
1850
|
+
/\.(?:js|jsx|cjs|mjs|ts|tsx|mts|cts)$/.test(file) &&
|
|
1851
|
+
!isExcludedTestFile(file) &&
|
|
1852
|
+
!srcFileByResolvedPath.has(resolvedFile)
|
|
1853
|
+
) {
|
|
1854
|
+
srcFileByResolvedPath.set(
|
|
1855
|
+
resolvedFile,
|
|
1856
|
+
join(src, relative(srcRoot, resolvedFile))
|
|
1857
|
+
);
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
return [...srcFileByResolvedPath.values()].sort();
|
|
1861
|
+
};
|
|
1862
|
+
|
|
1863
|
+
const runGc = () => {
|
|
1864
|
+
if (typeof globalThis.gc === "function") {
|
|
1865
|
+
try {
|
|
1866
|
+
globalThis.gc();
|
|
1867
|
+
} catch (e) {
|
|
1868
|
+
// ignore
|
|
1869
|
+
}
|
|
1870
|
+
} else if (typeof Bun !== "undefined" && typeof Bun.gc === "function") {
|
|
1871
|
+
try {
|
|
1872
|
+
Bun.gc(true);
|
|
1873
|
+
} catch (e) {
|
|
1874
|
+
// ignore
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
};
|
|
1878
|
+
|
|
1879
|
+
/**
|
|
1880
|
+
* Process a list of files (AST + optional type generation) on the current
|
|
1881
|
+
* thread, in memory-bounded chunks. `ts` is the shared TypeScript program
|
|
1882
|
+
* instance (or undefined when type generation is disabled).
|
|
1883
|
+
*/
|
|
1884
|
+
const processFilesInline = async (srcFiles, options, ts) => {
|
|
1885
|
+
const CONCURRENCY_LIMIT = Math.max(
|
|
1886
|
+
1,
|
|
1887
|
+
Number.parseInt(process.env.ASTGEN_CONCURRENCY || "10", 10) || 10
|
|
1888
|
+
);
|
|
1889
|
+
for (let i = 0; i < srcFiles.length; i += CONCURRENCY_LIMIT) {
|
|
1890
|
+
const chunk = srcFiles.slice(i, i + CONCURRENCY_LIMIT);
|
|
1891
|
+
await Promise.all(chunk.map((file) => processFile(file, options, ts)));
|
|
1892
|
+
runGc();
|
|
1893
|
+
}
|
|
1894
|
+
};
|
|
1895
|
+
|
|
1896
|
+
/**
|
|
1897
|
+
* Decide how many worker threads to use for the type-generation phase.
|
|
1898
|
+
* The TypeScript checker is single-threaded and CPU-bound, so real speedups
|
|
1899
|
+
* only come from running independent programs on separate threads. Each worker
|
|
1900
|
+
* builds its own full program (needed for cross-file type resolution).
|
|
1901
|
+
*
|
|
1902
|
+
* IMPORTANT: parallelism is OPT-IN. When files are sharded across workers,
|
|
1903
|
+
* TypeScript's per-program type-id assignment order changes, which reorders the
|
|
1904
|
+
* members of a small number of inferred union types (e.g. `A | B` -> `B | A`;
|
|
1905
|
+
* semantically identical, textually different). To keep the default output
|
|
1906
|
+
* byte-identical for downstream consumers, workers are only used when
|
|
1907
|
+
* ASTGEN_TYPE_WORKERS is set explicitly. Set it to the desired worker count
|
|
1908
|
+
* (e.g. number of cores) to trade that cosmetic reordering for speed; "auto"
|
|
1909
|
+
* derives the count from the available CPUs. Unset or 1 => single-threaded.
|
|
1910
|
+
*/
|
|
1911
|
+
const resolveTypeWorkerCount = (fileCount) => {
|
|
1912
|
+
const envValue = process.env.ASTGEN_TYPE_WORKERS;
|
|
1913
|
+
if (envValue === undefined || envValue === "") {
|
|
1914
|
+
return 1;
|
|
1915
|
+
}
|
|
1916
|
+
if (envValue.toLowerCase() === "auto") {
|
|
1917
|
+
let cores = 1;
|
|
1918
|
+
try {
|
|
1919
|
+
cores =
|
|
1920
|
+
typeof availableParallelism === "function"
|
|
1921
|
+
? availableParallelism()
|
|
1922
|
+
: cpus().length;
|
|
1923
|
+
} catch (e) {
|
|
1924
|
+
cores = 1;
|
|
1925
|
+
}
|
|
1926
|
+
const autoCount = Math.max(cores - 1, 1);
|
|
1927
|
+
return Math.min(autoCount, Math.max(1, fileCount));
|
|
1928
|
+
}
|
|
1929
|
+
const requested = Number.parseInt(envValue, 10);
|
|
1930
|
+
if (!Number.isFinite(requested) || requested < 1) {
|
|
1931
|
+
return 1;
|
|
1932
|
+
}
|
|
1933
|
+
return Math.min(requested, Math.max(1, fileCount));
|
|
1934
|
+
};
|
|
1935
|
+
|
|
1936
|
+
/**
|
|
1937
|
+
* Run the type-generation phase across worker threads. Files are sharded
|
|
1938
|
+
* round-robin so heavy files spread across workers. Each worker builds its own
|
|
1939
|
+
* program over `projectFiles`; the per-file AST and type set are identical to
|
|
1940
|
+
* the single-threaded path, except that a small number of inferred union types
|
|
1941
|
+
* may have their members printed in a different order (see resolveTypeWorkerCount).
|
|
1942
|
+
* Returns true only if every worker completed cleanly; on any failure the
|
|
1943
|
+
* caller falls back to inline processing (writes are idempotent, so
|
|
1944
|
+
* re-processing is safe).
|
|
1945
|
+
*/
|
|
1946
|
+
const runTypeGenerationInWorkers = (
|
|
1947
|
+
srcFiles,
|
|
1948
|
+
projectFiles,
|
|
1949
|
+
options,
|
|
1950
|
+
workerCount
|
|
1951
|
+
) => {
|
|
1952
|
+
const shards = Array.from({ length: workerCount }, () => []);
|
|
1953
|
+
srcFiles.forEach((file, index) => shards[index % workerCount].push(file));
|
|
1954
|
+
const workerEntry = fileURLToPath(import.meta.url);
|
|
1955
|
+
return Promise.all(
|
|
1956
|
+
shards.map((shard, index) => {
|
|
1957
|
+
if (shard.length === 0) {
|
|
1958
|
+
return Promise.resolve(true);
|
|
1959
|
+
}
|
|
1960
|
+
return new Promise((resolvePromise) => {
|
|
1961
|
+
let settled = false;
|
|
1962
|
+
const settle = (value) => {
|
|
1963
|
+
if (!settled) {
|
|
1964
|
+
settled = true;
|
|
1965
|
+
resolvePromise(value);
|
|
1966
|
+
}
|
|
1967
|
+
};
|
|
1968
|
+
try {
|
|
1969
|
+
const worker = new Worker(workerEntry, {
|
|
1970
|
+
workerData: {
|
|
1971
|
+
kind: "astgen-typegen",
|
|
1972
|
+
shard,
|
|
1973
|
+
projectFiles,
|
|
1974
|
+
options,
|
|
1975
|
+
index
|
|
1976
|
+
}
|
|
1977
|
+
});
|
|
1978
|
+
worker.on("error", (err) => {
|
|
1979
|
+
console.error("astgen type worker failed:", err?.message || err);
|
|
1980
|
+
settle(false);
|
|
1981
|
+
});
|
|
1982
|
+
worker.on("exit", (code) => settle(code === 0));
|
|
1983
|
+
} catch (err) {
|
|
1984
|
+
console.error("Unable to start astgen type worker:", err?.message);
|
|
1985
|
+
settle(false);
|
|
1986
|
+
}
|
|
1987
|
+
});
|
|
1988
|
+
})
|
|
1989
|
+
).then((results) => results.every(Boolean));
|
|
1990
|
+
};
|
|
1991
|
+
|
|
1540
1992
|
/**
|
|
1541
1993
|
* Generate AST for JavaScript or TypeScript
|
|
1542
1994
|
*/
|
|
1543
1995
|
const createJSAst = async (options) => {
|
|
1544
1996
|
try {
|
|
1545
|
-
const
|
|
1546
|
-
let srcFiles =
|
|
1547
|
-
|
|
1548
|
-
if (options.tsTypes) {
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
: srcFiles;
|
|
1552
|
-
ts = createTsc(projectFiles, options.src);
|
|
1553
|
-
if (ts?.rootNames) {
|
|
1554
|
-
const srcRoot = resolve(options.src);
|
|
1555
|
-
const srcFileByResolvedPath = new Map(
|
|
1556
|
-
srcFiles.map((file) => [resolve(file), file])
|
|
1557
|
-
);
|
|
1558
|
-
for (const file of ts.rootNames) {
|
|
1559
|
-
const resolvedFile = resolve(file);
|
|
1560
|
-
if (
|
|
1561
|
-
resolvedFile.startsWith(srcRoot) &&
|
|
1562
|
-
/\.(?:js|jsx|cjs|mjs|ts|tsx|mts|cts)$/.test(file) &&
|
|
1563
|
-
!srcFileByResolvedPath.has(resolvedFile)
|
|
1564
|
-
) {
|
|
1565
|
-
srcFileByResolvedPath.set(
|
|
1566
|
-
resolvedFile,
|
|
1567
|
-
join(options.src, relative(srcRoot, resolvedFile))
|
|
1568
|
-
);
|
|
1569
|
-
}
|
|
1570
|
-
}
|
|
1571
|
-
srcFiles = [...srcFileByResolvedPath.values()].sort();
|
|
1572
|
-
}
|
|
1997
|
+
const discovered = await getAllSrcJSAndTSFiles(options.src);
|
|
1998
|
+
let srcFiles = [...discovered].sort();
|
|
1999
|
+
|
|
2000
|
+
if (!options.tsTypes) {
|
|
2001
|
+
await processFilesInline(srcFiles, options, undefined);
|
|
2002
|
+
return;
|
|
1573
2003
|
}
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
2004
|
+
|
|
2005
|
+
const projectFiles = !shouldIncludeNodeModulesBundles
|
|
2006
|
+
? srcFiles.filter((file) => !file.includes("node_modules"))
|
|
2007
|
+
: srcFiles;
|
|
2008
|
+
// Compute the program root names from the parsed tsconfig without building
|
|
2009
|
+
// a program on the main thread, then expand the output file set to match.
|
|
2010
|
+
const tscConfig = createTscProgramConfig(projectFiles, options.src);
|
|
2011
|
+
srcFiles = expandSrcFilesWithRootNames(
|
|
2012
|
+
srcFiles,
|
|
2013
|
+
tscConfig?.rootNames,
|
|
2014
|
+
options.src
|
|
1577
2015
|
);
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
}
|
|
1590
|
-
} else if (typeof Bun !== "undefined" && typeof Bun.gc === "function") {
|
|
1591
|
-
try {
|
|
1592
|
-
Bun.gc(true);
|
|
1593
|
-
} catch (e) {
|
|
1594
|
-
// ignore
|
|
1595
|
-
}
|
|
2016
|
+
|
|
2017
|
+
const workerCount = resolveTypeWorkerCount(srcFiles.length);
|
|
2018
|
+
if (workerCount > 1) {
|
|
2019
|
+
const ranInParallel = await runTypeGenerationInWorkers(
|
|
2020
|
+
srcFiles,
|
|
2021
|
+
projectFiles,
|
|
2022
|
+
options,
|
|
2023
|
+
workerCount
|
|
2024
|
+
);
|
|
2025
|
+
if (ranInParallel) {
|
|
2026
|
+
return;
|
|
1596
2027
|
}
|
|
2028
|
+
console.error(
|
|
2029
|
+
"Falling back to single-threaded type generation after worker failure."
|
|
2030
|
+
);
|
|
1597
2031
|
}
|
|
2032
|
+
|
|
2033
|
+
const ts = createTsc(projectFiles, options.src);
|
|
2034
|
+
await processFilesInline(srcFiles, options, ts);
|
|
1598
2035
|
} catch (err) {
|
|
1599
2036
|
console.error(err);
|
|
1600
2037
|
}
|
|
@@ -1602,25 +2039,12 @@ const createJSAst = async (options) => {
|
|
|
1602
2039
|
|
|
1603
2040
|
const processFile = (file, options, ts) => {
|
|
1604
2041
|
try {
|
|
1605
|
-
const ast = fileToJsAst(file, options.type);
|
|
2042
|
+
const ast = fileToJsAst(file, options.type, ts);
|
|
1606
2043
|
writeAstFile(file, ast, options);
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
const seenTypes = ts.collectTypes
|
|
1612
|
-
? ts.collectTypes(tsAst)
|
|
1613
|
-
: (() => {
|
|
1614
|
-
tsc.forEachChild(tsAst, ts.addType);
|
|
1615
|
-
const collectedTypes = new Map(ts.seenTypes);
|
|
1616
|
-
ts.seenTypes.clear();
|
|
1617
|
-
return collectedTypes;
|
|
1618
|
-
})();
|
|
1619
|
-
writeTypesFile(file, seenTypes, options);
|
|
1620
|
-
}
|
|
1621
|
-
} catch (err) {
|
|
1622
|
-
console.warn("Process file", file, ":", err.message);
|
|
1623
|
-
}
|
|
2044
|
+
|
|
2045
|
+
const seenTypes = collectSeenTypesForFile(file, ts, options);
|
|
2046
|
+
if (seenTypes && seenTypes.size > 0) {
|
|
2047
|
+
writeTypesFile(file, seenTypes, options);
|
|
1624
2048
|
}
|
|
1625
2049
|
} catch (err) {
|
|
1626
2050
|
console.error("Failure:", file, err?.message);
|
|
@@ -1650,6 +2074,12 @@ const createVueAst = async (options) => {
|
|
|
1650
2074
|
const getCircularReplacer = () => {
|
|
1651
2075
|
const seen = new WeakSet();
|
|
1652
2076
|
return (key, value) => {
|
|
2077
|
+
// Babel 8 emits BigIntLiteral/`extra.value` as a native bigint, which
|
|
2078
|
+
// JSON.stringify cannot serialize. Emit it as a string, which also matches
|
|
2079
|
+
// the Babel 7 shape (BigIntLiteral.value was already a string there).
|
|
2080
|
+
if (typeof value === "bigint") {
|
|
2081
|
+
return value.toString();
|
|
2082
|
+
}
|
|
1653
2083
|
if (typeof value === "object" && value !== null) {
|
|
1654
2084
|
if (seen.has(value)) {
|
|
1655
2085
|
return;
|
|
@@ -1758,4 +2188,25 @@ async function main(argvs) {
|
|
|
1758
2188
|
}
|
|
1759
2189
|
}
|
|
1760
2190
|
|
|
1761
|
-
|
|
2191
|
+
/**
|
|
2192
|
+
* Type-generation worker entry point. Runs in a worker thread spawned by
|
|
2193
|
+
* runTypeGenerationInWorkers: builds its own TypeScript program over the full
|
|
2194
|
+
* project file set and processes only its assigned shard, so cross-file type
|
|
2195
|
+
* resolution is identical to the single-threaded path.
|
|
2196
|
+
*/
|
|
2197
|
+
const runTypeGenWorker = async ({ shard, projectFiles, options }) => {
|
|
2198
|
+
try {
|
|
2199
|
+
const ts = createTsc(projectFiles, options.src);
|
|
2200
|
+
await processFilesInline(shard, options, ts);
|
|
2201
|
+
parentPort?.postMessage({ done: true });
|
|
2202
|
+
} catch (err) {
|
|
2203
|
+
console.error(err);
|
|
2204
|
+
process.exit(1);
|
|
2205
|
+
}
|
|
2206
|
+
};
|
|
2207
|
+
|
|
2208
|
+
if (isMainThread) {
|
|
2209
|
+
main(process.argv);
|
|
2210
|
+
} else if (workerData?.kind === "astgen-typegen") {
|
|
2211
|
+
runTypeGenWorker(workerData);
|
|
2212
|
+
}
|
package/package.json
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appthreat/atom-parsetools",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Parsing tools that complement the @appthreat/atom project.",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"pretty": "prettier --write *.js --trailing-comma=none",
|
|
9
|
-
"test": "node test-fixtures/astgen-type-regression.js && node test-fixtures/astgen-json-regression.js && node test-fixtures/evaluate-astgen.js",
|
|
9
|
+
"test": "node test-fixtures/astgen-type-regression.js && node test-fixtures/astgen-json-regression.js && node test-fixtures/astgen-vue-regression.js && node test-fixtures/astgen-shape-snapshot.js && node test-fixtures/evaluate-astgen.js",
|
|
10
10
|
"test:evaluate": "node test-fixtures/evaluate-astgen.js",
|
|
11
|
+
"test:shape": "node test-fixtures/astgen-shape-snapshot.js",
|
|
12
|
+
"test:shape:update": "UPDATE_SHAPE_SNAPSHOT=1 node test-fixtures/astgen-shape-snapshot.js",
|
|
11
13
|
"test:json": "node test-fixtures/astgen-json-regression.js",
|
|
12
|
-
"test:fixtures": "node test-fixtures/test-suite.js"
|
|
14
|
+
"test:fixtures": "node test-fixtures/test-suite.js",
|
|
15
|
+
"test:vue": "node test-fixtures/astgen-vue-regression.js"
|
|
13
16
|
},
|
|
14
17
|
"dependencies": {
|
|
15
18
|
"@appthreat/atom-common": "^1.1.0",
|
|
16
|
-
"@babel/parser": "^
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
+
"@babel/parser": "^8.0.4",
|
|
20
|
+
"@typescript/typescript6": "^6.0.2",
|
|
21
|
+
"hermes-parser": "^0.37.0"
|
|
19
22
|
},
|
|
20
23
|
"bin": {
|
|
21
24
|
"astgen": "astgen.js",
|
|
@@ -24,7 +27,7 @@
|
|
|
24
27
|
"scalasem": "scalasem.js"
|
|
25
28
|
},
|
|
26
29
|
"engines": {
|
|
27
|
-
"node": ">=
|
|
30
|
+
"node": ">=22.0.0"
|
|
28
31
|
},
|
|
29
32
|
"repository": {
|
|
30
33
|
"type": "git",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<?php return array(
|
|
2
2
|
'root' => array(
|
|
3
3
|
'name' => '__root__',
|
|
4
|
-
'pretty_version' => 'v1.
|
|
5
|
-
'version' => '1.
|
|
6
|
-
'reference' => '
|
|
4
|
+
'pretty_version' => 'v1.3.0',
|
|
5
|
+
'version' => '1.3.0.0',
|
|
6
|
+
'reference' => '65d96106e47fe9607cec2ba1be69da7c9c4d574c',
|
|
7
7
|
'type' => 'library',
|
|
8
8
|
'install_path' => __DIR__ . '/../../',
|
|
9
9
|
'aliases' => array(),
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
),
|
|
12
12
|
'versions' => array(
|
|
13
13
|
'__root__' => array(
|
|
14
|
-
'pretty_version' => 'v1.
|
|
15
|
-
'version' => '1.
|
|
16
|
-
'reference' => '
|
|
14
|
+
'pretty_version' => 'v1.3.0',
|
|
15
|
+
'version' => '1.3.0.0',
|
|
16
|
+
'reference' => '65d96106e47fe9607cec2ba1be69da7c9c4d574c',
|
|
17
17
|
'type' => 'library',
|
|
18
18
|
'install_path' => __DIR__ . '/../../',
|
|
19
19
|
'aliases' => array(),
|
package/plugins/rubyastgen/bundle/ruby/4.0.0/extensions/x86_64-linux/4.0.0/prism-1.9.0/gem_make.out
CHANGED
|
@@ -6,10 +6,10 @@ checking for whether -fvisibility=hidden is accepted as CFLAGS... yes
|
|
|
6
6
|
creating Makefile
|
|
7
7
|
|
|
8
8
|
current directory: /home/runner/work/atom-parsetools/atom-parsetools/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/ext/prism
|
|
9
|
-
make -j5 DESTDIR\= sitearchdir\=./.gem.
|
|
9
|
+
make -j5 DESTDIR\= sitearchdir\=./.gem.20260807-2447-y3xzkz sitelibdir\=./.gem.20260807-2447-y3xzkz clean
|
|
10
10
|
|
|
11
11
|
current directory: /home/runner/work/atom-parsetools/atom-parsetools/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/ext/prism
|
|
12
|
-
make -j5 DESTDIR\= sitearchdir\=./.gem.
|
|
12
|
+
make -j5 DESTDIR\= sitearchdir\=./.gem.20260807-2447-y3xzkz sitelibdir\=./.gem.20260807-2447-y3xzkz
|
|
13
13
|
compiling api_node.c
|
|
14
14
|
compiling api_pack.c
|
|
15
15
|
compiling extension.c
|
|
@@ -37,8 +37,8 @@ compiling ./../../src/util/pm_strpbrk.c
|
|
|
37
37
|
linking shared-object prism/prism.so
|
|
38
38
|
|
|
39
39
|
current directory: /home/runner/work/atom-parsetools/atom-parsetools/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/ext/prism
|
|
40
|
-
make -j5 DESTDIR\= sitearchdir\=./.gem.
|
|
41
|
-
/usr/bin/install -c -m 0755 prism.so ./.gem.
|
|
40
|
+
make -j5 DESTDIR\= sitearchdir\=./.gem.20260807-2447-y3xzkz sitelibdir\=./.gem.20260807-2447-y3xzkz install
|
|
41
|
+
/usr/bin/install -c -m 0755 prism.so ./.gem.20260807-2447-y3xzkz/prism
|
|
42
42
|
|
|
43
43
|
current directory: /home/runner/work/atom-parsetools/atom-parsetools/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/ext/prism
|
|
44
|
-
make DESTDIR\= sitearchdir\=./.gem.
|
|
44
|
+
make DESTDIR\= sitearchdir\=./.gem.20260807-2447-y3xzkz sitelibdir\=./.gem.20260807-2447-y3xzkz clean
|
package/plugins/rubyastgen/bundle/ruby/4.0.0/extensions/x86_64-linux/4.0.0/racc-1.8.1/gem_make.out
CHANGED
|
@@ -3,16 +3,16 @@ current directory: /home/runner/work/atom-parsetools/atom-parsetools/plugins/rub
|
|
|
3
3
|
creating Makefile
|
|
4
4
|
|
|
5
5
|
current directory: /home/runner/work/atom-parsetools/atom-parsetools/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/ext/racc/cparse
|
|
6
|
-
make -j5 DESTDIR\= sitearchdir\=./.gem.
|
|
6
|
+
make -j5 DESTDIR\= sitearchdir\=./.gem.20260807-2447-krkev6 sitelibdir\=./.gem.20260807-2447-krkev6 clean
|
|
7
7
|
|
|
8
8
|
current directory: /home/runner/work/atom-parsetools/atom-parsetools/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/ext/racc/cparse
|
|
9
|
-
make -j5 DESTDIR\= sitearchdir\=./.gem.
|
|
9
|
+
make -j5 DESTDIR\= sitearchdir\=./.gem.20260807-2447-krkev6 sitelibdir\=./.gem.20260807-2447-krkev6
|
|
10
10
|
compiling cparse.c
|
|
11
11
|
linking shared-object racc/cparse.so
|
|
12
12
|
|
|
13
13
|
current directory: /home/runner/work/atom-parsetools/atom-parsetools/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/ext/racc/cparse
|
|
14
|
-
make -j5 DESTDIR\= sitearchdir\=./.gem.
|
|
15
|
-
/usr/bin/install -c -m 0755 cparse.so ./.gem.
|
|
14
|
+
make -j5 DESTDIR\= sitearchdir\=./.gem.20260807-2447-krkev6 sitelibdir\=./.gem.20260807-2447-krkev6 install
|
|
15
|
+
/usr/bin/install -c -m 0755 cparse.so ./.gem.20260807-2447-krkev6/racc
|
|
16
16
|
|
|
17
17
|
current directory: /home/runner/work/atom-parsetools/atom-parsetools/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/ext/racc/cparse
|
|
18
|
-
make DESTDIR\= sitearchdir\=./.gem.
|
|
18
|
+
make DESTDIR\= sitearchdir\=./.gem.20260807-2447-krkev6 sitelibdir\=./.gem.20260807-2447-krkev6 clean
|