@nudojs/service 1.0.1 → 3.0.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/dist/{chunk-2VHITMTM.js → chunk-ADFCZP72.js} +70 -2
- package/dist/config-Cqj8zeZH.d.ts +117 -0
- package/dist/evaluator/evaluator-api.d.ts +3 -12
- package/dist/evaluator/evaluator-api.js +3 -1
- package/dist/index.d.ts +436 -18
- package/dist/index.js +2046 -237
- package/package.json +5 -5
- package/dist/config-NqDRWWpn.d.ts +0 -68
package/dist/index.js
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
|
+
DEFAULT_ANALYSIS_MODE,
|
|
3
|
+
analysisConfig,
|
|
2
4
|
builtinProtoMember,
|
|
3
5
|
builtinProtoMemberNames,
|
|
6
|
+
clearPathEnvCaches,
|
|
4
7
|
describeAbsMember,
|
|
8
|
+
diskCacheRoot,
|
|
5
9
|
findProjectConfig,
|
|
6
10
|
interfaceConfig,
|
|
7
11
|
loadEnvs,
|
|
8
12
|
matchesEmitAllowlist,
|
|
9
13
|
preloadPathEnvs
|
|
10
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-ADFCZP72.js";
|
|
11
15
|
|
|
12
16
|
// src/analyzer.ts
|
|
13
|
-
import { readFileSync as readFileSync5, existsSync as
|
|
14
|
-
import { resolve as
|
|
17
|
+
import { readFileSync as readFileSync5, existsSync as existsSync5, statSync as statSync5, realpathSync } from "fs";
|
|
18
|
+
import { resolve as resolve6, dirname as dirname7 } from "path";
|
|
15
19
|
import traverse from "@babel/traverse";
|
|
16
20
|
import {
|
|
17
21
|
createEnvironment as createEnvironment2,
|
|
@@ -34,6 +38,9 @@ import {
|
|
|
34
38
|
strLit,
|
|
35
39
|
boolLit,
|
|
36
40
|
abs as makeAbsVal,
|
|
41
|
+
formalParamsFromNodes,
|
|
42
|
+
formalParamDisplayNames,
|
|
43
|
+
runWithEvalMissingSlot,
|
|
37
44
|
stableAnalyzeKeySource as stableAnalyzeKeySource2,
|
|
38
45
|
fnFingerprints
|
|
39
46
|
} from "@nudojs/core";
|
|
@@ -383,9 +390,70 @@ function defaultLoadModule(spec, fromFile) {
|
|
|
383
390
|
}
|
|
384
391
|
}
|
|
385
392
|
|
|
393
|
+
// src/env-path-deps.ts
|
|
394
|
+
import { existsSync as existsSync2, statSync as statSync2 } from "fs";
|
|
395
|
+
import { dirname as dirname2, resolve as resolve2, join as join2 } from "path";
|
|
396
|
+
import { extractAllLoadSpecs } from "@nudojs/core";
|
|
397
|
+
var envDependents = /* @__PURE__ */ new Map();
|
|
398
|
+
function norm(p) {
|
|
399
|
+
return p.replace(/\\/g, "/");
|
|
400
|
+
}
|
|
401
|
+
function resolveLoadSpecPath(spec, fromFile) {
|
|
402
|
+
if (!spec.startsWith(".") && !spec.startsWith("/")) return void 0;
|
|
403
|
+
try {
|
|
404
|
+
const base = dirname2(resolve2(fromFile));
|
|
405
|
+
const p = resolve2(base, spec);
|
|
406
|
+
for (const cand of [
|
|
407
|
+
p,
|
|
408
|
+
`${p}.js`,
|
|
409
|
+
`${p}.mjs`,
|
|
410
|
+
`${p}.ts`,
|
|
411
|
+
join2(p, "index.js"),
|
|
412
|
+
join2(p, "index.mjs"),
|
|
413
|
+
join2(p, "index.ts")
|
|
414
|
+
]) {
|
|
415
|
+
if (existsSync2(cand) && !statSync2(cand).isDirectory()) return cand;
|
|
416
|
+
}
|
|
417
|
+
return void 0;
|
|
418
|
+
} catch {
|
|
419
|
+
return void 0;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
function noteEnvPathDeps(sourcePath, source) {
|
|
423
|
+
const src = norm(sourcePath);
|
|
424
|
+
for (const set of envDependents.values()) set.delete(src);
|
|
425
|
+
let specs = [];
|
|
426
|
+
try {
|
|
427
|
+
specs = extractAllLoadSpecs(source);
|
|
428
|
+
} catch {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
for (const spec of specs) {
|
|
432
|
+
if (!spec.startsWith(".") && !spec.startsWith("/")) continue;
|
|
433
|
+
const abs = resolveLoadSpecPath(spec, sourcePath);
|
|
434
|
+
if (!abs) continue;
|
|
435
|
+
const key = norm(abs);
|
|
436
|
+
if (!envDependents.has(key)) envDependents.set(key, /* @__PURE__ */ new Set());
|
|
437
|
+
envDependents.get(key).add(src);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
function envPathDependents(envPath) {
|
|
441
|
+
return [...envDependents.get(norm(envPath)) ?? []];
|
|
442
|
+
}
|
|
443
|
+
function clearEnvPathDeps() {
|
|
444
|
+
envDependents.clear();
|
|
445
|
+
}
|
|
446
|
+
function isEnvTemplatePath(path) {
|
|
447
|
+
const lower = norm(path).toLowerCase();
|
|
448
|
+
return lower.endsWith(".env.js") || lower.endsWith(".env.mjs") || lower.endsWith(".env.ts") || lower.endsWith(".env.cjs") || lower.endsWith(".env.cts") || lower.endsWith(".env.mts") || /\/mock-[^/]+\.(js|mjs|ts)$/.test(lower) || /\.mock\.(js|mjs|ts)$/.test(lower);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// src/analyzer.ts
|
|
452
|
+
import { loadModuleDepsFingerprint as loadModuleDepsFingerprint2, hashSource as hashSource2 } from "@nudojs/core";
|
|
453
|
+
|
|
386
454
|
// src/abs-modules-graph.ts
|
|
387
|
-
import { readFileSync as readFileSync4, statSync as
|
|
388
|
-
import { dirname as
|
|
455
|
+
import { readFileSync as readFileSync4, statSync as statSync4 } from "fs";
|
|
456
|
+
import { dirname as dirname6, resolve as resolve5 } from "path";
|
|
389
457
|
import { parse as parse3 } from "@nudojs/parser";
|
|
390
458
|
import {
|
|
391
459
|
evalProgramAbs,
|
|
@@ -398,19 +466,19 @@ import {
|
|
|
398
466
|
confJoin as confJoin2,
|
|
399
467
|
absFunction as absFunction2
|
|
400
468
|
} from "@nudojs/core";
|
|
401
|
-
import { dirname as
|
|
469
|
+
import { dirname as dirname5 } from "path";
|
|
402
470
|
|
|
403
471
|
// src/harvest-auto.ts
|
|
404
472
|
import { parse as parse2 } from "@nudojs/parser";
|
|
405
473
|
|
|
406
474
|
// src/static-imports.ts
|
|
407
475
|
import { parse } from "@nudojs/parser";
|
|
408
|
-
import { readFileSync as readFileSync2, existsSync as
|
|
409
|
-
import { resolve as
|
|
476
|
+
import { readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
|
|
477
|
+
import { resolve as resolve3, dirname as dirname3, join as join3 } from "path";
|
|
410
478
|
import { generalizeFromAst } from "@nudojs/core";
|
|
411
479
|
function resolveRelative(fromFile, spec) {
|
|
412
480
|
if (!spec.startsWith(".") && !spec.startsWith("/")) return void 0;
|
|
413
|
-
const base =
|
|
481
|
+
const base = resolve3(dirname3(fromFile), spec);
|
|
414
482
|
for (const cand of [
|
|
415
483
|
base,
|
|
416
484
|
base + ".js",
|
|
@@ -419,12 +487,12 @@ function resolveRelative(fromFile, spec) {
|
|
|
419
487
|
base + ".ts",
|
|
420
488
|
base + ".mts",
|
|
421
489
|
base + ".cts",
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
490
|
+
join3(base, "index.js"),
|
|
491
|
+
join3(base, "index.cjs"),
|
|
492
|
+
join3(base, "index.mjs"),
|
|
493
|
+
join3(base, "index.ts")
|
|
426
494
|
]) {
|
|
427
|
-
if (
|
|
495
|
+
if (existsSync3(cand)) return cand;
|
|
428
496
|
}
|
|
429
497
|
return void 0;
|
|
430
498
|
}
|
|
@@ -543,14 +611,14 @@ function collectCjsExports(stmt, named, setDefault) {
|
|
|
543
611
|
function collectStaticImports(entryFile, maxDepth = 8) {
|
|
544
612
|
const graph = /* @__PURE__ */ new Map();
|
|
545
613
|
const queue = [
|
|
546
|
-
{ file:
|
|
614
|
+
{ file: resolve3(entryFile), depth: 0 }
|
|
547
615
|
];
|
|
548
616
|
const seen = /* @__PURE__ */ new Set();
|
|
549
617
|
while (queue.length > 0) {
|
|
550
618
|
const { file, depth } = queue.shift();
|
|
551
619
|
if (seen.has(file) || depth > maxDepth) continue;
|
|
552
620
|
seen.add(file);
|
|
553
|
-
if (!
|
|
621
|
+
if (!existsSync3(file)) continue;
|
|
554
622
|
const source = readFileSync2(file, "utf8");
|
|
555
623
|
const mod = analyzeExportsFromSource(file, source);
|
|
556
624
|
graph.set(file, mod);
|
|
@@ -564,19 +632,19 @@ function collectStaticImports(entryFile, maxDepth = 8) {
|
|
|
564
632
|
}
|
|
565
633
|
|
|
566
634
|
// src/harvest-package.ts
|
|
567
|
-
import { existsSync as
|
|
568
|
-
import { join as
|
|
635
|
+
import { existsSync as existsSync4, readdirSync, statSync as statSync3, readFileSync as readFileSync3 } from "fs";
|
|
636
|
+
import { join as join4, resolve as resolve4, dirname as dirname4, basename } from "path";
|
|
569
637
|
import { harvestDts } from "@nudojs/harvester";
|
|
570
638
|
import { formatShape } from "@nudojs/core";
|
|
571
639
|
function resolvePackageRoot(pkg, fromDir = process.cwd()) {
|
|
572
|
-
let dir =
|
|
640
|
+
let dir = resolve4(fromDir);
|
|
573
641
|
const bare = pkg.replace(/^@/, "").replace(/\//g, "__");
|
|
574
642
|
for (let i = 0; i < 8; i++) {
|
|
575
|
-
const typesPath =
|
|
576
|
-
if (
|
|
577
|
-
const pkgPath =
|
|
578
|
-
if (
|
|
579
|
-
const parent =
|
|
643
|
+
const typesPath = join4(dir, "node_modules", "@types", bare);
|
|
644
|
+
if (existsSync4(typesPath)) return typesPath;
|
|
645
|
+
const pkgPath = join4(dir, "node_modules", pkg);
|
|
646
|
+
if (existsSync4(pkgPath)) return pkgPath;
|
|
647
|
+
const parent = resolve4(dir, "..");
|
|
580
648
|
if (parent === dir) break;
|
|
581
649
|
dir = parent;
|
|
582
650
|
}
|
|
@@ -606,10 +674,10 @@ function collectDtsFiles(root, maxFiles = 8) {
|
|
|
606
674
|
for (const name of entries) {
|
|
607
675
|
if (out.length >= maxFiles * 3) return;
|
|
608
676
|
if (SKIP_DIRS.has(name)) continue;
|
|
609
|
-
const p =
|
|
677
|
+
const p = join4(dir, name);
|
|
610
678
|
let st;
|
|
611
679
|
try {
|
|
612
|
-
st =
|
|
680
|
+
st = statSync3(p);
|
|
613
681
|
} catch {
|
|
614
682
|
continue;
|
|
615
683
|
}
|
|
@@ -625,15 +693,15 @@ function collectDtsFiles(root, maxFiles = 8) {
|
|
|
625
693
|
return out.slice(0, maxFiles);
|
|
626
694
|
}
|
|
627
695
|
function entryDtsFromPackageJson(root) {
|
|
628
|
-
const pj =
|
|
629
|
-
if (!
|
|
696
|
+
const pj = join4(root, "package.json");
|
|
697
|
+
if (!existsSync4(pj)) return void 0;
|
|
630
698
|
try {
|
|
631
699
|
const raw = JSON.parse(readFileSync3(pj, "utf8"));
|
|
632
700
|
const entry = raw.types ?? raw.typings;
|
|
633
701
|
if (typeof entry === "string" && entry.endsWith(".d.ts")) {
|
|
634
|
-
const p =
|
|
635
|
-
if (
|
|
636
|
-
const st =
|
|
702
|
+
const p = resolve4(root, entry);
|
|
703
|
+
if (existsSync4(p)) {
|
|
704
|
+
const st = statSync3(p);
|
|
637
705
|
if (st.size <= MAX_DTS_BYTES) return p;
|
|
638
706
|
}
|
|
639
707
|
}
|
|
@@ -649,7 +717,7 @@ function collectDtsFromEntry(entry, maxFiles = 200) {
|
|
|
649
717
|
const RELATIVE_FROM_REGEX = /\bfrom\s+["'](\.[^"']+)["']/g;
|
|
650
718
|
while (queue.length > 0 && files.length < maxFiles) {
|
|
651
719
|
const current = queue.shift();
|
|
652
|
-
if (seen.has(current) || !
|
|
720
|
+
if (seen.has(current) || !existsSync4(current)) continue;
|
|
653
721
|
seen.add(current);
|
|
654
722
|
if (!current.endsWith(".d.ts")) continue;
|
|
655
723
|
files.push(current);
|
|
@@ -659,14 +727,14 @@ function collectDtsFromEntry(entry, maxFiles = 200) {
|
|
|
659
727
|
} catch {
|
|
660
728
|
continue;
|
|
661
729
|
}
|
|
662
|
-
const dir =
|
|
730
|
+
const dir = dirname4(current);
|
|
663
731
|
for (const match of text.matchAll(REFERENCE_PATH_REGEX)) {
|
|
664
|
-
queue.push(
|
|
732
|
+
queue.push(resolve4(dir, match[1]));
|
|
665
733
|
}
|
|
666
734
|
for (const match of text.matchAll(RELATIVE_FROM_REGEX)) {
|
|
667
|
-
const base =
|
|
668
|
-
for (const candidate of [`${base}.d.ts`,
|
|
669
|
-
if (
|
|
735
|
+
const base = resolve4(dir, match[1]);
|
|
736
|
+
for (const candidate of [`${base}.d.ts`, join4(base, "index.d.ts")]) {
|
|
737
|
+
if (existsSync4(candidate)) {
|
|
670
738
|
queue.push(candidate);
|
|
671
739
|
break;
|
|
672
740
|
}
|
|
@@ -678,11 +746,11 @@ function collectDtsFromEntry(entry, maxFiles = 200) {
|
|
|
678
746
|
function harvestPackage(pkg, fromDir, maxFiles = 8) {
|
|
679
747
|
const root = resolvePackageRoot(pkg, fromDir);
|
|
680
748
|
if (!root) return { error: `package not found: ${pkg}` };
|
|
681
|
-
const entry = entryDtsFromPackageJson(root) ??
|
|
682
|
-
let dtsFiles =
|
|
749
|
+
const entry = entryDtsFromPackageJson(root) ?? join4(root, "index.d.ts");
|
|
750
|
+
let dtsFiles = existsSync4(entry) ? collectDtsFromEntry(entry, maxFiles) : [];
|
|
683
751
|
if (dtsFiles.length === 0) {
|
|
684
752
|
const collected = collectDtsFiles(root, maxFiles);
|
|
685
|
-
dtsFiles = entry &&
|
|
753
|
+
dtsFiles = entry && existsSync4(entry) && !collected.includes(entry) ? [entry, ...collected.filter((f) => f !== entry)].slice(0, maxFiles) : collected;
|
|
686
754
|
}
|
|
687
755
|
if (dtsFiles.length === 0) {
|
|
688
756
|
return { error: `no .d.ts under ${root}` };
|
|
@@ -710,6 +778,49 @@ function lookupHarvested(h, moduleName, exportName) {
|
|
|
710
778
|
}
|
|
711
779
|
|
|
712
780
|
// src/harvest-auto.ts
|
|
781
|
+
var NODE_BUILTINS = /* @__PURE__ */ new Set([
|
|
782
|
+
"assert",
|
|
783
|
+
"async_hooks",
|
|
784
|
+
"buffer",
|
|
785
|
+
"child_process",
|
|
786
|
+
"cluster",
|
|
787
|
+
"console",
|
|
788
|
+
"constants",
|
|
789
|
+
"crypto",
|
|
790
|
+
"dgram",
|
|
791
|
+
"diagnostics_channel",
|
|
792
|
+
"dns",
|
|
793
|
+
"domain",
|
|
794
|
+
"events",
|
|
795
|
+
"fs",
|
|
796
|
+
"http",
|
|
797
|
+
"http2",
|
|
798
|
+
"https",
|
|
799
|
+
"inspector",
|
|
800
|
+
"module",
|
|
801
|
+
"net",
|
|
802
|
+
"os",
|
|
803
|
+
"path",
|
|
804
|
+
"perf_hooks",
|
|
805
|
+
"process",
|
|
806
|
+
"punycode",
|
|
807
|
+
"querystring",
|
|
808
|
+
"readline",
|
|
809
|
+
"repl",
|
|
810
|
+
"stream",
|
|
811
|
+
"string_decoder",
|
|
812
|
+
"timers",
|
|
813
|
+
"tls",
|
|
814
|
+
"trace_events",
|
|
815
|
+
"tty",
|
|
816
|
+
"url",
|
|
817
|
+
"util",
|
|
818
|
+
"v8",
|
|
819
|
+
"vm",
|
|
820
|
+
"wasi",
|
|
821
|
+
"worker_threads",
|
|
822
|
+
"zlib"
|
|
823
|
+
]);
|
|
713
824
|
function barePackageName(spec) {
|
|
714
825
|
if (!spec) return void 0;
|
|
715
826
|
if (spec.startsWith(".") || spec.startsWith("/") || spec.startsWith("node:")) return void 0;
|
|
@@ -717,7 +828,9 @@ function barePackageName(spec) {
|
|
|
717
828
|
if (spec.startsWith("@")) {
|
|
718
829
|
return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : void 0;
|
|
719
830
|
}
|
|
720
|
-
|
|
831
|
+
const name = parts[0];
|
|
832
|
+
if (NODE_BUILTINS.has(name)) return void 0;
|
|
833
|
+
return name;
|
|
721
834
|
}
|
|
722
835
|
function collectBarePackages(source) {
|
|
723
836
|
try {
|
|
@@ -840,7 +953,7 @@ function bareSpecToAbsModules(spec, fromFile) {
|
|
|
840
953
|
const parts = spec.split("/");
|
|
841
954
|
const pkg = spec.startsWith("@") ? parts.length >= 2 ? `${parts[0]}/${parts[1]}` : void 0 : parts[0];
|
|
842
955
|
if (!pkg) return void 0;
|
|
843
|
-
const table = harvestToAbsModules(pkg,
|
|
956
|
+
const table = harvestToAbsModules(pkg, dirname5(fromFile));
|
|
844
957
|
return table[spec] ?? table[pkg];
|
|
845
958
|
}
|
|
846
959
|
|
|
@@ -848,8 +961,8 @@ function bareSpecToAbsModules(spec, fromFile) {
|
|
|
848
961
|
function defaultAbsLoadModule(spec, fromFile) {
|
|
849
962
|
if (!spec.startsWith(".") && !spec.startsWith("/")) return void 0;
|
|
850
963
|
try {
|
|
851
|
-
const p =
|
|
852
|
-
for (const cand of [p, `${p}.js`, `${p}.mjs`, `${p}.ts`,
|
|
964
|
+
const p = resolve5(dirname6(resolve5(fromFile)), spec);
|
|
965
|
+
for (const cand of [p, `${p}.js`, `${p}.mjs`, `${p}.ts`, resolve5(p, "index.js")]) {
|
|
853
966
|
try {
|
|
854
967
|
return readFileSync4(cand, "utf-8");
|
|
855
968
|
} catch {
|
|
@@ -861,8 +974,8 @@ function defaultAbsLoadModule(spec, fromFile) {
|
|
|
861
974
|
}
|
|
862
975
|
}
|
|
863
976
|
function relCandidates(spec, fromFile) {
|
|
864
|
-
const p =
|
|
865
|
-
return [p, `${p}.js`, `${p}.mjs`, `${p}.ts`,
|
|
977
|
+
const p = resolve5(dirname6(resolve5(fromFile)), spec);
|
|
978
|
+
return [p, `${p}.js`, `${p}.mjs`, `${p}.ts`, resolve5(p, "index.js")];
|
|
866
979
|
}
|
|
867
980
|
function resolveRel(spec, fromFile) {
|
|
868
981
|
if (!spec.startsWith(".") && !spec.startsWith("/")) return null;
|
|
@@ -966,7 +1079,7 @@ function evalAbsModuleGraph(entrySource, entryFile, opts = {}) {
|
|
|
966
1079
|
const shared = absModuleCache.get(absPath);
|
|
967
1080
|
if (shared) {
|
|
968
1081
|
try {
|
|
969
|
-
const st =
|
|
1082
|
+
const st = statSync4(absPath);
|
|
970
1083
|
if (st.mtimeMs === shared.mtimeMs && st.size === shared.size) {
|
|
971
1084
|
for (const iss of shared.issues) pushIssue(iss.kind, iss.label, iss.reason);
|
|
972
1085
|
cache.set(absPath, shared.exports);
|
|
@@ -1031,7 +1144,7 @@ function evalAbsModuleGraph(entrySource, entryFile, opts = {}) {
|
|
|
1031
1144
|
cache.set(absPath, exports);
|
|
1032
1145
|
let fingerprint;
|
|
1033
1146
|
try {
|
|
1034
|
-
const st =
|
|
1147
|
+
const st = statSync4(absPath);
|
|
1035
1148
|
fingerprint = { mtimeMs: st.mtimeMs, size: st.size };
|
|
1036
1149
|
} catch {
|
|
1037
1150
|
}
|
|
@@ -1102,7 +1215,8 @@ import {
|
|
|
1102
1215
|
stableAnalyzeKeySource,
|
|
1103
1216
|
formatAbs as formatAbs3,
|
|
1104
1217
|
hashSource,
|
|
1105
|
-
getFnImpl as getFnImpl2
|
|
1218
|
+
getFnImpl as getFnImpl2,
|
|
1219
|
+
loadModuleDepsFingerprint
|
|
1106
1220
|
} from "@nudojs/core";
|
|
1107
1221
|
import { parse as parse4, extractInlineDirectives } from "@nudojs/parser";
|
|
1108
1222
|
|
|
@@ -1245,6 +1359,50 @@ function collectEnvModules(envNames) {
|
|
|
1245
1359
|
}
|
|
1246
1360
|
return out;
|
|
1247
1361
|
}
|
|
1362
|
+
var envHarvestConflictCollector = null;
|
|
1363
|
+
function setEnvHarvestConflictCollector(collector) {
|
|
1364
|
+
const prev = envHarvestConflictCollector;
|
|
1365
|
+
envHarvestConflictCollector = collector;
|
|
1366
|
+
return prev;
|
|
1367
|
+
}
|
|
1368
|
+
function getEnvHarvestConflictCollector() {
|
|
1369
|
+
return envHarvestConflictCollector;
|
|
1370
|
+
}
|
|
1371
|
+
function mergeHarvestUnderEnv(harvestModules, envModules, opts) {
|
|
1372
|
+
const out = {};
|
|
1373
|
+
for (const [mod, exports] of Object.entries(harvestModules)) {
|
|
1374
|
+
const named = { ...exports.named };
|
|
1375
|
+
out[mod] = exports.default !== void 0 ? { named, default: exports.default } : { named };
|
|
1376
|
+
}
|
|
1377
|
+
const notify = opts?.onConflict ?? envHarvestConflictCollector;
|
|
1378
|
+
for (const [mod, envExports] of Object.entries(envModules)) {
|
|
1379
|
+
const existing = out[mod];
|
|
1380
|
+
if (!existing) {
|
|
1381
|
+
const named2 = { ...envExports.named };
|
|
1382
|
+
out[mod] = envExports.default !== void 0 ? { named: named2, default: envExports.default } : { named: named2 };
|
|
1383
|
+
continue;
|
|
1384
|
+
}
|
|
1385
|
+
const overwritten = [];
|
|
1386
|
+
for (const key of Object.keys(envExports.named)) {
|
|
1387
|
+
if (existing.named[key] !== void 0) overwritten.push(key);
|
|
1388
|
+
}
|
|
1389
|
+
const defaultOverwritten = envExports.default !== void 0 && existing.default !== void 0;
|
|
1390
|
+
if (notify && (overwritten.length > 0 || defaultOverwritten)) {
|
|
1391
|
+
try {
|
|
1392
|
+
notify({
|
|
1393
|
+
module: mod,
|
|
1394
|
+
exports: overwritten,
|
|
1395
|
+
defaultOverwritten
|
|
1396
|
+
});
|
|
1397
|
+
} catch {
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
const named = { ...existing.named, ...envExports.named };
|
|
1401
|
+
const defaultAbs = envExports.default !== void 0 ? envExports.default : existing.default;
|
|
1402
|
+
out[mod] = defaultAbs !== void 0 ? { named, default: defaultAbs } : { named };
|
|
1403
|
+
}
|
|
1404
|
+
return out;
|
|
1405
|
+
}
|
|
1248
1406
|
function collectBPathReplacements(source) {
|
|
1249
1407
|
const targets = [];
|
|
1250
1408
|
const values = {};
|
|
@@ -1304,6 +1462,15 @@ function isBPathCapable(source, envNames = []) {
|
|
|
1304
1462
|
}
|
|
1305
1463
|
var bRunByFile = /* @__PURE__ */ new Map();
|
|
1306
1464
|
var MAX_B_RUN_CACHE = 32;
|
|
1465
|
+
function bPathDepKey(source, filePath) {
|
|
1466
|
+
try {
|
|
1467
|
+
const fp = loadModuleDepsFingerprint(source, defaultLoadModule, filePath);
|
|
1468
|
+
if (fp.truncated) return null;
|
|
1469
|
+
return hashSource(fp.fp);
|
|
1470
|
+
} catch {
|
|
1471
|
+
return null;
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1307
1474
|
function clearBPathCache() {
|
|
1308
1475
|
bRunByFile.clear();
|
|
1309
1476
|
clearAnalysisFileCache();
|
|
@@ -1316,12 +1483,12 @@ function evictBPathCacheForFiles(files) {
|
|
|
1316
1483
|
}
|
|
1317
1484
|
return n;
|
|
1318
1485
|
}
|
|
1319
|
-
function bPathCacheSet(filePath, stableSource, mode, envKey, mockKey, value) {
|
|
1486
|
+
function bPathCacheSet(filePath, stableSource, mode, envKey, mockKey, depKey, value) {
|
|
1320
1487
|
if (bRunByFile.size >= MAX_B_RUN_CACHE && !bRunByFile.has(filePath)) {
|
|
1321
1488
|
const oldest = bRunByFile.keys().next().value;
|
|
1322
1489
|
if (oldest !== void 0) bRunByFile.delete(oldest);
|
|
1323
1490
|
}
|
|
1324
|
-
bRunByFile.set(filePath, { stableSource, mode, envKey, mockKey, value });
|
|
1491
|
+
bRunByFile.set(filePath, { stableSource, mode, envKey, mockKey, depKey, value });
|
|
1325
1492
|
}
|
|
1326
1493
|
function tryRunBPath(source, filePath, opts = {}) {
|
|
1327
1494
|
if (!isBPathCapable(source, opts.envNames ?? [])) return void 0;
|
|
@@ -1329,11 +1496,15 @@ function tryRunBPath(source, filePath, opts = {}) {
|
|
|
1329
1496
|
const envKey = (opts.envNames ?? []).join(",");
|
|
1330
1497
|
const mockKey = mockSeedFingerprint(opts.mocks);
|
|
1331
1498
|
const stable = stableAnalyzeKeySource(source);
|
|
1332
|
-
const
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
bRunByFile.
|
|
1336
|
-
|
|
1499
|
+
const depKey = bPathDepKey(source, filePath);
|
|
1500
|
+
const canCache = depKey !== null;
|
|
1501
|
+
if (canCache) {
|
|
1502
|
+
const cached = bRunByFile.get(filePath);
|
|
1503
|
+
if (cached && cached.stableSource === stable && cached.mode === mode && cached.envKey === envKey && cached.mockKey === mockKey && cached.depKey === depKey) {
|
|
1504
|
+
bRunByFile.delete(filePath);
|
|
1505
|
+
bRunByFile.set(filePath, cached);
|
|
1506
|
+
return cached.value ?? void 0;
|
|
1507
|
+
}
|
|
1337
1508
|
}
|
|
1338
1509
|
let out = null;
|
|
1339
1510
|
try {
|
|
@@ -1346,7 +1517,7 @@ function tryRunBPath(source, filePath, opts = {}) {
|
|
|
1346
1517
|
try {
|
|
1347
1518
|
const { modules: graphMods, issues } = evalAbsModuleGraph(source, filePath);
|
|
1348
1519
|
const envMods = collectEnvModules(opts.envNames ?? []);
|
|
1349
|
-
const modules =
|
|
1520
|
+
const modules = mergeHarvestUnderEnv(graphMods, envMods);
|
|
1350
1521
|
const { targets, values, asTargets, asValues } = collectBPathReplacements(source);
|
|
1351
1522
|
const envGlobals = {
|
|
1352
1523
|
...collectEnvGlobals(opts.envNames ?? []),
|
|
@@ -1378,7 +1549,9 @@ function tryRunBPath(source, filePath, opts = {}) {
|
|
|
1378
1549
|
} catch {
|
|
1379
1550
|
out = null;
|
|
1380
1551
|
}
|
|
1381
|
-
|
|
1552
|
+
if (canCache && depKey !== null) {
|
|
1553
|
+
bPathCacheSet(filePath, stable, mode, envKey, mockKey, depKey, out);
|
|
1554
|
+
}
|
|
1382
1555
|
return out ?? void 0;
|
|
1383
1556
|
}
|
|
1384
1557
|
function tryBPathCallFull(source, filePath, fnName, args, opts = {}) {
|
|
@@ -1520,6 +1693,9 @@ function collectDeclared(file) {
|
|
|
1520
1693
|
case "ImportNamespaceSpecifier":
|
|
1521
1694
|
addId(o.local);
|
|
1522
1695
|
break;
|
|
1696
|
+
case "CatchClause":
|
|
1697
|
+
addId(o.param);
|
|
1698
|
+
break;
|
|
1523
1699
|
default:
|
|
1524
1700
|
break;
|
|
1525
1701
|
}
|
|
@@ -1621,10 +1797,10 @@ function collectBPathDiagnostics(source, extraKnown) {
|
|
|
1621
1797
|
// src/analyzer.ts
|
|
1622
1798
|
import { setAbsTruncationCollector as setAbsTruncationCollector2 } from "@nudojs/core";
|
|
1623
1799
|
function resolveImportPath(specifier, fromDir) {
|
|
1624
|
-
const basePath =
|
|
1800
|
+
const basePath = resolve6(fromDir, specifier);
|
|
1625
1801
|
for (const ext of ["", ".js", ".ts", ".mjs"]) {
|
|
1626
1802
|
const candidate = basePath + ext;
|
|
1627
|
-
if (
|
|
1803
|
+
if (existsSync5(candidate)) return candidate;
|
|
1628
1804
|
}
|
|
1629
1805
|
return null;
|
|
1630
1806
|
}
|
|
@@ -1644,7 +1820,7 @@ function buildModuleGraph(files, cache) {
|
|
|
1644
1820
|
if (cache) {
|
|
1645
1821
|
let stat = null;
|
|
1646
1822
|
try {
|
|
1647
|
-
stat =
|
|
1823
|
+
stat = statSync5(file);
|
|
1648
1824
|
} catch {
|
|
1649
1825
|
}
|
|
1650
1826
|
const cached = stat ? cache.get(file) : void 0;
|
|
@@ -1673,7 +1849,7 @@ function extractImportEdges(file) {
|
|
|
1673
1849
|
if (stmt.type !== "ImportDeclaration") continue;
|
|
1674
1850
|
const specifier = stmt.source.value;
|
|
1675
1851
|
if (!specifier.startsWith(".") && !specifier.startsWith("/")) continue;
|
|
1676
|
-
const resolved = resolveImportPath(specifier,
|
|
1852
|
+
const resolved = resolveImportPath(specifier, dirname7(file));
|
|
1677
1853
|
if (resolved) edges.push(resolved);
|
|
1678
1854
|
}
|
|
1679
1855
|
return edges;
|
|
@@ -1748,23 +1924,13 @@ function locFromNode(node) {
|
|
|
1748
1924
|
}
|
|
1749
1925
|
function extractParamNames(node) {
|
|
1750
1926
|
const fn = node.type === "ExportDefaultDeclaration" ? node.declaration : node;
|
|
1751
|
-
if (fn.type === "FunctionDeclaration" || fn.type === "FunctionExpression" || fn.type === "ArrowFunctionExpression") {
|
|
1752
|
-
return fn.params
|
|
1753
|
-
if (p.type === "Identifier") return p.name;
|
|
1754
|
-
if (p.type === "AssignmentPattern" && p.left.type === "Identifier") return p.left.name;
|
|
1755
|
-
if (p.type === "RestElement" && p.argument.type === "Identifier") return `...${p.argument.name}`;
|
|
1756
|
-
return "_";
|
|
1757
|
-
});
|
|
1927
|
+
if (fn.type === "FunctionDeclaration" || fn.type === "FunctionExpression" || fn.type === "ArrowFunctionExpression" || fn.type === "ClassMethod" || fn.type === "ObjectMethod" || fn.type === "TSDeclareMethod") {
|
|
1928
|
+
return formalParamDisplayNames(formalParamsFromNodes(fn.params));
|
|
1758
1929
|
}
|
|
1759
1930
|
if (fn.type === "VariableDeclaration") {
|
|
1760
1931
|
const decl = fn.declarations[0];
|
|
1761
1932
|
if (decl.init?.type === "FunctionExpression" || decl.init?.type === "ArrowFunctionExpression") {
|
|
1762
|
-
return decl.init.params
|
|
1763
|
-
if (p.type === "Identifier") return p.name;
|
|
1764
|
-
if (p.type === "AssignmentPattern" && p.left.type === "Identifier") return p.left.name;
|
|
1765
|
-
if (p.type === "RestElement" && p.argument.type === "Identifier") return `...${p.argument.name}`;
|
|
1766
|
-
return "_";
|
|
1767
|
-
});
|
|
1933
|
+
return formalParamDisplayNames(formalParamsFromNodes(decl.init.params));
|
|
1768
1934
|
}
|
|
1769
1935
|
}
|
|
1770
1936
|
return [];
|
|
@@ -1828,12 +1994,103 @@ function assignmentChainName(expr) {
|
|
|
1828
1994
|
function collectTopLevelFunctions(ast) {
|
|
1829
1995
|
const results = [];
|
|
1830
1996
|
if (ast.type !== "File") return results;
|
|
1831
|
-
|
|
1997
|
+
const body = ast.program.body;
|
|
1998
|
+
const classDecls = /* @__PURE__ */ new Map();
|
|
1999
|
+
const exportedClassNames = /* @__PURE__ */ new Set();
|
|
2000
|
+
const collectClassMethods = (cname, decl, stmt) => {
|
|
2001
|
+
const members = decl?.body?.body ?? [];
|
|
2002
|
+
for (const m of members) {
|
|
2003
|
+
const mem = m;
|
|
2004
|
+
const isMethod = mem.type === "MethodDefinition" || mem.type === "ClassMethod" || mem.type === "TSDeclareMethod";
|
|
2005
|
+
if (!isMethod) continue;
|
|
2006
|
+
if (mem.kind && mem.kind !== "method") continue;
|
|
2007
|
+
if (mem.static) continue;
|
|
2008
|
+
const keyName = mem.key?.type === "Identifier" ? mem.key.name : void 0;
|
|
2009
|
+
if (!keyName) continue;
|
|
2010
|
+
const methodNode = mem.type === "MethodDefinition" ? mem.value : mem;
|
|
2011
|
+
if (!methodNode) continue;
|
|
2012
|
+
results.push({
|
|
2013
|
+
name: `${cname}.${keyName}`,
|
|
2014
|
+
node: methodNode,
|
|
2015
|
+
stmt,
|
|
2016
|
+
noDeclaration: true
|
|
2017
|
+
});
|
|
2018
|
+
}
|
|
2019
|
+
};
|
|
2020
|
+
for (const stmt of body) {
|
|
2021
|
+
if (stmt.type === "ClassDeclaration" && stmt.id?.name) {
|
|
2022
|
+
const n = stmt.id.name;
|
|
2023
|
+
classDecls.set(n, { node: stmt, stmt });
|
|
2024
|
+
continue;
|
|
2025
|
+
}
|
|
2026
|
+
if (stmt.type === "ExportNamedDeclaration") {
|
|
2027
|
+
const d = stmt.declaration;
|
|
2028
|
+
if (d?.type === "ClassDeclaration" && d.id?.name) {
|
|
2029
|
+
const n = d.id.name;
|
|
2030
|
+
classDecls.set(n, { node: d, stmt });
|
|
2031
|
+
exportedClassNames.add(n);
|
|
2032
|
+
continue;
|
|
2033
|
+
}
|
|
2034
|
+
if (!d && stmt.specifiers) {
|
|
2035
|
+
for (const spec of stmt.specifiers) {
|
|
2036
|
+
if (spec?.type !== "ExportSpecifier") continue;
|
|
2037
|
+
const local = spec.local?.name;
|
|
2038
|
+
const exported = spec.exported?.name ?? spec.exported?.value;
|
|
2039
|
+
if (!local) continue;
|
|
2040
|
+
if (classDecls.has(local) || body.some(
|
|
2041
|
+
(s) => s.type === "ClassDeclaration" && s.id?.name === local
|
|
2042
|
+
)) {
|
|
2043
|
+
exportedClassNames.add(local);
|
|
2044
|
+
}
|
|
2045
|
+
if (exported === "default") exportedClassNames.add(local);
|
|
2046
|
+
}
|
|
2047
|
+
continue;
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
if (stmt.type === "ExportDefaultDeclaration") {
|
|
2051
|
+
const d = stmt.declaration;
|
|
2052
|
+
if (d?.type === "ClassDeclaration" && d.id?.name) {
|
|
2053
|
+
const n = d.id.name;
|
|
2054
|
+
classDecls.set(n, { node: d, stmt });
|
|
2055
|
+
exportedClassNames.add(n);
|
|
2056
|
+
} else if (d?.type === "Identifier" && typeof d.name === "string") {
|
|
2057
|
+
exportedClassNames.add(d.name);
|
|
2058
|
+
}
|
|
2059
|
+
continue;
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
for (const stmt of body) {
|
|
1832
2063
|
const decl = resolveFunctionNode(stmt);
|
|
1833
2064
|
if (decl.type === "FunctionDeclaration" && decl.id) {
|
|
1834
2065
|
results.push({ name: decl.id.name, node: decl, stmt, noDeclaration: false });
|
|
1835
2066
|
continue;
|
|
1836
2067
|
}
|
|
2068
|
+
if (decl.type === "ClassDeclaration" && decl.id?.name) {
|
|
2069
|
+
continue;
|
|
2070
|
+
}
|
|
2071
|
+
if (stmt.type === "ExportNamedDeclaration") {
|
|
2072
|
+
const d = resolveFunctionNode(stmt);
|
|
2073
|
+
if (d.type === "FunctionDeclaration" && d.id) {
|
|
2074
|
+
results.push({ name: d.id.name, node: d, stmt, noDeclaration: false });
|
|
2075
|
+
}
|
|
2076
|
+
if (d.type === "ClassDeclaration" && d.id?.name) {
|
|
2077
|
+
}
|
|
2078
|
+
continue;
|
|
2079
|
+
}
|
|
2080
|
+
if (stmt.type === "ExportDefaultDeclaration") {
|
|
2081
|
+
const d = stmt.declaration;
|
|
2082
|
+
if (d?.type === "FunctionDeclaration" && d.id) {
|
|
2083
|
+
results.push({ name: d.id.name, node: d, stmt, noDeclaration: false });
|
|
2084
|
+
} else if (d && (isFnExprValue(d) || d.type === "FunctionDeclaration" && !d.id)) {
|
|
2085
|
+
results.push({
|
|
2086
|
+
name: "default",
|
|
2087
|
+
node: d,
|
|
2088
|
+
stmt,
|
|
2089
|
+
noDeclaration: true
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2092
|
+
continue;
|
|
2093
|
+
}
|
|
1837
2094
|
if (stmt.type === "VariableDeclaration") {
|
|
1838
2095
|
for (const declarator of stmt.declarations) {
|
|
1839
2096
|
if (declarator.id?.type === "Identifier" && isFnExprValue(declarator.init)) {
|
|
@@ -1851,6 +2108,11 @@ function collectTopLevelFunctions(ast) {
|
|
|
1851
2108
|
}
|
|
1852
2109
|
}
|
|
1853
2110
|
}
|
|
2111
|
+
for (const cname of exportedClassNames) {
|
|
2112
|
+
const hit = classDecls.get(cname);
|
|
2113
|
+
if (!hit) continue;
|
|
2114
|
+
collectClassMethods(cname, hit.node, hit.stmt);
|
|
2115
|
+
}
|
|
1854
2116
|
return results;
|
|
1855
2117
|
}
|
|
1856
2118
|
function findSingleModuleExportsFunction(ast) {
|
|
@@ -1877,7 +2139,7 @@ function findSingleModuleExportsFunction(ast) {
|
|
|
1877
2139
|
}
|
|
1878
2140
|
return found;
|
|
1879
2141
|
}
|
|
1880
|
-
var
|
|
2142
|
+
var DEFAULT_CALLSITE_BUDGET = 3;
|
|
1881
2143
|
var COLLAPSE_LITERAL_THRESHOLD = 4;
|
|
1882
2144
|
function dedupeCallRecords(records) {
|
|
1883
2145
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1895,7 +2157,7 @@ function locFromCallLoc(loc) {
|
|
|
1895
2157
|
const p = loc ?? { line: 0, column: 0 };
|
|
1896
2158
|
return { start: { line: p.line, column: p.column }, end: { line: p.line, column: p.column } };
|
|
1897
2159
|
}
|
|
1898
|
-
function synthesizeExternalFunctions(records, currentFile) {
|
|
2160
|
+
function synthesizeExternalFunctions(records, currentFile, callSiteBudget = DEFAULT_CALLSITE_BUDGET) {
|
|
1899
2161
|
const groups = /* @__PURE__ */ new Map();
|
|
1900
2162
|
for (const rec of records) {
|
|
1901
2163
|
if (!rec.targetModule || !rec.targetExport) continue;
|
|
@@ -1919,7 +2181,7 @@ function synthesizeExternalFunctions(records, currentFile) {
|
|
|
1919
2181
|
cases: [],
|
|
1920
2182
|
fromModule: module
|
|
1921
2183
|
};
|
|
1922
|
-
const precise = deduped.slice(0,
|
|
2184
|
+
const precise = deduped.slice(0, callSiteBudget);
|
|
1923
2185
|
for (const rec of precise) {
|
|
1924
2186
|
analysis.cases.push({
|
|
1925
2187
|
name: `call@L${rec.callLoc?.line ?? 0}`,
|
|
@@ -1929,7 +2191,7 @@ function synthesizeExternalFunctions(records, currentFile) {
|
|
|
1929
2191
|
source: "callsite"
|
|
1930
2192
|
});
|
|
1931
2193
|
}
|
|
1932
|
-
const remaining = deduped.slice(
|
|
2194
|
+
const remaining = deduped.slice(callSiteBudget);
|
|
1933
2195
|
if (remaining.length > 0) {
|
|
1934
2196
|
const symArgsAbs = Array.from(
|
|
1935
2197
|
{ length: arity },
|
|
@@ -1970,16 +2232,17 @@ function collectEnvNames(filePath, source, includeProject) {
|
|
|
1970
2232
|
const fileDirectives = extractFileDirectives(ast);
|
|
1971
2233
|
const fileEnvNames = fileDirectives.filter((d) => d.kind === "env").flatMap((d) => d.envs);
|
|
1972
2234
|
if (!includeProject) return fileEnvNames;
|
|
1973
|
-
const projectConfig = findProjectConfig(
|
|
2235
|
+
const projectConfig = findProjectConfig(dirname7(filePath));
|
|
1974
2236
|
const projectEnvNames = projectConfig?.config.env ?? [];
|
|
1975
2237
|
return [.../* @__PURE__ */ new Set([...projectEnvNames, ...fileEnvNames])];
|
|
1976
2238
|
}
|
|
1977
|
-
async function analyzeFileAsync(filePath, source, activeCases, externalCallRecords) {
|
|
2239
|
+
async function analyzeFileAsync(filePath, source, activeCases, externalCallRecords, loadModule) {
|
|
1978
2240
|
const envNames = collectEnvNames(filePath, source, true);
|
|
2241
|
+
noteEnvPathDeps(filePath, source);
|
|
1979
2242
|
if (envNames.length > 0) {
|
|
1980
|
-
await preloadPathEnvs(envNames,
|
|
2243
|
+
await preloadPathEnvs(envNames, dirname7(filePath));
|
|
1981
2244
|
}
|
|
1982
|
-
return analyzeFile(filePath, source, activeCases, externalCallRecords);
|
|
2245
|
+
return analyzeFile(filePath, source, activeCases, externalCallRecords, loadModule);
|
|
1983
2246
|
}
|
|
1984
2247
|
function collectCallRecords(filePath, source) {
|
|
1985
2248
|
if (/\brequire\s*\(/.test(source) && filePath) {
|
|
@@ -2050,7 +2313,7 @@ function collectCallRecords(filePath, source) {
|
|
|
2050
2313
|
var TEST_CALLBACK_NAMES = /* @__PURE__ */ new Set(["it", "test", "describe"]);
|
|
2051
2314
|
var externalRecordIds = /* @__PURE__ */ new WeakMap();
|
|
2052
2315
|
var nextExternalRecordId = 1;
|
|
2053
|
-
function analysisFileCacheKey(filePath, source, activeCases, externalCallRecords) {
|
|
2316
|
+
function analysisFileCacheKey(filePath, source, activeCases, externalCallRecords, analysisCfg, loadModule, projectEnvNames, autoBind) {
|
|
2054
2317
|
let cases = "-";
|
|
2055
2318
|
if (activeCases && activeCases.size > 0) {
|
|
2056
2319
|
cases = [...activeCases.entries()].sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0).map(([k, v]) => `${k}=${v}`).join(",");
|
|
@@ -2064,11 +2327,31 @@ function analysisFileCacheKey(filePath, source, activeCases, externalCallRecords
|
|
|
2064
2327
|
}
|
|
2065
2328
|
ext = `n${externalCallRecords.length}#id${id}`;
|
|
2066
2329
|
}
|
|
2330
|
+
const cfg = analysisCfg ? `m=${analysisCfg.mode}|e=${analysisCfg.evalMissingSlot}|b=${analysisCfg.callSiteBudget}|d=${analysisCfg.diagnostics}` : "-";
|
|
2331
|
+
const lm = loadModule !== void 0 && loadModule !== defaultLoadModule ? "lm1" : "lm0";
|
|
2332
|
+
const envSeg = projectEnvNames && projectEnvNames.length > 0 ? projectEnvNames.join(",") : "-";
|
|
2333
|
+
const abSeg = autoBind === false ? "ab0" : autoBind === true ? "ab1" : "ab?";
|
|
2334
|
+
const effectiveLoader = loadModule ?? defaultLoadModule;
|
|
2335
|
+
let depSeg = "-";
|
|
2336
|
+
let noCache = false;
|
|
2337
|
+
try {
|
|
2338
|
+
const fp = loadModuleDepsFingerprint2(source, effectiveLoader, filePath);
|
|
2339
|
+
if (fp.truncated) {
|
|
2340
|
+
noCache = true;
|
|
2341
|
+
depSeg = `trunc:${fp.paths.length}`;
|
|
2342
|
+
} else {
|
|
2343
|
+
depSeg = hashSource2(fp.fp);
|
|
2344
|
+
}
|
|
2345
|
+
} catch {
|
|
2346
|
+
noCache = true;
|
|
2347
|
+
depSeg = "fperr";
|
|
2348
|
+
}
|
|
2067
2349
|
return {
|
|
2068
2350
|
filePath,
|
|
2069
2351
|
// 尾部无 @nudo 注释/空行不进键:comment-only 编辑命中 AnalysisResult
|
|
2070
2352
|
source: stableAnalyzeKeySource2(source),
|
|
2071
|
-
auxKey: `${cases}\0${ext}
|
|
2353
|
+
auxKey: `${cases}\0${ext}\0${cfg}\0${lm}\0${envSeg}\0${abSeg}\0${depSeg}`,
|
|
2354
|
+
noCache
|
|
2072
2355
|
};
|
|
2073
2356
|
}
|
|
2074
2357
|
function cloneAnalysisResult(r) {
|
|
@@ -2086,12 +2369,20 @@ function cloneFunctionAnalysis(a) {
|
|
|
2086
2369
|
return {
|
|
2087
2370
|
...a,
|
|
2088
2371
|
paramNames: [...a.paramNames],
|
|
2372
|
+
...a.formals ? { formals: a.formals.map((f) => ({ ...f })) } : {},
|
|
2089
2373
|
cases: a.cases.map((c) => ({
|
|
2090
2374
|
...c,
|
|
2091
2375
|
argAbs: [...c.argAbs],
|
|
2092
2376
|
...c.intension ? { intension: { ...c.intension } } : {}
|
|
2093
2377
|
})),
|
|
2094
|
-
loc: { start: { ...a.loc.start }, end: { ...a.loc.end } }
|
|
2378
|
+
loc: { start: { ...a.loc.start }, end: { ...a.loc.end } },
|
|
2379
|
+
...a.hof ? {
|
|
2380
|
+
hof: {
|
|
2381
|
+
...a.hof.fnRels ? { fnRels: a.hof.fnRels.map((r) => ({ ...r })) } : {},
|
|
2382
|
+
...a.hof.entryShapes ? { entryShapes: a.hof.entryShapes.map((s) => ({ ...s })) } : {},
|
|
2383
|
+
...a.hof.symbolic ? { symbolic: a.hof.symbolic } : {}
|
|
2384
|
+
}
|
|
2385
|
+
} : {}
|
|
2095
2386
|
};
|
|
2096
2387
|
}
|
|
2097
2388
|
function shiftSourceLoc(loc, lineDelta) {
|
|
@@ -2115,17 +2406,60 @@ function shiftCallRecordLines(r, lineDelta) {
|
|
|
2115
2406
|
callLoc: { line: r.callLoc.line + lineDelta, column: r.callLoc.column }
|
|
2116
2407
|
};
|
|
2117
2408
|
}
|
|
2118
|
-
function analyzeFile(filePath, source, activeCases, externalCallRecords) {
|
|
2119
|
-
const
|
|
2120
|
-
const
|
|
2121
|
-
|
|
2122
|
-
|
|
2409
|
+
function analyzeFile(filePath, source, activeCases, externalCallRecords, loadModule) {
|
|
2410
|
+
const projectConfig = findProjectConfig(dirname7(filePath));
|
|
2411
|
+
const cfg = analysisConfig(projectConfig?.config);
|
|
2412
|
+
const projectEnvNames = projectConfig?.config.env ?? [];
|
|
2413
|
+
const autoBind = interfaceConfig(projectConfig?.config).autoBind;
|
|
2414
|
+
const k = analysisFileCacheKey(
|
|
2415
|
+
filePath,
|
|
2416
|
+
source,
|
|
2417
|
+
activeCases,
|
|
2418
|
+
externalCallRecords,
|
|
2419
|
+
cfg,
|
|
2420
|
+
loadModule,
|
|
2421
|
+
projectEnvNames,
|
|
2422
|
+
autoBind !== false
|
|
2423
|
+
);
|
|
2424
|
+
if (!k.noCache) {
|
|
2425
|
+
const hit = analysisCacheGet(k.filePath, k.source, k.auxKey);
|
|
2426
|
+
if (hit !== void 0) {
|
|
2427
|
+
return cloneAnalysisResult(hit);
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
const result = analyzeFileUncached(filePath, source, activeCases, externalCallRecords, loadModule);
|
|
2431
|
+
if (!k.noCache) {
|
|
2432
|
+
analysisCacheSet(k.filePath, k.source, k.auxKey, result);
|
|
2123
2433
|
}
|
|
2124
|
-
const result = analyzeFileUncached(filePath, source, activeCases, externalCallRecords);
|
|
2125
|
-
analysisCacheSet(k.filePath, k.source, k.auxKey, result);
|
|
2126
2434
|
return cloneAnalysisResult(result);
|
|
2127
2435
|
}
|
|
2128
|
-
function analyzeFileUncached(filePath, source, activeCases, externalCallRecords) {
|
|
2436
|
+
function analyzeFileUncached(filePath, source, activeCases, externalCallRecords, loadModule) {
|
|
2437
|
+
const projectConfig = findProjectConfig(dirname7(filePath));
|
|
2438
|
+
const missingSlotOn = analysisConfig(projectConfig?.config).evalMissingSlot === "warning";
|
|
2439
|
+
return runWithEvalMissingSlot(
|
|
2440
|
+
missingSlotOn,
|
|
2441
|
+
() => analyzeFileUncachedInner(filePath, source, activeCases, externalCallRecords, loadModule)
|
|
2442
|
+
);
|
|
2443
|
+
}
|
|
2444
|
+
function findModuleImportLoc(source, module) {
|
|
2445
|
+
const bare = module.startsWith("node:") ? module.slice("node:".length) : module;
|
|
2446
|
+
const alts = [.../* @__PURE__ */ new Set([module, bare, `node:${bare}`])];
|
|
2447
|
+
const lines = source.split(/\r?\n/);
|
|
2448
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2449
|
+
const line = lines[i];
|
|
2450
|
+
for (const alt of alts) {
|
|
2451
|
+
const re = new RegExp(
|
|
2452
|
+
`["'\`]${alt.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}["'\`]`
|
|
2453
|
+
);
|
|
2454
|
+
const m = re.exec(line);
|
|
2455
|
+
if (m && m.index !== void 0) {
|
|
2456
|
+
return { line: i, column: m.index, length: m[0].length };
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
return null;
|
|
2461
|
+
}
|
|
2462
|
+
function analyzeFileUncachedInner(filePath, source, activeCases, externalCallRecords, loadModule) {
|
|
2129
2463
|
const ast = parse5(source);
|
|
2130
2464
|
const functions = extractDirectives(ast);
|
|
2131
2465
|
const diagnostics = [];
|
|
@@ -2133,20 +2467,47 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
|
|
|
2133
2467
|
const nodeAbsMap = /* @__PURE__ */ new Map();
|
|
2134
2468
|
const functionResults = [];
|
|
2135
2469
|
const caseHints = [];
|
|
2470
|
+
const envHarvestConflicts = [];
|
|
2471
|
+
const prevHarvestConflictCollector = setEnvHarvestConflictCollector((c) => {
|
|
2472
|
+
if (!envHarvestConflicts.some((x) => x.module === c.module)) {
|
|
2473
|
+
envHarvestConflicts.push(c);
|
|
2474
|
+
} else {
|
|
2475
|
+
const prev = envHarvestConflicts.find((x) => x.module === c.module);
|
|
2476
|
+
for (const e of c.exports) {
|
|
2477
|
+
if (!prev.exports.includes(e)) prev.exports.push(e);
|
|
2478
|
+
}
|
|
2479
|
+
prev.defaultOverwritten ||= c.defaultOverwritten;
|
|
2480
|
+
}
|
|
2481
|
+
});
|
|
2136
2482
|
const fileDirectives = extractFileDirectives(ast);
|
|
2137
2483
|
const fileEnvNames = fileDirectives.filter((d) => d.kind === "env").flatMap((d) => d.envs);
|
|
2138
|
-
const projectConfig = findProjectConfig(
|
|
2484
|
+
const projectConfig = findProjectConfig(dirname7(filePath));
|
|
2139
2485
|
const projectEnvNames = projectConfig?.config.env ?? [];
|
|
2140
2486
|
const envNames = [.../* @__PURE__ */ new Set([...projectEnvNames, ...fileEnvNames])];
|
|
2487
|
+
const analysisCfg = analysisConfig(projectConfig?.config);
|
|
2488
|
+
const callSiteBudget = analysisCfg.callSiteBudget;
|
|
2141
2489
|
const callRecords = [];
|
|
2142
2490
|
const globalEnv = createEnvironment2();
|
|
2143
2491
|
const bMemberDiagNames = /* @__PURE__ */ new Set();
|
|
2144
2492
|
const bMemberDiagSeen = /* @__PURE__ */ new Set();
|
|
2145
2493
|
const pushBMemberDiag = (d, fallbackLine) => {
|
|
2146
2494
|
bMemberDiagNames.add(d.name);
|
|
2147
|
-
const key = `${d.kind}:${d.name}:${d.receiver}:${d.line ?? fallbackLine}:${d.column ?? 0}`;
|
|
2495
|
+
const key = `${d.kind}:${d.name}:${d.receiver}:${d.line ?? fallbackLine}:${d.column ?? 0}:${d.code ?? ""}`;
|
|
2148
2496
|
if (bMemberDiagSeen.has(key)) return;
|
|
2149
2497
|
bMemberDiagSeen.add(key);
|
|
2498
|
+
if (d.code === "nudo:missing-slot") {
|
|
2499
|
+
diagnostics.push({
|
|
2500
|
+
range: {
|
|
2501
|
+
start: { line: d.line ?? fallbackLine, column: d.column ?? 0 },
|
|
2502
|
+
end: { line: d.line ?? fallbackLine, column: (d.column ?? 0) + d.name.length }
|
|
2503
|
+
},
|
|
2504
|
+
severity: "warning",
|
|
2505
|
+
message: `Field '${d.name}' is missing on the evaluated object shape`,
|
|
2506
|
+
code: "nudo:missing-slot",
|
|
2507
|
+
...d.origin ? { origin: d.origin } : {}
|
|
2508
|
+
});
|
|
2509
|
+
return;
|
|
2510
|
+
}
|
|
2150
2511
|
if (d.receiver === "unknown") {
|
|
2151
2512
|
diagnostics.push({
|
|
2152
2513
|
range: {
|
|
@@ -2204,9 +2565,10 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
|
|
|
2204
2565
|
try {
|
|
2205
2566
|
const g = evalAbsModuleGraph(source, filePath, {
|
|
2206
2567
|
seedVars: seeds.seedVars,
|
|
2207
|
-
seedFns: seeds.seedFns
|
|
2568
|
+
seedFns: seeds.seedFns,
|
|
2569
|
+
...loadModule ? { loadModule } : {}
|
|
2208
2570
|
});
|
|
2209
|
-
absGraphModules =
|
|
2571
|
+
absGraphModules = mergeHarvestUnderEnv(g.modules, collectEnvModules(envNames));
|
|
2210
2572
|
pushBModuleIssues(g.issues);
|
|
2211
2573
|
} catch {
|
|
2212
2574
|
}
|
|
@@ -2235,7 +2597,7 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
|
|
|
2235
2597
|
if (selfContained || canAbsModules) {
|
|
2236
2598
|
setAbsTruncationCollector2((label) => bTruncatedFns.add(label));
|
|
2237
2599
|
try {
|
|
2238
|
-
absCallRecords = collectAbsCallRecords(source, seeds, filePath, absGraphModules, envNames);
|
|
2600
|
+
absCallRecords = collectAbsCallRecords(source, seeds, filePath, absGraphModules, envNames, loadModule);
|
|
2239
2601
|
} finally {
|
|
2240
2602
|
setAbsTruncationCollector2(null);
|
|
2241
2603
|
}
|
|
@@ -2257,7 +2619,10 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
|
|
|
2257
2619
|
} catch {
|
|
2258
2620
|
}
|
|
2259
2621
|
}
|
|
2260
|
-
if (
|
|
2622
|
+
if (bHostedEval && bTopCallRecords.length > 0) {
|
|
2623
|
+
callRecords.length = 0;
|
|
2624
|
+
callRecords.push(...bTopCallRecords);
|
|
2625
|
+
} else if ((selfContained || canAbsModules) && absCallRecords.length > 0) {
|
|
2261
2626
|
callRecords.length = 0;
|
|
2262
2627
|
callRecords.push(...absCallRecords);
|
|
2263
2628
|
} else if (bTopCallRecords.length > 0) {
|
|
@@ -2345,7 +2710,16 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
|
|
|
2345
2710
|
const skipDirective = fn.directives.find((d) => d.kind === "skip");
|
|
2346
2711
|
const fnLoc = locFromNode(fn.node);
|
|
2347
2712
|
const paramNames = extractParamNames(fn.node);
|
|
2348
|
-
const
|
|
2713
|
+
const formals = formalParamsFromNodes(
|
|
2714
|
+
fn.node.params ?? []
|
|
2715
|
+
);
|
|
2716
|
+
const analysis = {
|
|
2717
|
+
name: fn.name,
|
|
2718
|
+
loc: fnLoc,
|
|
2719
|
+
paramNames,
|
|
2720
|
+
...formals.length > 0 ? { formals } : {},
|
|
2721
|
+
cases: []
|
|
2722
|
+
};
|
|
2349
2723
|
if (skipDirective && skipDirective.kind === "skip") {
|
|
2350
2724
|
analysis.skipped = true;
|
|
2351
2725
|
if (skipDirective.returns) {
|
|
@@ -2357,14 +2731,31 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
|
|
|
2357
2731
|
const caseDirectives = fn.directives.filter((d) => d.kind === "case");
|
|
2358
2732
|
const activeCaseIdx = activeCases?.get(fn.name) ?? 0;
|
|
2359
2733
|
const fp = fnFpMap?.get(fn.name);
|
|
2360
|
-
const
|
|
2734
|
+
const analysisFnKey = `m=${analysisCfg.mode}|e=${analysisCfg.evalMissingSlot}|b=${analysisCfg.callSiteBudget}`;
|
|
2735
|
+
let fnDepSeg = "-";
|
|
2736
|
+
let fnDepFailClosed = false;
|
|
2737
|
+
try {
|
|
2738
|
+
const dfp = loadModuleDepsFingerprint2(source, loadModule ?? defaultLoadModule, filePath);
|
|
2739
|
+
if (dfp.truncated) {
|
|
2740
|
+
fnDepFailClosed = true;
|
|
2741
|
+
fnDepSeg = null;
|
|
2742
|
+
} else {
|
|
2743
|
+
fnDepSeg = hashSource2(dfp.fp);
|
|
2744
|
+
}
|
|
2745
|
+
} catch {
|
|
2746
|
+
fnDepFailClosed = true;
|
|
2747
|
+
fnDepSeg = null;
|
|
2748
|
+
}
|
|
2749
|
+
const fnCacheKey = !fnDepFailClosed && fp && caseDirectives.length > 0 ? [
|
|
2361
2750
|
filePath,
|
|
2362
2751
|
fp.own,
|
|
2363
2752
|
fp.deps,
|
|
2364
2753
|
String(activeCaseIdx),
|
|
2365
2754
|
caseDirectiveKey(caseDirectives, formatAbs4),
|
|
2366
2755
|
envKeyFn,
|
|
2367
|
-
mockKeyFn
|
|
2756
|
+
mockKeyFn,
|
|
2757
|
+
analysisFnKey,
|
|
2758
|
+
fnDepSeg ?? "-"
|
|
2368
2759
|
].join("\0") : void 0;
|
|
2369
2760
|
const dLen0 = diagnostics.length;
|
|
2370
2761
|
const hLen0 = caseHints.length;
|
|
@@ -2538,6 +2929,7 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
|
|
|
2538
2929
|
COLLAPSE_LITERAL_THRESHOLD
|
|
2539
2930
|
);
|
|
2540
2931
|
}
|
|
2932
|
+
attachHofSnapshot(analysis, source);
|
|
2541
2933
|
if (fnCacheKey) {
|
|
2542
2934
|
fnAnalysisCacheSet(fnCacheKey, {
|
|
2543
2935
|
analysis: cloneFunctionAnalysis(analysis),
|
|
@@ -2571,7 +2963,7 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
|
|
|
2571
2963
|
}
|
|
2572
2964
|
return n;
|
|
2573
2965
|
};
|
|
2574
|
-
const currentModulePath = normalizeModulePath(
|
|
2966
|
+
const currentModulePath = normalizeModulePath(resolve6(filePath));
|
|
2575
2967
|
const singleExportFn = findSingleModuleExportsFunction(ast);
|
|
2576
2968
|
const reportInjectedDomainExceeds = (name, node, fallbackLoc) => {
|
|
2577
2969
|
if (!externalCallRecords || externalCallRecords.length === 0) return;
|
|
@@ -2586,10 +2978,10 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
|
|
|
2586
2978
|
if (injected.length === 0) return;
|
|
2587
2979
|
const nameLoc = fnNameLoc(node, fallbackLoc);
|
|
2588
2980
|
const fnNode = resolveFunctionNode(node);
|
|
2589
|
-
const autoBind = interfaceConfig(findProjectConfig(
|
|
2981
|
+
const autoBind = interfaceConfig(findProjectConfig(dirname7(filePath))?.config).autoBind;
|
|
2590
2982
|
const domainIssues = checkInjectedDomainEvidence(name, source, injected, {
|
|
2591
2983
|
paramNames: extractParamNames(fnNode),
|
|
2592
|
-
loadModule: defaultLoadModule,
|
|
2984
|
+
loadModule: loadModule ?? defaultLoadModule,
|
|
2593
2985
|
fromFile: filePath,
|
|
2594
2986
|
loc: { line: nameLoc.start.line, column: nameLoc.start.column },
|
|
2595
2987
|
...autoBind === false ? { autoBind: false } : {}
|
|
@@ -2639,7 +3031,7 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
|
|
|
2639
3031
|
return 0;
|
|
2640
3032
|
};
|
|
2641
3033
|
const ordered = records.map((r, i) => ({ r, i })).sort((a, b) => informativeness(a.r) - informativeness(b.r) || a.i - b.i).map(({ r }) => r);
|
|
2642
|
-
const precise = ordered.slice(0,
|
|
3034
|
+
const precise = ordered.slice(0, callSiteBudget);
|
|
2643
3035
|
for (const rec of precise) {
|
|
2644
3036
|
let absRaw;
|
|
2645
3037
|
let absResult;
|
|
@@ -2666,7 +3058,7 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
|
|
|
2666
3058
|
if (absRaw) attachAbsToIntension(caseResult2, absRaw, candidate.name);
|
|
2667
3059
|
candidate.analysis.cases.push(caseResult2);
|
|
2668
3060
|
}
|
|
2669
|
-
const remaining = ordered.slice(
|
|
3061
|
+
const remaining = ordered.slice(callSiteBudget).filter((rec) => !rec.argAbs.some((a) => a.shape.k === "unknown" && !a.term));
|
|
2670
3062
|
if (remaining.length > 0) {
|
|
2671
3063
|
const fnNode2 = resolveFunctionNode(candidate.node);
|
|
2672
3064
|
const paramCount = extractParamNames(fnNode2).length;
|
|
@@ -2707,7 +3099,8 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
|
|
|
2707
3099
|
const symCase = {
|
|
2708
3100
|
name: "call@symbolic",
|
|
2709
3101
|
argAbs: widenedArgsAbs,
|
|
2710
|
-
|
|
3102
|
+
// B4:超预算聚合必须 #widened(可解释降级)
|
|
3103
|
+
abs: symAbs ? { ...symAbs, conf: symAbs.conf === "exact" ? "widened" : symAbs.conf } : absUnknown2,
|
|
2711
3104
|
throwsAbs: neverAbs,
|
|
2712
3105
|
source: "callsite",
|
|
2713
3106
|
aggregatedFrom: remaining.length
|
|
@@ -2771,11 +3164,34 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
|
|
|
2771
3164
|
candidate.analysis.cases.push(caseResult);
|
|
2772
3165
|
candidate.analysis.entryOnly = true;
|
|
2773
3166
|
candidate.analysis.combinedAbs = entryAbs;
|
|
3167
|
+
attachHofSnapshot(candidate.analysis, source);
|
|
2774
3168
|
}
|
|
2775
3169
|
if (!bHostedEval) {
|
|
2776
3170
|
buildNodeTypeMap(ast, globalEnv, nodeAbsMap);
|
|
2777
3171
|
}
|
|
2778
|
-
const externalFunctions = synthesizeExternalFunctions(
|
|
3172
|
+
const externalFunctions = synthesizeExternalFunctions(
|
|
3173
|
+
callRecords,
|
|
3174
|
+
filePath,
|
|
3175
|
+
analysisConfig(projectConfig?.config).callSiteBudget
|
|
3176
|
+
);
|
|
3177
|
+
setEnvHarvestConflictCollector(prevHarvestConflictCollector);
|
|
3178
|
+
for (const c of envHarvestConflicts) {
|
|
3179
|
+
const parts = [];
|
|
3180
|
+
if (c.exports.length > 0) parts.push(`export(s) ${c.exports.join(", ")}`);
|
|
3181
|
+
if (c.defaultOverwritten) parts.push("default");
|
|
3182
|
+
const detail = parts.length > 0 ? ` \u2014 ${parts.join("; ")}` : "";
|
|
3183
|
+
const loc = findModuleImportLoc(source, c.module);
|
|
3184
|
+
const range = loc ? {
|
|
3185
|
+
start: { line: loc.line, column: loc.column },
|
|
3186
|
+
end: { line: loc.line, column: loc.column + loc.length }
|
|
3187
|
+
} : { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } };
|
|
3188
|
+
diagnostics.push({
|
|
3189
|
+
range,
|
|
3190
|
+
severity: "warning",
|
|
3191
|
+
message: `handwritten @nudo:env wins over harvest on module "${c.module}"${detail}; harvest only fills missing slots (B8). code=nudo:env-harvest-conflict`,
|
|
3192
|
+
code: "nudo:env-harvest-conflict"
|
|
3193
|
+
});
|
|
3194
|
+
}
|
|
2779
3195
|
return {
|
|
2780
3196
|
functions: functionResults,
|
|
2781
3197
|
diagnostics,
|
|
@@ -2870,7 +3286,7 @@ function absModulesOk(source, envNames) {
|
|
|
2870
3286
|
void envNames;
|
|
2871
3287
|
return true;
|
|
2872
3288
|
}
|
|
2873
|
-
function collectAbsCallRecords(source, seeds, filePath, precomputedModules, envNames = []) {
|
|
3289
|
+
function collectAbsCallRecords(source, seeds, filePath, precomputedModules, envNames = [], loadModule) {
|
|
2874
3290
|
const absCalls = [];
|
|
2875
3291
|
let modules = precomputedModules;
|
|
2876
3292
|
let importLocals = /* @__PURE__ */ new Map();
|
|
@@ -2879,7 +3295,8 @@ function collectAbsCallRecords(source, seeds, filePath, precomputedModules, envN
|
|
|
2879
3295
|
try {
|
|
2880
3296
|
const graph = evalAbsModuleGraph(source, filePath, {
|
|
2881
3297
|
seedVars: seeds?.seedVars,
|
|
2882
|
-
seedFns: seeds?.seedFns
|
|
3298
|
+
seedFns: seeds?.seedFns,
|
|
3299
|
+
...loadModule ? { loadModule } : {}
|
|
2883
3300
|
});
|
|
2884
3301
|
modules = graph.modules;
|
|
2885
3302
|
} catch {
|
|
@@ -2887,7 +3304,7 @@ function collectAbsCallRecords(source, seeds, filePath, precomputedModules, envN
|
|
|
2887
3304
|
}
|
|
2888
3305
|
}
|
|
2889
3306
|
if (envNames.length > 0) {
|
|
2890
|
-
modules =
|
|
3307
|
+
modules = mergeHarvestUnderEnv(modules ?? {}, collectEnvModules(envNames));
|
|
2891
3308
|
}
|
|
2892
3309
|
try {
|
|
2893
3310
|
importLocals = buildAbsImportLocalMap(source, filePath);
|
|
@@ -2986,10 +3403,10 @@ function buildAbsImportLocalMap(source, fromFile) {
|
|
|
2986
3403
|
return out;
|
|
2987
3404
|
}
|
|
2988
3405
|
function resolveImportAbs(spec, fromFile) {
|
|
2989
|
-
const base =
|
|
2990
|
-
const p =
|
|
2991
|
-
for (const cand of [p, `${p}.js`, `${p}.mjs`, `${p}.ts`,
|
|
2992
|
-
if (
|
|
3406
|
+
const base = dirname7(resolve6(fromFile));
|
|
3407
|
+
const p = resolve6(base, spec);
|
|
3408
|
+
for (const cand of [p, `${p}.js`, `${p}.mjs`, `${p}.ts`, resolve6(p, "index.js")]) {
|
|
3409
|
+
if (existsSync5(cand) && !statSync5(cand).isDirectory()) return cand;
|
|
2993
3410
|
}
|
|
2994
3411
|
return null;
|
|
2995
3412
|
}
|
|
@@ -3063,6 +3480,22 @@ function tryAttachIntension(caseResult, source, fnName) {
|
|
|
3063
3480
|
} catch {
|
|
3064
3481
|
}
|
|
3065
3482
|
}
|
|
3483
|
+
function attachHofSnapshot(analysis, source) {
|
|
3484
|
+
if (analysis.hof) return;
|
|
3485
|
+
try {
|
|
3486
|
+
const g = generalizeFromAst2(analysis.name, source);
|
|
3487
|
+
if (!g) return;
|
|
3488
|
+
const fnRels = g.fnRels ? [...g.fnRels.entries()].map(([param, rec]) => ({ param, abs: rec.abs })) : void 0;
|
|
3489
|
+
const entryShapes = g.entryShapes ? [...g.entryShapes.entries()].map(([param, rec]) => ({ param, abs: rec.abs })) : void 0;
|
|
3490
|
+
if (!fnRels?.length && !entryShapes?.length) return;
|
|
3491
|
+
analysis.hof = {
|
|
3492
|
+
...fnRels?.length ? { fnRels } : {},
|
|
3493
|
+
...entryShapes?.length ? { entryShapes } : {},
|
|
3494
|
+
symbolic: g.symbolic
|
|
3495
|
+
};
|
|
3496
|
+
} catch {
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
3066
3499
|
function attachAbsToIntension(caseResult, absVal, label) {
|
|
3067
3500
|
const prev = caseResult.intension ?? {};
|
|
3068
3501
|
caseResult.abs = absVal;
|
|
@@ -3074,10 +3507,27 @@ function attachAbsToIntension(caseResult, absVal, label) {
|
|
|
3074
3507
|
};
|
|
3075
3508
|
}
|
|
3076
3509
|
function safeAbsOrUnknown(a) {
|
|
3077
|
-
if (
|
|
3078
|
-
return
|
|
3510
|
+
if (a && typeof a === "object" && "shape" in a && "conf" in a) {
|
|
3511
|
+
return a;
|
|
3512
|
+
}
|
|
3513
|
+
if (typeof a === "function") {
|
|
3514
|
+
const fn = a;
|
|
3515
|
+
const n = Math.max(0, fn.length);
|
|
3516
|
+
const params = Array.from({ length: n }, (_, i) => `arg${i}`);
|
|
3517
|
+
return absFunction4(params, {
|
|
3518
|
+
body: { type: "BlockStatement", body: [], directives: [] },
|
|
3519
|
+
apply: (args) => {
|
|
3520
|
+
try {
|
|
3521
|
+
const r = fn(...args);
|
|
3522
|
+
if (r && typeof r === "object" && "shape" in r) return r;
|
|
3523
|
+
return absUnknown2;
|
|
3524
|
+
} catch {
|
|
3525
|
+
return absUnknown2;
|
|
3526
|
+
}
|
|
3527
|
+
}
|
|
3528
|
+
});
|
|
3079
3529
|
}
|
|
3080
|
-
return
|
|
3530
|
+
return absUnknown2;
|
|
3081
3531
|
}
|
|
3082
3532
|
function callRecordFromAbsCall(r, impMap) {
|
|
3083
3533
|
const argAbs = r.args.map(safeAbsOrUnknown);
|
|
@@ -3099,7 +3549,7 @@ function callRecordFromAbsCall(r, impMap) {
|
|
|
3099
3549
|
}
|
|
3100
3550
|
|
|
3101
3551
|
// src/lsp-surface.ts
|
|
3102
|
-
import { dirname as
|
|
3552
|
+
import { dirname as dirname8 } from "path";
|
|
3103
3553
|
import traverse2 from "@babel/traverse";
|
|
3104
3554
|
import {
|
|
3105
3555
|
generalizeFromAst as generalizeFromAst3,
|
|
@@ -3109,7 +3559,8 @@ import {
|
|
|
3109
3559
|
collectAbsNodeTypes,
|
|
3110
3560
|
findAbsAtPosition,
|
|
3111
3561
|
evalSource,
|
|
3112
|
-
setAbsNodeCollector as setAbsNodeCollector2
|
|
3562
|
+
setAbsNodeCollector as setAbsNodeCollector2,
|
|
3563
|
+
interfaceTierOf
|
|
3113
3564
|
} from "@nudojs/core";
|
|
3114
3565
|
import { parse as parse6, extractDirectives as extractDirectives2, extractFileDirectives as extractFileDirectives2 } from "@nudojs/parser";
|
|
3115
3566
|
function getCasesForFile(filePath, source) {
|
|
@@ -3123,14 +3574,14 @@ function getCasesForFile(filePath, source) {
|
|
|
3123
3574
|
async function getTypeAtPositionAsync(filePath, source, line, column, activeCases) {
|
|
3124
3575
|
const envNames = collectEnvNames(filePath, source, false);
|
|
3125
3576
|
if (envNames.length > 0) {
|
|
3126
|
-
await preloadPathEnvs(envNames,
|
|
3577
|
+
await preloadPathEnvs(envNames, dirname8(filePath));
|
|
3127
3578
|
}
|
|
3128
3579
|
return getTypeAtPosition(filePath, source, line, column, activeCases);
|
|
3129
3580
|
}
|
|
3130
3581
|
async function getAbsAtPositionAsync(filePath, source, line, column, activeCases) {
|
|
3131
3582
|
const envNames = collectEnvNames(filePath, source, false);
|
|
3132
3583
|
if (envNames.length > 0) {
|
|
3133
|
-
await preloadPathEnvs(envNames,
|
|
3584
|
+
await preloadPathEnvs(envNames, dirname8(filePath));
|
|
3134
3585
|
}
|
|
3135
3586
|
return getAbsAtPosition(filePath, source, line, column, activeCases);
|
|
3136
3587
|
}
|
|
@@ -3218,7 +3669,7 @@ function getAbsAtPosition(filePath, source, line, column, activeCases) {
|
|
|
3218
3669
|
function getTypeAtPosition(filePath, source, line, column, activeCases) {
|
|
3219
3670
|
return getAbsAtPosition(filePath, source, line, column, activeCases);
|
|
3220
3671
|
}
|
|
3221
|
-
function getHoverAtPosition(filePath, source, line, column, activeCases) {
|
|
3672
|
+
function getHoverAtPosition(filePath, source, line, column, activeCases, opts) {
|
|
3222
3673
|
let file;
|
|
3223
3674
|
try {
|
|
3224
3675
|
file = parse6(source);
|
|
@@ -3227,6 +3678,7 @@ function getHoverAtPosition(filePath, source, line, column, activeCases) {
|
|
|
3227
3678
|
}
|
|
3228
3679
|
const envNames = collectEnvNames(filePath, source, false);
|
|
3229
3680
|
const fnName = findFunctionNameAtPosition(source, line, column, file);
|
|
3681
|
+
const tier = fnName !== void 0 ? interfaceTierOf(source, fnName, filePath, opts ?? {}) : void 0;
|
|
3230
3682
|
let gDisplay;
|
|
3231
3683
|
let gAbs;
|
|
3232
3684
|
let gMulti;
|
|
@@ -3241,18 +3693,26 @@ function getHoverAtPosition(filePath, source, line, column, activeCases) {
|
|
|
3241
3693
|
} catch {
|
|
3242
3694
|
}
|
|
3243
3695
|
}
|
|
3696
|
+
const withTier = (info) => {
|
|
3697
|
+
if (!info || !tier) return info;
|
|
3698
|
+
return {
|
|
3699
|
+
...info,
|
|
3700
|
+
interfaceSource: tier.source,
|
|
3701
|
+
...tier.display !== void 0 ? { interfaceDisplay: tier.display } : {}
|
|
3702
|
+
};
|
|
3703
|
+
};
|
|
3244
3704
|
const attachIntension = (info) => {
|
|
3245
|
-
if (!gDisplay) return info;
|
|
3705
|
+
if (!gDisplay) return withTier(info);
|
|
3246
3706
|
if (!info) {
|
|
3247
|
-
return { typeText: gDisplay, intension: gDisplay, abs: gAbs, absMultiline: gMulti };
|
|
3707
|
+
return withTier({ typeText: gDisplay, intension: gDisplay, abs: gAbs, absMultiline: gMulti });
|
|
3248
3708
|
}
|
|
3249
|
-
return {
|
|
3709
|
+
return withTier({
|
|
3250
3710
|
...info,
|
|
3251
3711
|
intension: gDisplay,
|
|
3252
3712
|
// 外延侧已有更准 Abs 时保留;否则用 symbolic 兜底
|
|
3253
3713
|
abs: info.abs ?? gAbs,
|
|
3254
3714
|
absMultiline: info.absMultiline ?? gMulti
|
|
3255
|
-
};
|
|
3715
|
+
});
|
|
3256
3716
|
};
|
|
3257
3717
|
const insideCaseFn = positionInsideCaseFunction(
|
|
3258
3718
|
source,
|
|
@@ -3333,6 +3793,14 @@ function getHoverAtPosition(filePath, source, line, column, activeCases) {
|
|
|
3333
3793
|
}
|
|
3334
3794
|
} catch {
|
|
3335
3795
|
}
|
|
3796
|
+
if (tier && gDisplay) {
|
|
3797
|
+
return withTier({
|
|
3798
|
+
typeText: gDisplay,
|
|
3799
|
+
intension: gDisplay,
|
|
3800
|
+
abs: gAbs,
|
|
3801
|
+
absMultiline: gMulti
|
|
3802
|
+
});
|
|
3803
|
+
}
|
|
3336
3804
|
return null;
|
|
3337
3805
|
}
|
|
3338
3806
|
function findIdentNameAtPosition(source, line, column, fileAst) {
|
|
@@ -3657,10 +4125,111 @@ function serializeInferJson(result, file) {
|
|
|
3657
4125
|
function isNudoTargetPath(path) {
|
|
3658
4126
|
const lower = path.toLowerCase();
|
|
3659
4127
|
if (lower.endsWith(".d.ts")) return false;
|
|
3660
|
-
if (lower.endsWith(".nudo.js") || lower.endsWith(".nudo.ts"))
|
|
4128
|
+
if (lower.endsWith(".nudo.js") || lower.endsWith(".nudo.mjs") || lower.endsWith(".nudo.ts") || lower.endsWith(".nudo.draft.js") || lower.endsWith(".nudo.draft.mjs") || lower.endsWith(".nudo.draft.ts")) {
|
|
4129
|
+
return false;
|
|
4130
|
+
}
|
|
3661
4131
|
return lower.endsWith(".js") || lower.endsWith(".mjs") || lower.endsWith(".ts");
|
|
3662
4132
|
}
|
|
3663
4133
|
|
|
4134
|
+
// src/watch-paths.ts
|
|
4135
|
+
var PROJECT_CONFIG_BASENAMES = /* @__PURE__ */ new Set([
|
|
4136
|
+
"package.json",
|
|
4137
|
+
"nudo.json",
|
|
4138
|
+
"nudo.config.js",
|
|
4139
|
+
"nudo.config.mjs",
|
|
4140
|
+
"nudo.config.ts",
|
|
4141
|
+
".nudorc",
|
|
4142
|
+
".nudorc.json"
|
|
4143
|
+
]);
|
|
4144
|
+
function isSidecarPath(path) {
|
|
4145
|
+
const lower = path.toLowerCase().replace(/\\/g, "/");
|
|
4146
|
+
if (lower.includes(".nudo.draft.")) return false;
|
|
4147
|
+
return lower.endsWith(".nudo.js") || lower.endsWith(".nudo.mjs") || lower.endsWith(".nudo.ts");
|
|
4148
|
+
}
|
|
4149
|
+
function isProjectConfigPath(path) {
|
|
4150
|
+
const norm2 = path.replace(/\\/g, "/");
|
|
4151
|
+
if (/\/node_modules\//.test(`/${norm2}`)) return false;
|
|
4152
|
+
const base = norm2.split("/").pop() ?? path;
|
|
4153
|
+
return PROJECT_CONFIG_BASENAMES.has(base.toLowerCase());
|
|
4154
|
+
}
|
|
4155
|
+
function isWatchRelevantPath(path) {
|
|
4156
|
+
return isNudoTargetPath(path) || isSidecarPath(path) || isProjectConfigPath(path) || isEnvTemplatePath(path);
|
|
4157
|
+
}
|
|
4158
|
+
function ambientSourcesOfSidecar(sidecarPath) {
|
|
4159
|
+
const norm2 = sidecarPath.replace(/\\/g, "/");
|
|
4160
|
+
const m = norm2.match(/^(.*)\.nudo\.(js|ts)$/i);
|
|
4161
|
+
if (!m || norm2.toLowerCase().includes(".nudo.draft.")) return [];
|
|
4162
|
+
const base = m[1];
|
|
4163
|
+
const ext = m[2].toLowerCase();
|
|
4164
|
+
return ext === "js" ? [`${base}.js`, `${base}.mjs`] : [`${base}.ts`, `${base}.mts`];
|
|
4165
|
+
}
|
|
4166
|
+
|
|
4167
|
+
// src/analysis-scope.ts
|
|
4168
|
+
import { dirname as dirname9 } from "path";
|
|
4169
|
+
import { existsSync as existsSync6 } from "fs";
|
|
4170
|
+
import { sidecarPathOf } from "@nudojs/core";
|
|
4171
|
+
var NOISY_WARNING_CODES = /* @__PURE__ */ new Set([
|
|
4172
|
+
"nudo:unknown-recv",
|
|
4173
|
+
"nudo:builtin-unknown",
|
|
4174
|
+
"nudo:no-signature"
|
|
4175
|
+
]);
|
|
4176
|
+
function filterDiagnosticsByLevel(diags, level) {
|
|
4177
|
+
if (level === "verbose") return diags;
|
|
4178
|
+
if (level === "off") return [];
|
|
4179
|
+
if (level === "errors") return diags.filter((d) => d.severity === "error");
|
|
4180
|
+
return diags.filter((d) => {
|
|
4181
|
+
if (d.severity === "error") return true;
|
|
4182
|
+
if (d.severity === "info") return false;
|
|
4183
|
+
if (d.severity === "warning" && d.code && NOISY_WARNING_CODES.has(d.code)) return false;
|
|
4184
|
+
return d.severity === "warning";
|
|
4185
|
+
});
|
|
4186
|
+
}
|
|
4187
|
+
function diagnosticsLevelForFile(filePath) {
|
|
4188
|
+
return analysisConfig(findProjectConfig(dirname9(filePath))?.config).diagnostics;
|
|
4189
|
+
}
|
|
4190
|
+
function hasNudoDirectives(source) {
|
|
4191
|
+
return /@nudo:(case|mock|pure|skip|sample|refine|interface|import|env|mock-module|as|replace)\b/.test(source);
|
|
4192
|
+
}
|
|
4193
|
+
function stripCommentsAndStrings(source) {
|
|
4194
|
+
return source.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/\/\/[^\n]*/g, " ").replace(/'(?:\\.|[^'\\])*'|"(?:\\.|[^"\\])*"|`(?:\\.|[^`\\])*`/g, '""');
|
|
4195
|
+
}
|
|
4196
|
+
function hasExport(source) {
|
|
4197
|
+
const s = stripCommentsAndStrings(source);
|
|
4198
|
+
return /(^|[\s;}])export\b/.test(s) || /\bmodule\.exports\b/.test(s) || /\bexports\s*[.[]/.test(s) || /Object\.assign\s*\(\s*(module\.)?exports\b/.test(s);
|
|
4199
|
+
}
|
|
4200
|
+
function shouldAnalyzeFile(filePath, source, config) {
|
|
4201
|
+
if (!isNudoTargetPath(filePath)) return false;
|
|
4202
|
+
const proj = findProjectConfig(dirname9(filePath));
|
|
4203
|
+
const cfg = config ?? analysisConfig(proj?.config);
|
|
4204
|
+
const projectDir = proj?.projectDir;
|
|
4205
|
+
if (cfg.exclude.length > 0 && projectDir) {
|
|
4206
|
+
if (matchesEmitAllowlist(filePath, projectDir, cfg.exclude)) return false;
|
|
4207
|
+
} else if (cfg.exclude.length > 0 && !projectDir) {
|
|
4208
|
+
const norm2 = filePath.replace(/\\/g, "/");
|
|
4209
|
+
if (cfg.exclude.some((p) => simpleExcludeHit(p, norm2))) return false;
|
|
4210
|
+
}
|
|
4211
|
+
if (cfg.include.length > 0 && projectDir) {
|
|
4212
|
+
if (!matchesEmitAllowlist(filePath, projectDir, cfg.include)) return false;
|
|
4213
|
+
}
|
|
4214
|
+
if (cfg.mode === "all") return true;
|
|
4215
|
+
const text = source;
|
|
4216
|
+
if (text !== void 0 && hasNudoDirectives(text)) return true;
|
|
4217
|
+
if (cfg.mode === "directives") return false;
|
|
4218
|
+
if (text !== void 0 && hasExport(text)) return true;
|
|
4219
|
+
try {
|
|
4220
|
+
if (existsSync6(sidecarPathOf(filePath))) return true;
|
|
4221
|
+
} catch {
|
|
4222
|
+
}
|
|
4223
|
+
return false;
|
|
4224
|
+
}
|
|
4225
|
+
function simpleExcludeHit(pattern, path) {
|
|
4226
|
+
const bare = pattern.replace(/^\*\*/, "").replace(/^\//, "").replace(/\/\*\*$/, "").replace(/^\*\//, "");
|
|
4227
|
+
if (!bare || bare.includes("*")) {
|
|
4228
|
+
return /\/(node_modules|dist|coverage)\//.test("/" + path + "/");
|
|
4229
|
+
}
|
|
4230
|
+
return path.includes(`/${bare}/`) || path.endsWith(`/${bare}`);
|
|
4231
|
+
}
|
|
4232
|
+
|
|
3664
4233
|
// src/session-cache.ts
|
|
3665
4234
|
import {
|
|
3666
4235
|
resetGeneralizeMemo,
|
|
@@ -3674,10 +4243,12 @@ function evictAnalysisCachesForFiles(files) {
|
|
|
3674
4243
|
evictAnalysisFileCacheForFiles(files);
|
|
3675
4244
|
evictFnAnalysisCacheForFiles(files);
|
|
3676
4245
|
evictAbsModuleCacheFiles(files);
|
|
4246
|
+
clearPathEnvCaches();
|
|
3677
4247
|
}
|
|
3678
4248
|
function clearAnalysisSessionCaches() {
|
|
3679
4249
|
clearBPathCache();
|
|
3680
4250
|
clearAbsModuleCache();
|
|
4251
|
+
clearPathEnvCaches();
|
|
3681
4252
|
resetGeneralizeMemo();
|
|
3682
4253
|
resetCheckSourceMemo();
|
|
3683
4254
|
resetNudoModuleExecCache();
|
|
@@ -3687,20 +4258,269 @@ function resetAllAnalysisCaches() {
|
|
|
3687
4258
|
resetParseSourceCache();
|
|
3688
4259
|
}
|
|
3689
4260
|
|
|
4261
|
+
// src/analysis-session.ts
|
|
4262
|
+
function createDefaultSession() {
|
|
4263
|
+
return {
|
|
4264
|
+
evictForDependents(files) {
|
|
4265
|
+
evictAnalysisCachesForFiles(files);
|
|
4266
|
+
},
|
|
4267
|
+
clear() {
|
|
4268
|
+
clearAnalysisSessionCaches();
|
|
4269
|
+
},
|
|
4270
|
+
reset() {
|
|
4271
|
+
resetAllAnalysisCaches();
|
|
4272
|
+
},
|
|
4273
|
+
analyze(filePath, source, activeCases, externalCallRecords) {
|
|
4274
|
+
return analyzeFile(
|
|
4275
|
+
filePath,
|
|
4276
|
+
source,
|
|
4277
|
+
activeCases,
|
|
4278
|
+
externalCallRecords
|
|
4279
|
+
);
|
|
4280
|
+
}
|
|
4281
|
+
};
|
|
4282
|
+
}
|
|
4283
|
+
var defaultSession = void 0;
|
|
4284
|
+
function getAnalysisSession() {
|
|
4285
|
+
if (!defaultSession) defaultSession = createDefaultSession();
|
|
4286
|
+
return defaultSession;
|
|
4287
|
+
}
|
|
4288
|
+
function setAnalysisSession(session) {
|
|
4289
|
+
const prev = defaultSession;
|
|
4290
|
+
defaultSession = session;
|
|
4291
|
+
return prev;
|
|
4292
|
+
}
|
|
4293
|
+
|
|
4294
|
+
// src/disk-cache.ts
|
|
4295
|
+
import { createHash } from "crypto";
|
|
4296
|
+
import { createRequire } from "module";
|
|
4297
|
+
import { mkdirSync, readFileSync as readFileSync6, writeFileSync, existsSync as existsSync7, rmSync } from "fs";
|
|
4298
|
+
import { join as join5, dirname as dirname10, relative, sep, isAbsolute } from "path";
|
|
4299
|
+
function readServiceVersion() {
|
|
4300
|
+
try {
|
|
4301
|
+
const require2 = createRequire(import.meta.url);
|
|
4302
|
+
for (const p of ["../package.json", "./package.json", "../../package.json"]) {
|
|
4303
|
+
try {
|
|
4304
|
+
const pkg = require2(p);
|
|
4305
|
+
if (pkg?.name === "@nudojs/service" && pkg.version) return pkg.version;
|
|
4306
|
+
if (pkg?.version && p.includes("service")) return pkg.version;
|
|
4307
|
+
} catch {
|
|
4308
|
+
}
|
|
4309
|
+
}
|
|
4310
|
+
} catch {
|
|
4311
|
+
}
|
|
4312
|
+
return "0";
|
|
4313
|
+
}
|
|
4314
|
+
var ANALYSIS_ABI = `nudo-check-cache-v2+${readServiceVersion()}`;
|
|
4315
|
+
function sha256Hex(data) {
|
|
4316
|
+
return createHash("sha256").update(data).digest("hex");
|
|
4317
|
+
}
|
|
4318
|
+
function relativizePath(p, root) {
|
|
4319
|
+
const norm2 = p.split(sep).join("/");
|
|
4320
|
+
if (!root) return norm2;
|
|
4321
|
+
const r = relative(root, p).split(sep).join("/");
|
|
4322
|
+
if (!r.startsWith("..") && !isAbsolute(r)) return r;
|
|
4323
|
+
return `ext:${createHash("sha256").update(norm2).digest("hex").slice(0, 16)}`;
|
|
4324
|
+
}
|
|
4325
|
+
function sanitizeCacheNamespace(ns) {
|
|
4326
|
+
return ns.replace(/[^a-z0-9_-]/gi, "_").replace(/^_+|_+$/g, "") || "cache";
|
|
4327
|
+
}
|
|
4328
|
+
var DiskCache = class {
|
|
4329
|
+
root;
|
|
4330
|
+
ns;
|
|
4331
|
+
enabled = false;
|
|
4332
|
+
constructor(opts) {
|
|
4333
|
+
this.root = opts.root;
|
|
4334
|
+
this.ns = sanitizeCacheNamespace(opts.namespace);
|
|
4335
|
+
this.enabled = !!opts.root;
|
|
4336
|
+
}
|
|
4337
|
+
pathFor(key) {
|
|
4338
|
+
if (!/^[a-f0-9]{16,128}$/i.test(key)) {
|
|
4339
|
+
throw new Error("DiskCache key must be a hex digest");
|
|
4340
|
+
}
|
|
4341
|
+
return join5(this.root, this.ns, key.slice(0, 2), `${key}.json`);
|
|
4342
|
+
}
|
|
4343
|
+
get(key) {
|
|
4344
|
+
if (!this.enabled || !this.root) return void 0;
|
|
4345
|
+
try {
|
|
4346
|
+
const p = this.pathFor(key);
|
|
4347
|
+
if (!existsSync7(p)) return void 0;
|
|
4348
|
+
const raw = readFileSync6(p, "utf8");
|
|
4349
|
+
const parsed = JSON.parse(raw);
|
|
4350
|
+
if (parsed?.abi !== ANALYSIS_ABI) return void 0;
|
|
4351
|
+
return parsed.value;
|
|
4352
|
+
} catch {
|
|
4353
|
+
return void 0;
|
|
4354
|
+
}
|
|
4355
|
+
}
|
|
4356
|
+
set(key, value) {
|
|
4357
|
+
if (!this.enabled || !this.root) return;
|
|
4358
|
+
try {
|
|
4359
|
+
const p = this.pathFor(key);
|
|
4360
|
+
mkdirSync(dirname10(p), { recursive: true });
|
|
4361
|
+
writeFileSync(p, JSON.stringify({ abi: ANALYSIS_ABI, value }), "utf8");
|
|
4362
|
+
} catch {
|
|
4363
|
+
}
|
|
4364
|
+
}
|
|
4365
|
+
clearNamespace() {
|
|
4366
|
+
if (!this.enabled || !this.root) return;
|
|
4367
|
+
try {
|
|
4368
|
+
rmSync(join5(this.root, this.ns), { recursive: true, force: true });
|
|
4369
|
+
} catch {
|
|
4370
|
+
}
|
|
4371
|
+
}
|
|
4372
|
+
};
|
|
4373
|
+
function checkCacheKey(filePath, source, opts) {
|
|
4374
|
+
const rel = relativizePath(filePath, opts.projectDir);
|
|
4375
|
+
const sidecarSha = opts.sidecarContent != null ? sha256Hex(opts.sidecarContent) : "nosidecar";
|
|
4376
|
+
const depSeg = (opts.depContents ?? []).map(
|
|
4377
|
+
(d) => `${relativizePath(d.path, opts.projectDir)}\0${d.content != null ? sha256Hex(d.content) : "miss"}`
|
|
4378
|
+
).join("\n");
|
|
4379
|
+
const envSeg = (opts.projectEnvNames ?? []).length > 0 ? [...opts.projectEnvNames ?? []].sort().join(",") : "-";
|
|
4380
|
+
const cfgSeg = opts.analysisCfg ? `${opts.analysisCfg.mode ?? "-"}|${opts.analysisCfg.evalMissingSlot ?? "-"}|${opts.analysisCfg.callSiteBudget ?? "-"}` : "-";
|
|
4381
|
+
return sha256Hex(
|
|
4382
|
+
[
|
|
4383
|
+
ANALYSIS_ABI,
|
|
4384
|
+
rel,
|
|
4385
|
+
opts.autoBind ? "ab1" : "ab0",
|
|
4386
|
+
sha256Hex(source),
|
|
4387
|
+
sidecarSha,
|
|
4388
|
+
depSeg,
|
|
4389
|
+
envSeg,
|
|
4390
|
+
cfgSeg
|
|
4391
|
+
].join("\0")
|
|
4392
|
+
);
|
|
4393
|
+
}
|
|
4394
|
+
function ifaceCacheKey(filePath, source, opts) {
|
|
4395
|
+
const rel = relativizePath(filePath, opts.projectDir);
|
|
4396
|
+
const sidecarSeg = opts.autoBind && opts.sidecarSource !== void 0 ? `sc:${sha256Hex(opts.sidecarSource)}` : "sc0";
|
|
4397
|
+
const depSeg = (opts.depContents ?? []).map(
|
|
4398
|
+
(d) => `${relativizePath(d.path, opts.projectDir)}\0${d.content != null ? sha256Hex(d.content) : "miss"}`
|
|
4399
|
+
).join("\n");
|
|
4400
|
+
const envSeg = (opts.projectEnvNames ?? []).length > 0 ? [...opts.projectEnvNames ?? []].sort().join(",") : "-";
|
|
4401
|
+
return sha256Hex(
|
|
4402
|
+
[
|
|
4403
|
+
ANALYSIS_ABI,
|
|
4404
|
+
"iface",
|
|
4405
|
+
rel,
|
|
4406
|
+
opts.autoBind ? "ab1" : "ab0",
|
|
4407
|
+
sha256Hex(source),
|
|
4408
|
+
sidecarSeg,
|
|
4409
|
+
depSeg,
|
|
4410
|
+
envSeg
|
|
4411
|
+
].join("\0")
|
|
4412
|
+
);
|
|
4413
|
+
}
|
|
4414
|
+
function extractNudoImportSpecs(source) {
|
|
4415
|
+
const specs = /* @__PURE__ */ new Set();
|
|
4416
|
+
const named = /@nudo:import\s*\{[^}]*\}\s*from\s*["']([^"']+)["']/g;
|
|
4417
|
+
const ns = /@nudo:import\s+\*\s+as\s+\w+\s+from\s*["']([^"']+)["']/g;
|
|
4418
|
+
let m;
|
|
4419
|
+
while (m = named.exec(source)) specs.add(m[1]);
|
|
4420
|
+
while (m = ns.exec(source)) specs.add(m[1]);
|
|
4421
|
+
return [...specs];
|
|
4422
|
+
}
|
|
4423
|
+
|
|
4424
|
+
// src/dep-contents.ts
|
|
4425
|
+
import { loadModuleDepsFingerprint as loadModuleDepsFingerprint3 } from "@nudojs/core";
|
|
4426
|
+
function collectLoadDepContents(filePath, source, loadModule) {
|
|
4427
|
+
const fp = loadModuleDepsFingerprint3(source, loadModule, filePath);
|
|
4428
|
+
const depContents = fp.contents.length > 0 ? fp.contents : fp.paths.map((path) => ({ path, content: null }));
|
|
4429
|
+
return { depContents, truncated: fp.truncated };
|
|
4430
|
+
}
|
|
4431
|
+
|
|
3690
4432
|
// src/harvest-node.ts
|
|
3691
|
-
import { existsSync as
|
|
4433
|
+
import { existsSync as existsSync8, statSync as statSync6 } from "fs";
|
|
4434
|
+
import { join as join6 } from "path";
|
|
3692
4435
|
import { harvestDts as harvestDts2 } from "@nudojs/harvester";
|
|
3693
|
-
|
|
4436
|
+
var HARVEST_NODE_DEFAULT_MAX_FILES = 12;
|
|
4437
|
+
var HARVEST_NODE_DEFAULT_MAX_MS = 2500;
|
|
4438
|
+
var nodeHarvestCache = /* @__PURE__ */ new Map();
|
|
4439
|
+
function notFoundCacheKey(fromDir) {
|
|
4440
|
+
return `not-found|@types/node|${fromDir ?? ""}`;
|
|
4441
|
+
}
|
|
4442
|
+
function nodeHarvestCacheKey(root, maxFiles, maxMs) {
|
|
4443
|
+
let sig = "nostat";
|
|
4444
|
+
try {
|
|
4445
|
+
const st = statSync6(join6(root, "package.json"));
|
|
4446
|
+
sig = `${st.size}:${Math.floor(st.mtimeMs)}`;
|
|
4447
|
+
} catch {
|
|
4448
|
+
}
|
|
4449
|
+
return `${root}|${maxFiles}|${maxMs}|${sig}`;
|
|
4450
|
+
}
|
|
4451
|
+
function clearNodeHarvestCache() {
|
|
4452
|
+
nodeHarvestCache.clear();
|
|
4453
|
+
}
|
|
4454
|
+
function getNodeHarvestCacheSize() {
|
|
4455
|
+
return nodeHarvestCache.size;
|
|
4456
|
+
}
|
|
4457
|
+
function isHarvestNodeDisabled(env = process.env) {
|
|
4458
|
+
return env.NUDO_HARVEST_NODE === "off";
|
|
4459
|
+
}
|
|
4460
|
+
function harvestNodeTypes(fromDir, maxFiles = HARVEST_NODE_DEFAULT_MAX_FILES, maxMs = HARVEST_NODE_DEFAULT_MAX_MS) {
|
|
4461
|
+
if (isHarvestNodeDisabled()) {
|
|
4462
|
+
return {
|
|
4463
|
+
ok: false,
|
|
4464
|
+
error: "harvest disabled via NUDO_HARVEST_NODE=off",
|
|
4465
|
+
reason: "disabled"
|
|
4466
|
+
};
|
|
4467
|
+
}
|
|
3694
4468
|
const root = resolvePackageRoot("@types/node", fromDir) ?? resolvePackageRoot("node", fromDir);
|
|
3695
|
-
if (!root || !
|
|
3696
|
-
|
|
4469
|
+
if (!root || !existsSync8(root)) {
|
|
4470
|
+
const missKey = notFoundCacheKey(fromDir);
|
|
4471
|
+
const miss = nodeHarvestCache.get(missKey);
|
|
4472
|
+
if (miss && !miss.ok && miss.reason === "not-found") {
|
|
4473
|
+
return { ...miss, cached: true };
|
|
4474
|
+
}
|
|
4475
|
+
const result = {
|
|
4476
|
+
ok: false,
|
|
4477
|
+
error: "@types/node not found",
|
|
4478
|
+
reason: "not-found"
|
|
4479
|
+
};
|
|
4480
|
+
nodeHarvestCache.set(missKey, result);
|
|
4481
|
+
return result;
|
|
4482
|
+
}
|
|
4483
|
+
const key = nodeHarvestCacheKey(root, maxFiles, maxMs);
|
|
4484
|
+
const hit = nodeHarvestCache.get(key);
|
|
4485
|
+
if (hit) {
|
|
4486
|
+
return hit.ok ? { ...hit, cached: true } : { ...hit, cached: true };
|
|
3697
4487
|
}
|
|
3698
4488
|
const dts = collectDtsFiles(root, maxFiles);
|
|
3699
4489
|
if (dts.length === 0) {
|
|
3700
|
-
|
|
4490
|
+
const result = {
|
|
4491
|
+
ok: false,
|
|
4492
|
+
error: `no .d.ts under ${root}`,
|
|
4493
|
+
reason: "no-dts"
|
|
4494
|
+
};
|
|
4495
|
+
nodeHarvestCache.set(key, result);
|
|
4496
|
+
return result;
|
|
4497
|
+
}
|
|
4498
|
+
try {
|
|
4499
|
+
const env = harvestDts2(dts, { maxMs });
|
|
4500
|
+
const stats = {
|
|
4501
|
+
files: env.stats.files,
|
|
4502
|
+
symbols: env.stats.symbols,
|
|
4503
|
+
skipped: env.stats.skipped
|
|
4504
|
+
};
|
|
4505
|
+
const result = {
|
|
4506
|
+
ok: true,
|
|
4507
|
+
env,
|
|
4508
|
+
root,
|
|
4509
|
+
files: stats.files,
|
|
4510
|
+
stats,
|
|
4511
|
+
cached: false
|
|
4512
|
+
};
|
|
4513
|
+
nodeHarvestCache.set(key, result);
|
|
4514
|
+
return result;
|
|
4515
|
+
} catch (e) {
|
|
4516
|
+
const result = {
|
|
4517
|
+
ok: false,
|
|
4518
|
+
error: `harvest failed: ${e.message}`,
|
|
4519
|
+
reason: "failed"
|
|
4520
|
+
};
|
|
4521
|
+
nodeHarvestCache.set(key, result);
|
|
4522
|
+
return result;
|
|
3701
4523
|
}
|
|
3702
|
-
const env = harvestDts2(dts, { maxMs });
|
|
3703
|
-
return { ok: true, env, root, files: env.stats.files };
|
|
3704
4524
|
}
|
|
3705
4525
|
function summarizeNodeEnv(env) {
|
|
3706
4526
|
return {
|
|
@@ -3712,6 +4532,9 @@ function summarizeNodeEnv(env) {
|
|
|
3712
4532
|
|
|
3713
4533
|
// src/semantic-tokens.ts
|
|
3714
4534
|
import { parse as parse7 } from "@nudojs/parser";
|
|
4535
|
+
import {
|
|
4536
|
+
interfaceTierOf as interfaceTierOf2
|
|
4537
|
+
} from "@nudojs/core";
|
|
3715
4538
|
var SEMANTIC_TOKEN_TYPES = [
|
|
3716
4539
|
"function",
|
|
3717
4540
|
"variable",
|
|
@@ -3729,7 +4552,13 @@ var SEMANTIC_TOKEN_MODIFIERS = [
|
|
|
3729
4552
|
"declaration",
|
|
3730
4553
|
"readonly",
|
|
3731
4554
|
"deprecated",
|
|
3732
|
-
"unreachable"
|
|
4555
|
+
"unreachable",
|
|
4556
|
+
/** handwritten:显式契约(侧车手写 / @nudo:refine) */
|
|
4557
|
+
"contract",
|
|
4558
|
+
/** generated:侧车 @generated 段 */
|
|
4559
|
+
"generated",
|
|
4560
|
+
/** derived:implicit 展示档(非义务契约) */
|
|
4561
|
+
"derived"
|
|
3733
4562
|
];
|
|
3734
4563
|
function encodeSemanticTokens(tokens) {
|
|
3735
4564
|
const result = [];
|
|
@@ -3750,6 +4579,12 @@ var TYPE_PARAMETER = SEMANTIC_TOKEN_TYPES.indexOf("parameter");
|
|
|
3750
4579
|
var TYPE_PROPERTY = SEMANTIC_TOKEN_TYPES.indexOf("property");
|
|
3751
4580
|
var TYPE_METHOD = SEMANTIC_TOKEN_TYPES.indexOf("method");
|
|
3752
4581
|
var MOD_DECLARATION = 1 << SEMANTIC_TOKEN_MODIFIERS.indexOf("declaration");
|
|
4582
|
+
var MOD_CONTRACT = 1 << SEMANTIC_TOKEN_MODIFIERS.indexOf("contract");
|
|
4583
|
+
var MOD_GENERATED = 1 << SEMANTIC_TOKEN_MODIFIERS.indexOf("generated");
|
|
4584
|
+
var MOD_DERIVED = 1 << SEMANTIC_TOKEN_MODIFIERS.indexOf("derived");
|
|
4585
|
+
function interfaceTierModifierBit(src) {
|
|
4586
|
+
return src === "handwritten" ? MOD_CONTRACT : src === "generated" ? MOD_GENERATED : MOD_DERIVED;
|
|
4587
|
+
}
|
|
3753
4588
|
function collectFunctionBindingNames(filePath, source, _ast) {
|
|
3754
4589
|
try {
|
|
3755
4590
|
const binds = collectAbsBindingsFromGraph(source, filePath);
|
|
@@ -3764,7 +4599,7 @@ function collectFunctionBindingNames(filePath, source, _ast) {
|
|
|
3764
4599
|
}
|
|
3765
4600
|
return /* @__PURE__ */ new Set();
|
|
3766
4601
|
}
|
|
3767
|
-
function buildSemanticTokens(filePath, source) {
|
|
4602
|
+
function buildSemanticTokens(filePath, source, opts) {
|
|
3768
4603
|
let ast;
|
|
3769
4604
|
try {
|
|
3770
4605
|
ast = parse7(source);
|
|
@@ -3774,6 +4609,7 @@ function buildSemanticTokens(filePath, source) {
|
|
|
3774
4609
|
const functionNames = collectFunctionBindingNames(filePath, source, ast);
|
|
3775
4610
|
const program = ast.program ?? ast;
|
|
3776
4611
|
const topLevelDeclarators = /* @__PURE__ */ new Set();
|
|
4612
|
+
const topLevelFnDeclNodes = /* @__PURE__ */ new Set();
|
|
3777
4613
|
for (const stmt of program.body ?? []) {
|
|
3778
4614
|
const decl = stmt.type === "ExportNamedDeclaration" || stmt.type === "ExportDefaultDeclaration" ? stmt.declaration ?? null : stmt;
|
|
3779
4615
|
if (decl && decl.type === "VariableDeclaration") {
|
|
@@ -3781,9 +4617,30 @@ function buildSemanticTokens(filePath, source) {
|
|
|
3781
4617
|
topLevelDeclarators.add(d);
|
|
3782
4618
|
}
|
|
3783
4619
|
}
|
|
4620
|
+
if (decl && decl.type === "FunctionDeclaration" && decl.id) {
|
|
4621
|
+
topLevelFnDeclNodes.add(decl);
|
|
4622
|
+
}
|
|
3784
4623
|
}
|
|
4624
|
+
const tierOpts = {
|
|
4625
|
+
...opts?.loadModule ? { loadModule: opts.loadModule } : {},
|
|
4626
|
+
...opts?.autoBind !== void 0 ? { autoBind: opts.autoBind } : {}
|
|
4627
|
+
};
|
|
4628
|
+
const tierModCache = /* @__PURE__ */ new Map();
|
|
4629
|
+
const tierModFor = (name) => {
|
|
4630
|
+
const cached = tierModCache.get(name);
|
|
4631
|
+
if (cached !== void 0) return cached;
|
|
4632
|
+
let bit = 0;
|
|
4633
|
+
try {
|
|
4634
|
+
const tier = interfaceTierOf2(source, name, filePath, tierOpts);
|
|
4635
|
+
if (tier) bit = interfaceTierModifierBit(tier.source);
|
|
4636
|
+
} catch {
|
|
4637
|
+
bit = 0;
|
|
4638
|
+
}
|
|
4639
|
+
tierModCache.set(name, bit);
|
|
4640
|
+
return bit;
|
|
4641
|
+
};
|
|
3785
4642
|
const tokens = [];
|
|
3786
|
-
const pushIdentifier = (id, typeIndex) => {
|
|
4643
|
+
const pushIdentifier = (id, typeIndex, extraMod = 0) => {
|
|
3787
4644
|
const loc = id.loc;
|
|
3788
4645
|
const name = id.name;
|
|
3789
4646
|
if (!loc || typeof name !== "string") return;
|
|
@@ -3792,7 +4649,7 @@ function buildSemanticTokens(filePath, source) {
|
|
|
3792
4649
|
char: loc.start.column,
|
|
3793
4650
|
length: name.length,
|
|
3794
4651
|
typeIndex,
|
|
3795
|
-
modifierBitmask: MOD_DECLARATION
|
|
4652
|
+
modifierBitmask: MOD_DECLARATION | extraMod
|
|
3796
4653
|
});
|
|
3797
4654
|
};
|
|
3798
4655
|
const collectParams = (params) => {
|
|
@@ -3815,15 +4672,23 @@ function buildSemanticTokens(filePath, source) {
|
|
|
3815
4672
|
const id = n.id;
|
|
3816
4673
|
if (id?.type === "Identifier") {
|
|
3817
4674
|
const isTopLevel = topLevelDeclarators.has(node);
|
|
3818
|
-
const
|
|
3819
|
-
|
|
4675
|
+
const name = id.name;
|
|
4676
|
+
const isFn = isTopLevel && isFunctionValue(name);
|
|
4677
|
+
const typeIndex = isFn ? TYPE_FUNCTION : TYPE_VARIABLE;
|
|
4678
|
+
const extraMod = isFn ? tierModFor(name) : 0;
|
|
4679
|
+
pushIdentifier(id, typeIndex, extraMod);
|
|
3820
4680
|
}
|
|
3821
4681
|
break;
|
|
3822
4682
|
}
|
|
3823
4683
|
case "FunctionDeclaration":
|
|
3824
4684
|
case "FunctionExpression": {
|
|
3825
4685
|
const id = n.id;
|
|
3826
|
-
if (id?.type === "Identifier")
|
|
4686
|
+
if (id?.type === "Identifier") {
|
|
4687
|
+
const name = id.name;
|
|
4688
|
+
const isTopLevelFnDecl = n.type === "FunctionDeclaration" && topLevelFnDeclNodes.has(node);
|
|
4689
|
+
const extraMod = isTopLevelFnDecl ? tierModFor(name) : 0;
|
|
4690
|
+
pushIdentifier(id, TYPE_FUNCTION, extraMod);
|
|
4691
|
+
}
|
|
3827
4692
|
collectParams(n.params ?? []);
|
|
3828
4693
|
break;
|
|
3829
4694
|
}
|
|
@@ -3871,14 +4736,102 @@ import {
|
|
|
3871
4736
|
litValue as litValue3,
|
|
3872
4737
|
abs as makeAbs2,
|
|
3873
4738
|
isTemplateLike,
|
|
3874
|
-
templatePartsOf
|
|
4739
|
+
templatePartsOf,
|
|
4740
|
+
collectAbsFreeVars
|
|
3875
4741
|
} from "@nudojs/core";
|
|
3876
|
-
function
|
|
3877
|
-
|
|
3878
|
-
if (
|
|
4742
|
+
function tsTypeParamName(id) {
|
|
4743
|
+
let n = id.replace(/[^A-Za-z0-9_$]/g, "_");
|
|
4744
|
+
if (!/^[A-Za-z_$]/.test(n)) n = `T_${n}`;
|
|
4745
|
+
if (n.length === 0) n = "T";
|
|
4746
|
+
return n;
|
|
4747
|
+
}
|
|
4748
|
+
function wrapComplexAbs(a, typeVars) {
|
|
4749
|
+
const ts = absToTSType(a, typeVars);
|
|
4750
|
+
if (a.shape.k === "sum" || a.shape.k === "fn") return `(${ts})`;
|
|
3879
4751
|
return ts;
|
|
3880
4752
|
}
|
|
3881
|
-
function
|
|
4753
|
+
function wrapUnionMember(a, typeVars) {
|
|
4754
|
+
const ts = absToTSType(a, typeVars);
|
|
4755
|
+
if (a.shape.k === "fn") return `(${ts})`;
|
|
4756
|
+
return ts;
|
|
4757
|
+
}
|
|
4758
|
+
var TS_PARAM_RESERVED = /* @__PURE__ */ new Set([
|
|
4759
|
+
"break",
|
|
4760
|
+
"case",
|
|
4761
|
+
"catch",
|
|
4762
|
+
"class",
|
|
4763
|
+
"const",
|
|
4764
|
+
"continue",
|
|
4765
|
+
"debugger",
|
|
4766
|
+
"default",
|
|
4767
|
+
"delete",
|
|
4768
|
+
"do",
|
|
4769
|
+
"else",
|
|
4770
|
+
"enum",
|
|
4771
|
+
"export",
|
|
4772
|
+
"extends",
|
|
4773
|
+
"false",
|
|
4774
|
+
"finally",
|
|
4775
|
+
"for",
|
|
4776
|
+
"function",
|
|
4777
|
+
"if",
|
|
4778
|
+
"import",
|
|
4779
|
+
"in",
|
|
4780
|
+
"instanceof",
|
|
4781
|
+
"new",
|
|
4782
|
+
"null",
|
|
4783
|
+
"return",
|
|
4784
|
+
"super",
|
|
4785
|
+
"switch",
|
|
4786
|
+
"this",
|
|
4787
|
+
"throw",
|
|
4788
|
+
"true",
|
|
4789
|
+
"try",
|
|
4790
|
+
"typeof",
|
|
4791
|
+
"var",
|
|
4792
|
+
"void",
|
|
4793
|
+
"while",
|
|
4794
|
+
"with",
|
|
4795
|
+
"yield",
|
|
4796
|
+
"let",
|
|
4797
|
+
"static",
|
|
4798
|
+
"await",
|
|
4799
|
+
"implements",
|
|
4800
|
+
"interface",
|
|
4801
|
+
"package",
|
|
4802
|
+
"private",
|
|
4803
|
+
"protected",
|
|
4804
|
+
"public",
|
|
4805
|
+
"arguments",
|
|
4806
|
+
"eval",
|
|
4807
|
+
"constructor"
|
|
4808
|
+
]);
|
|
4809
|
+
function isTsIdent(name) {
|
|
4810
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) && !TS_PARAM_RESERVED.has(name);
|
|
4811
|
+
}
|
|
4812
|
+
function sanitizeParamName(name, index) {
|
|
4813
|
+
if (name.startsWith("...")) {
|
|
4814
|
+
const rest = name.slice(3);
|
|
4815
|
+
if (isTsIdent(rest)) return name;
|
|
4816
|
+
return `...arg${index}`;
|
|
4817
|
+
}
|
|
4818
|
+
if (isTsIdent(name)) return name;
|
|
4819
|
+
return `arg${index}`;
|
|
4820
|
+
}
|
|
4821
|
+
function formatPropKey(k) {
|
|
4822
|
+
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(k)) return k;
|
|
4823
|
+
if (/^\d+$/.test(k)) return k;
|
|
4824
|
+
return JSON.stringify(k);
|
|
4825
|
+
}
|
|
4826
|
+
function absToTSType(a, typeVars) {
|
|
4827
|
+
if (a.shape.k === "any" && a.term?.op === "var" && typeVars) {
|
|
4828
|
+
const mapped = typeVars.get(a.term.id);
|
|
4829
|
+
if (mapped) return mapped;
|
|
4830
|
+
}
|
|
4831
|
+
if (a.shape.k === "arr" && a.shape.element.shape.k === "any" && a.shape.element.term?.op === "var" && typeVars) {
|
|
4832
|
+
const mapped = typeVars.get(a.shape.element.term.id);
|
|
4833
|
+
if (mapped) return `${mapped}[]`;
|
|
4834
|
+
}
|
|
3882
4835
|
if (a.term?.op === "lit") {
|
|
3883
4836
|
const v = a.term.value;
|
|
3884
4837
|
if (v === null) return "null";
|
|
@@ -3894,7 +4847,7 @@ function absToTSType(a) {
|
|
|
3894
4847
|
const inner = parts.map((p) => {
|
|
3895
4848
|
const lv = litValue3(p);
|
|
3896
4849
|
if (typeof lv === "string") return lv;
|
|
3897
|
-
return `\${${absToTSType(p)}}`;
|
|
4850
|
+
return `\${${absToTSType(p, typeVars)}}`;
|
|
3898
4851
|
}).join("");
|
|
3899
4852
|
return `\`${inner}\``;
|
|
3900
4853
|
}
|
|
@@ -3908,34 +4861,50 @@ function absToTSType(a) {
|
|
|
3908
4861
|
return a.shape.type;
|
|
3909
4862
|
case "obj": {
|
|
3910
4863
|
const entries = Object.entries(a.shape.slots).map(([k, slot]) => {
|
|
3911
|
-
const inner = absToTSType(slot.value);
|
|
4864
|
+
const inner = absToTSType(slot.value, typeVars);
|
|
3912
4865
|
if (slot.optional) {
|
|
3913
|
-
const t = slot.value
|
|
3914
|
-
return `${k}: ${t} | undefined`;
|
|
4866
|
+
const t = wrapComplexAbs(slot.value, typeVars);
|
|
4867
|
+
return `${formatPropKey(k)}: ${t} | undefined`;
|
|
3915
4868
|
}
|
|
3916
|
-
return `${k}: ${inner}`;
|
|
4869
|
+
return `${formatPropKey(k)}: ${inner}`;
|
|
3917
4870
|
});
|
|
3918
4871
|
if (entries.length === 0) return "{}";
|
|
3919
4872
|
return `{ ${entries.join("; ")} }`;
|
|
3920
4873
|
}
|
|
3921
4874
|
case "arr":
|
|
3922
|
-
return `${wrapComplexAbs(a.shape.element)}[]`;
|
|
4875
|
+
return `${wrapComplexAbs(a.shape.element, typeVars)}[]`;
|
|
3923
4876
|
case "tuple": {
|
|
3924
|
-
const
|
|
3925
|
-
|
|
4877
|
+
const parts = a.shape.elements.map((e) => absToTSType(e, typeVars));
|
|
4878
|
+
if (a.shape.rest) {
|
|
4879
|
+
const rest = a.shape.rest;
|
|
4880
|
+
const restTs = rest.shape.k === "arr" ? absToTSType(rest, typeVars) : `${absToTSType(rest, typeVars)}[]`;
|
|
4881
|
+
parts.push(`...${restTs}`);
|
|
4882
|
+
}
|
|
4883
|
+
return `[${parts.join(", ")}]`;
|
|
3926
4884
|
}
|
|
3927
4885
|
case "fn": {
|
|
3928
|
-
const
|
|
3929
|
-
const
|
|
4886
|
+
const paramTypes = a.shape.paramTypes;
|
|
4887
|
+
const params = a.shape.params.map((p, i) => {
|
|
4888
|
+
const isRest = p.startsWith("...");
|
|
4889
|
+
const name = sanitizeParamName(p, i);
|
|
4890
|
+
const pt = paramTypes?.[i];
|
|
4891
|
+
let typeStr;
|
|
4892
|
+
if (pt) typeStr = absToTSType(pt, typeVars);
|
|
4893
|
+
else if (isRest) typeStr = "unknown[]";
|
|
4894
|
+
else typeStr = "unknown";
|
|
4895
|
+
return `${name}: ${typeStr}`;
|
|
4896
|
+
}).join(", ");
|
|
4897
|
+
const ret = a.shape.returnType ? absToTSType(a.shape.returnType, typeVars) : "unknown";
|
|
3930
4898
|
return `(${params}) => ${ret}`;
|
|
3931
4899
|
}
|
|
3932
4900
|
case "brand":
|
|
3933
|
-
return a.shape.name;
|
|
4901
|
+
return isTsIdent(a.shape.name) || /^[A-Z][A-Za-z0-9_$]*$/.test(a.shape.name) ? a.shape.name : "unknown";
|
|
3934
4902
|
case "eff":
|
|
3935
|
-
if (a.shape.eff === "promise")
|
|
3936
|
-
|
|
4903
|
+
if (a.shape.eff === "promise")
|
|
4904
|
+
return `Promise<${absToTSType(a.shape.inner, typeVars)}>`;
|
|
4905
|
+
return absToTSType(a.shape.inner, typeVars);
|
|
3937
4906
|
case "sum": {
|
|
3938
|
-
const parts = a.shape.members.map(
|
|
4907
|
+
const parts = a.shape.members.map((m) => wrapUnionMember(m, typeVars)).filter((p) => p !== "never");
|
|
3939
4908
|
const uniq = [...new Set(parts)];
|
|
3940
4909
|
if (uniq.length === 0) return "never";
|
|
3941
4910
|
if (uniq.length === 1) return uniq[0];
|
|
@@ -4067,13 +5036,18 @@ function computeMainSignature(fn) {
|
|
|
4067
5036
|
}
|
|
4068
5037
|
const typeStr = paramTypeFromAbs(members);
|
|
4069
5038
|
let name = getParamName(fn, i);
|
|
5039
|
+
const isRest = name.startsWith("...");
|
|
5040
|
+
const bare = isRest ? name.slice(3) : name;
|
|
5041
|
+
if (!isTsIdent(bare)) {
|
|
5042
|
+
name = isRest ? `...arg${i}` : `arg${i}`;
|
|
5043
|
+
}
|
|
4070
5044
|
if (usedNames.has(name)) {
|
|
4071
5045
|
let n = 2;
|
|
4072
5046
|
while (usedNames.has(`${name}${n}`)) n++;
|
|
4073
5047
|
name = `${name}${n}`;
|
|
4074
5048
|
}
|
|
4075
5049
|
usedNames.add(name);
|
|
4076
|
-
const optional = i >= minArity && !
|
|
5050
|
+
const optional = i >= minArity && !isRest;
|
|
4077
5051
|
params.push(optional ? `${name}?: ${typeStr}` : `${name}: ${typeStr}`);
|
|
4078
5052
|
paramNames.push(name);
|
|
4079
5053
|
paramTypes.push(typeStr);
|
|
@@ -4088,7 +5062,7 @@ function generateJSDoc(fn, sig) {
|
|
|
4088
5062
|
for (const c of fn.cases) {
|
|
4089
5063
|
const preciseDiffers = c.argAbs.length !== sig.paramTypes.length || c.argAbs.some((a, i) => absToTSType(a) !== sig.paramTypes[i]) || absToTSType(c.abs) !== sig.returnType;
|
|
4090
5064
|
if (!preciseDiffers) continue;
|
|
4091
|
-
const argsStr = c.argAbs.map(absToTSType).join(", ");
|
|
5065
|
+
const argsStr = c.argAbs.map((a) => absToTSType(a)).join(", ");
|
|
4092
5066
|
lines.push(` * Case: ${c.name} (${argsStr}) => ${absToTSType(c.abs)}`);
|
|
4093
5067
|
}
|
|
4094
5068
|
for (let i = 0; i < sig.paramTypes.length; i++) {
|
|
@@ -4098,8 +5072,103 @@ function generateJSDoc(fn, sig) {
|
|
|
4098
5072
|
lines.push(" */");
|
|
4099
5073
|
return lines.join("\n");
|
|
4100
5074
|
}
|
|
5075
|
+
function computeHofSignature(fn) {
|
|
5076
|
+
const hof = fn.hof;
|
|
5077
|
+
if (!hof) return void 0;
|
|
5078
|
+
const byParam = /* @__PURE__ */ new Map();
|
|
5079
|
+
for (const s of hof.entryShapes ?? []) byParam.set(s.param, s.abs);
|
|
5080
|
+
for (const r of hof.fnRels ?? []) byParam.set(r.param, r.abs);
|
|
5081
|
+
if (byParam.size === 0 && !hof.symbolic) return void 0;
|
|
5082
|
+
if (fn.cases.length > 0) {
|
|
5083
|
+
for (const [param] of byParam) {
|
|
5084
|
+
const idx = fn.paramNames.indexOf(param);
|
|
5085
|
+
if (idx < 0) continue;
|
|
5086
|
+
const caseType = paramTypeFromAbs(
|
|
5087
|
+
fn.cases.map((c) => c.argAbs[idx]).filter((a) => !!a)
|
|
5088
|
+
);
|
|
5089
|
+
if (caseType !== "unknown" && caseType !== "unknown[]") {
|
|
5090
|
+
return void 0;
|
|
5091
|
+
}
|
|
5092
|
+
}
|
|
5093
|
+
}
|
|
5094
|
+
const arity = Math.max(
|
|
5095
|
+
fn.paramNames.length,
|
|
5096
|
+
...[...byParam.keys()].map((p) => fn.paramNames.indexOf(p) + 1),
|
|
5097
|
+
0
|
|
5098
|
+
);
|
|
5099
|
+
if (arity === 0 && !hof.symbolic) return void 0;
|
|
5100
|
+
const free = /* @__PURE__ */ new Set();
|
|
5101
|
+
for (const a of byParam.values()) {
|
|
5102
|
+
for (const id of collectAbsFreeVars(a)) free.add(id);
|
|
5103
|
+
}
|
|
5104
|
+
const retAbs = hof.symbolic ?? fn.combinedAbs;
|
|
5105
|
+
if (retAbs) {
|
|
5106
|
+
for (const id of collectAbsFreeVars(retAbs)) free.add(id);
|
|
5107
|
+
}
|
|
5108
|
+
if (free.size === 0) return void 0;
|
|
5109
|
+
const typeVars = /* @__PURE__ */ new Map();
|
|
5110
|
+
const used = /* @__PURE__ */ new Set();
|
|
5111
|
+
const typeParams = [];
|
|
5112
|
+
for (const id of [...free].sort()) {
|
|
5113
|
+
let n = tsTypeParamName(id);
|
|
5114
|
+
if (used.has(n) || TS_PARAM_RESERVED.has(n)) {
|
|
5115
|
+
let i = 2;
|
|
5116
|
+
while (used.has(`${n}${i}`)) i++;
|
|
5117
|
+
n = `${n}${i}`;
|
|
5118
|
+
}
|
|
5119
|
+
used.add(n);
|
|
5120
|
+
typeVars.set(id, n);
|
|
5121
|
+
typeParams.push(n);
|
|
5122
|
+
}
|
|
5123
|
+
const params = [];
|
|
5124
|
+
const paramNames = [];
|
|
5125
|
+
const paramTypes = [];
|
|
5126
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
5127
|
+
for (let i = 0; i < arity; i++) {
|
|
5128
|
+
const rawName = getParamName(fn, i);
|
|
5129
|
+
const isRest = rawName.startsWith("...");
|
|
5130
|
+
let name = sanitizeParamName(rawName, i);
|
|
5131
|
+
if (usedNames.has(name)) {
|
|
5132
|
+
let n = 2;
|
|
5133
|
+
while (usedNames.has(`${name}${n}`)) n++;
|
|
5134
|
+
name = isRest && name.startsWith("...") ? `...${name.slice(3)}${n}` : `${name}${n}`;
|
|
5135
|
+
}
|
|
5136
|
+
usedNames.add(name);
|
|
5137
|
+
const promoted = byParam.get(fn.paramNames[i] ?? rawName) ?? byParam.get(rawName);
|
|
5138
|
+
const typeStr = promoted ? absToTSType(promoted, typeVars) : isRest ? "unknown[]" : "unknown";
|
|
5139
|
+
params.push(`${name}: ${typeStr}`);
|
|
5140
|
+
paramNames.push(name);
|
|
5141
|
+
paramTypes.push(typeStr);
|
|
5142
|
+
}
|
|
5143
|
+
const returnType = retAbs ? absToTSType(retAbs, typeVars) : "unknown";
|
|
5144
|
+
return { typeParams, params, paramNames, paramTypes, returnType };
|
|
5145
|
+
}
|
|
4101
5146
|
function generateFunctionDtsLines(fn) {
|
|
4102
|
-
|
|
5147
|
+
let emitFn = fn;
|
|
5148
|
+
if (fn.noDeclaration) {
|
|
5149
|
+
const m = /^([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)$/.exec(fn.name);
|
|
5150
|
+
if (!m) return [];
|
|
5151
|
+
emitFn = { ...fn, name: `${m[1]}_${m[2]}`, noDeclaration: false };
|
|
5152
|
+
}
|
|
5153
|
+
fn = emitFn;
|
|
5154
|
+
const hofSig = computeHofSignature(fn);
|
|
5155
|
+
if (hofSig) {
|
|
5156
|
+
const lines2 = [];
|
|
5157
|
+
if (fn.cases.length > 0) {
|
|
5158
|
+
const jsdoc2 = generateJSDoc(fn, {
|
|
5159
|
+
params: hofSig.params,
|
|
5160
|
+
paramNames: hofSig.paramNames,
|
|
5161
|
+
paramTypes: hofSig.paramTypes,
|
|
5162
|
+
returnType: hofSig.returnType
|
|
5163
|
+
});
|
|
5164
|
+
if (jsdoc2) lines2.push(jsdoc2);
|
|
5165
|
+
}
|
|
5166
|
+
const tparams = hofSig.typeParams.length > 0 ? `<${hofSig.typeParams.join(", ")}>` : "";
|
|
5167
|
+
lines2.push(
|
|
5168
|
+
`export declare function ${fn.name}${tparams}(${hofSig.params.join(", ")}): ${hofSig.returnType};`
|
|
5169
|
+
);
|
|
5170
|
+
return lines2;
|
|
5171
|
+
}
|
|
4103
5172
|
if (fn.cases.length === 0) {
|
|
4104
5173
|
const retAbs = fn.combinedAbs;
|
|
4105
5174
|
if (retAbs) {
|
|
@@ -4108,7 +5177,6 @@ function generateFunctionDtsLines(fn) {
|
|
|
4108
5177
|
];
|
|
4109
5178
|
}
|
|
4110
5179
|
return [];
|
|
4111
|
-
return [];
|
|
4112
5180
|
}
|
|
4113
5181
|
const sig = computeMainSignature(fn);
|
|
4114
5182
|
const jsdoc = generateJSDoc(fn, sig);
|
|
@@ -4539,8 +5607,8 @@ function unifiedDiff(a, b, path) {
|
|
|
4539
5607
|
}
|
|
4540
5608
|
|
|
4541
5609
|
// src/interface-surface.ts
|
|
4542
|
-
import { readFileSync as
|
|
4543
|
-
import { dirname as
|
|
5610
|
+
import { readFileSync as readFileSync7, existsSync as existsSync9 } from "fs";
|
|
5611
|
+
import { dirname as dirname11, resolve as resolve7 } from "path";
|
|
4544
5612
|
import {
|
|
4545
5613
|
effectiveInterface,
|
|
4546
5614
|
formatConstraint,
|
|
@@ -4548,9 +5616,13 @@ import {
|
|
|
4548
5616
|
interfaceDiagCount,
|
|
4549
5617
|
localNamedExports,
|
|
4550
5618
|
refineDiagCount,
|
|
5619
|
+
sidecarPathOf as sidecarPathOf2,
|
|
4551
5620
|
takeInterfaceDiagsSince,
|
|
4552
5621
|
takeRefineDiagsSince
|
|
4553
5622
|
} from "@nudojs/core";
|
|
5623
|
+
function collectDepContents(filePath, source, loadModule) {
|
|
5624
|
+
return collectLoadDepContents(filePath, source, loadModule ?? defaultLoadModule);
|
|
5625
|
+
}
|
|
4554
5626
|
function formatInterfaceSurfaceLine(e) {
|
|
4555
5627
|
const params = `(${e.params.map((p) => `${p.name}: ${p.display}`).join(", ")})`;
|
|
4556
5628
|
let line = ` ${e.fn} [${e.source}] ${params}`;
|
|
@@ -4565,19 +5637,92 @@ function absToImplicitDisplay(a) {
|
|
|
4565
5637
|
return "unknown";
|
|
4566
5638
|
}
|
|
4567
5639
|
}
|
|
5640
|
+
function constraintToJson(c) {
|
|
5641
|
+
return JSON.parse(JSON.stringify(c));
|
|
5642
|
+
}
|
|
5643
|
+
function cachedToEffective(e) {
|
|
5644
|
+
return {
|
|
5645
|
+
fnName: e.fnName,
|
|
5646
|
+
params: e.params.map((p) => ({
|
|
5647
|
+
param: p.param,
|
|
5648
|
+
constraint: p.constraint
|
|
5649
|
+
})),
|
|
5650
|
+
...e.returns ? { returns: { constraint: e.returns.constraint } } : {},
|
|
5651
|
+
source: e.source,
|
|
5652
|
+
...e.conflict ? { conflict: e.conflict } : {}
|
|
5653
|
+
};
|
|
5654
|
+
}
|
|
4568
5655
|
async function interfaceSurface(filePath, opts = {}) {
|
|
4569
|
-
const abs =
|
|
4570
|
-
const source =
|
|
5656
|
+
const abs = resolve7(filePath);
|
|
5657
|
+
const source = opts.source ?? readFileSync7(abs, "utf-8");
|
|
5658
|
+
const fromBuffer = opts.source !== void 0;
|
|
4571
5659
|
const ifaceSince = interfaceDiagCount();
|
|
4572
5660
|
const refineSince = refineDiagCount();
|
|
4573
|
-
const
|
|
5661
|
+
const proj = findProjectConfig(dirname11(abs));
|
|
5662
|
+
const autoBind = opts.autoBind ?? interfaceConfig(proj?.config).autoBind;
|
|
4574
5663
|
const loadModule = opts.loadModule ?? defaultLoadModule;
|
|
4575
5664
|
const exported = localNamedExports(source);
|
|
4576
5665
|
const kindOf = (fnName) => exported.has(fnName) ? "export" : "local";
|
|
4577
|
-
const analysis = await analyzeFileAsync(abs, source, void 0, opts.records);
|
|
5666
|
+
const analysis = await analyzeFileAsync(abs, source, void 0, opts.records, loadModule);
|
|
5667
|
+
let disk;
|
|
5668
|
+
let ifaceKey;
|
|
5669
|
+
let cachedTable;
|
|
5670
|
+
if (!opts.loadModule && !opts.records && !fromBuffer) {
|
|
5671
|
+
const cacheRoot = diskCacheRoot(proj?.config, proj?.projectDir);
|
|
5672
|
+
disk = new DiskCache({ root: cacheRoot, namespace: "iface" });
|
|
5673
|
+
if (disk.enabled) {
|
|
5674
|
+
let sidecarSource;
|
|
5675
|
+
try {
|
|
5676
|
+
const sc = sidecarPathOf2(abs);
|
|
5677
|
+
if (autoBind !== false) {
|
|
5678
|
+
const openSc = loadModule(`./${sc.slice(sc.lastIndexOf("/") + 1)}`, abs);
|
|
5679
|
+
if (openSc !== void 0) sidecarSource = openSc;
|
|
5680
|
+
else if (existsSync9(sc)) sidecarSource = readFileSync7(sc, "utf-8");
|
|
5681
|
+
}
|
|
5682
|
+
} catch {
|
|
5683
|
+
sidecarSource = void 0;
|
|
5684
|
+
}
|
|
5685
|
+
const dep = collectDepContents(abs, source, loadModule);
|
|
5686
|
+
const hasBareMiss = (dep.depContents ?? []).some((d) => d.content == null);
|
|
5687
|
+
if (dep.truncated || hasBareMiss) {
|
|
5688
|
+
ifaceKey = void 0;
|
|
5689
|
+
cachedTable = void 0;
|
|
5690
|
+
} else {
|
|
5691
|
+
ifaceKey = ifaceCacheKey(abs, source, {
|
|
5692
|
+
autoBind: autoBind !== false,
|
|
5693
|
+
projectDir: proj?.projectDir,
|
|
5694
|
+
sidecarSource,
|
|
5695
|
+
depContents: dep.depContents,
|
|
5696
|
+
projectEnvNames: proj?.config.env ?? []
|
|
5697
|
+
});
|
|
5698
|
+
cachedTable = disk.get(ifaceKey);
|
|
5699
|
+
}
|
|
5700
|
+
}
|
|
5701
|
+
}
|
|
4578
5702
|
const entries = [];
|
|
5703
|
+
const freshTable = { fns: {} };
|
|
5704
|
+
const useCache = cachedTable !== void 0;
|
|
4579
5705
|
for (const fn of analysis.functions) {
|
|
4580
|
-
|
|
5706
|
+
let eff;
|
|
5707
|
+
if (useCache) {
|
|
5708
|
+
const hit = cachedTable.fns[fn.name];
|
|
5709
|
+
eff = hit === null ? void 0 : hit ? cachedToEffective(hit) : void 0;
|
|
5710
|
+
} else {
|
|
5711
|
+
eff = effectiveInterface(source, fn.name, { loadModule, fromFile: abs, autoBind });
|
|
5712
|
+
if (freshTable) {
|
|
5713
|
+
freshTable.fns[fn.name] = eff ? {
|
|
5714
|
+
fnName: eff.fnName,
|
|
5715
|
+
params: eff.params.map((p) => ({
|
|
5716
|
+
param: p.param,
|
|
5717
|
+
constraint: constraintToJson(p.constraint)
|
|
5718
|
+
})),
|
|
5719
|
+
...eff.returns ? { returns: { constraint: constraintToJson(eff.returns.constraint) } } : {},
|
|
5720
|
+
// 磁盘表保留真实分档;implicit 也可序列化(展示层用)
|
|
5721
|
+
source: eff.source,
|
|
5722
|
+
...eff.conflict ? { conflict: eff.conflict } : {}
|
|
5723
|
+
} : null;
|
|
5724
|
+
}
|
|
5725
|
+
}
|
|
4581
5726
|
if (eff) {
|
|
4582
5727
|
entries.push({
|
|
4583
5728
|
fn: fn.name,
|
|
@@ -4608,14 +5753,20 @@ async function interfaceSurface(filePath, opts = {}) {
|
|
|
4608
5753
|
}
|
|
4609
5754
|
entries.push({ fn: fn.name, kind: kindOf(fn.name), source: "implicit", params, returns: ret });
|
|
4610
5755
|
}
|
|
5756
|
+
if (disk?.enabled && ifaceKey && !useCache && analysis.functions.length > 0) {
|
|
5757
|
+
try {
|
|
5758
|
+
disk.set(ifaceKey, freshTable);
|
|
5759
|
+
} catch {
|
|
5760
|
+
}
|
|
5761
|
+
}
|
|
4611
5762
|
takeInterfaceDiagsSince(ifaceSince);
|
|
4612
5763
|
takeRefineDiagsSince(refineSince);
|
|
4613
5764
|
return entries;
|
|
4614
5765
|
}
|
|
4615
5766
|
|
|
4616
5767
|
// src/interface-emitter.ts
|
|
4617
|
-
import { existsSync as
|
|
4618
|
-
import { basename as basename2, dirname as
|
|
5768
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync2, renameSync, unlinkSync } from "fs";
|
|
5769
|
+
import { basename as basename2, dirname as dirname12, relative as relative2, resolve as resolve8 } from "path";
|
|
4619
5770
|
import {
|
|
4620
5771
|
execNudoModule,
|
|
4621
5772
|
formatConstraint as formatConstraint2,
|
|
@@ -4626,20 +5777,20 @@ import {
|
|
|
4626
5777
|
localNamedExports as localNamedExports2,
|
|
4627
5778
|
parseSource as parseSource2,
|
|
4628
5779
|
refineDiagCount as refineDiagCount2,
|
|
4629
|
-
sidecarPathOf,
|
|
5780
|
+
sidecarPathOf as sidecarPathOf3,
|
|
4630
5781
|
takeInterfaceDiagsSince as takeInterfaceDiagsSince2,
|
|
4631
5782
|
takeRefineDiagsSince as takeRefineDiagsSince2
|
|
4632
5783
|
} from "@nudojs/core";
|
|
4633
5784
|
import { randomBytes } from "crypto";
|
|
4634
5785
|
var GENERATED_HEADER = "// @generated by nudo \u2014 do not edit; regenerate with `nudo interface --emit`";
|
|
4635
5786
|
async function emitInterface(filePath, opts) {
|
|
4636
|
-
const abs =
|
|
4637
|
-
if (isNodeModulesPath(abs) || isNodeModulesPath(
|
|
5787
|
+
const abs = resolve8(filePath);
|
|
5788
|
+
if (isNodeModulesPath(abs) || isNodeModulesPath(sidecarPathOf3(abs))) {
|
|
4638
5789
|
throw new Error(
|
|
4639
5790
|
`emit target '${abs}' is inside node_modules; contract sidecars are never written there`
|
|
4640
5791
|
);
|
|
4641
5792
|
}
|
|
4642
|
-
const proj = findProjectConfig(
|
|
5793
|
+
const proj = findProjectConfig(dirname12(abs));
|
|
4643
5794
|
const allow = interfaceConfig(proj?.config).emit;
|
|
4644
5795
|
if (!matchesEmitAllowlist(abs, proj?.projectDir, allow)) {
|
|
4645
5796
|
return {
|
|
@@ -4650,28 +5801,34 @@ async function emitInterface(filePath, opts) {
|
|
|
4650
5801
|
{
|
|
4651
5802
|
code: "nudo:interface-emit-denied",
|
|
4652
5803
|
severity: "warning",
|
|
4653
|
-
message: `emit target '${
|
|
5804
|
+
message: `emit target '${relative2(process.cwd(), abs) || abs}' is outside package.json#nudo.interface.emit allowlist`
|
|
4654
5805
|
}
|
|
4655
5806
|
],
|
|
4656
|
-
sidecarPath:
|
|
5807
|
+
sidecarPath: sidecarPathOf3(abs)
|
|
4657
5808
|
};
|
|
4658
5809
|
}
|
|
4659
|
-
const source =
|
|
5810
|
+
const source = opts.source ?? readFileSync8(abs, "utf-8");
|
|
4660
5811
|
const ifaceSince = interfaceDiagCount2();
|
|
4661
5812
|
const refineSince = refineDiagCount2();
|
|
4662
|
-
const sidecarPath =
|
|
4663
|
-
const sidecarSrc =
|
|
4664
|
-
const srcRel =
|
|
5813
|
+
const sidecarPath = sidecarPathOf3(abs);
|
|
5814
|
+
const sidecarSrc = existsSync10(sidecarPath) ? readFileSync8(sidecarPath, "utf-8") : "";
|
|
5815
|
+
const srcRel = relative2(dirname12(sidecarPath), abs) || basename2(abs);
|
|
4665
5816
|
const sections = collectGeneratedSections(sidecarSrc);
|
|
4666
5817
|
const generatedNames = new Set(sections.flatMap((s) => s.names));
|
|
4667
5818
|
const declared = topLevelDeclaredNames(sidecarSrc);
|
|
4668
5819
|
if (declared === void 0) {
|
|
4669
5820
|
throw new Error(
|
|
4670
|
-
`sidecar '${
|
|
5821
|
+
`sidecar '${relative2(process.cwd(), sidecarPath) || sidecarPath}' is not parseable; fix it before emitting`
|
|
4671
5822
|
);
|
|
4672
5823
|
}
|
|
4673
5824
|
for (const n of generatedNames) declared.delete(n);
|
|
4674
|
-
const analysis = await analyzeFileAsync(
|
|
5825
|
+
const analysis = await analyzeFileAsync(
|
|
5826
|
+
abs,
|
|
5827
|
+
source,
|
|
5828
|
+
void 0,
|
|
5829
|
+
opts.records,
|
|
5830
|
+
opts.loadModule
|
|
5831
|
+
);
|
|
4675
5832
|
const exported = localNamedExports2(source);
|
|
4676
5833
|
const fnByName = /* @__PURE__ */ new Map();
|
|
4677
5834
|
for (const f of analysis.functions) {
|
|
@@ -4774,15 +5931,15 @@ async function emitInterface(filePath, opts) {
|
|
|
4774
5931
|
finalContent = joinSections(base, [...preserved, ...accepted.map((a) => a.text)]);
|
|
4775
5932
|
}
|
|
4776
5933
|
const changed = finalContent !== sidecarSrc;
|
|
4777
|
-
const diff = changed ? unifiedDiff(sidecarSrc, finalContent,
|
|
5934
|
+
const diff = changed ? unifiedDiff(sidecarSrc, finalContent, relative2(process.cwd(), sidecarPath) || sidecarPath) : void 0;
|
|
4778
5935
|
if (changed && !opts.dryRun) {
|
|
4779
5936
|
const tmp = `${sidecarPath}.tmp-${process.pid}-${randomBytes(4).toString("hex")}`;
|
|
4780
5937
|
try {
|
|
4781
|
-
|
|
5938
|
+
writeFileSync2(tmp, finalContent, "utf-8");
|
|
4782
5939
|
renameSync(tmp, sidecarPath);
|
|
4783
5940
|
} catch (e) {
|
|
4784
5941
|
try {
|
|
4785
|
-
if (
|
|
5942
|
+
if (existsSync10(tmp)) unlinkSync(tmp);
|
|
4786
5943
|
} catch {
|
|
4787
5944
|
}
|
|
4788
5945
|
throw e;
|
|
@@ -4972,29 +6129,638 @@ function joinSections(base, sectionTexts) {
|
|
|
4972
6129
|
return lead + normalized.join("\n");
|
|
4973
6130
|
}
|
|
4974
6131
|
|
|
4975
|
-
// src/interface-
|
|
4976
|
-
import { existsSync as
|
|
4977
|
-
import { basename as basename3, dirname as
|
|
6132
|
+
// src/interface-draft.ts
|
|
6133
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync3, realpathSync as realpathSync2 } from "fs";
|
|
6134
|
+
import { basename as basename3, dirname as dirname13, isAbsolute as isAbsolute2, join as join7, relative as relative3 } from "path";
|
|
6135
|
+
import {
|
|
6136
|
+
effectiveInterface as effectiveInterface2,
|
|
6137
|
+
formatConstraint as formatConstraint3,
|
|
6138
|
+
formatShape as formatShape6,
|
|
6139
|
+
generalizeFromAst as generalizeFromAst4,
|
|
6140
|
+
isIntFlag,
|
|
6141
|
+
joinThenProject as joinThenProject2,
|
|
6142
|
+
localNamedExports as localNamedExports3,
|
|
6143
|
+
sidecarPathOf as sidecarPathOf4
|
|
6144
|
+
} from "@nudojs/core";
|
|
4978
6145
|
import { parse as parse9 } from "@nudojs/parser";
|
|
6146
|
+
function collectParamBodyAccesses(source) {
|
|
6147
|
+
const out = /* @__PURE__ */ new Map();
|
|
6148
|
+
let ast;
|
|
6149
|
+
try {
|
|
6150
|
+
ast = parse9(source);
|
|
6151
|
+
} catch {
|
|
6152
|
+
return out;
|
|
6153
|
+
}
|
|
6154
|
+
const keyOf = (node) => {
|
|
6155
|
+
if (node.type === "Identifier") return node.name;
|
|
6156
|
+
if (node.type === "StringLiteral") return node.value;
|
|
6157
|
+
return void 0;
|
|
6158
|
+
};
|
|
6159
|
+
const visitFn = (fnName, fnNode, paramNames) => {
|
|
6160
|
+
if (paramNames.size === 0) return;
|
|
6161
|
+
const byParam = /* @__PURE__ */ new Map();
|
|
6162
|
+
const walk = (node, shadowed) => {
|
|
6163
|
+
if (!node || typeof node !== "object") return;
|
|
6164
|
+
const n = node;
|
|
6165
|
+
if ((n.type === "VariableDeclarator" || n.type === "FunctionDeclaration") && n.id?.type === "Identifier") {
|
|
6166
|
+
const id = n.id.name;
|
|
6167
|
+
if (paramNames.has(id)) {
|
|
6168
|
+
shadowed = new Set(shadowed).add(id);
|
|
6169
|
+
}
|
|
6170
|
+
}
|
|
6171
|
+
if (n.type === "MemberExpression" || n.type === "OptionalMemberExpression") {
|
|
6172
|
+
const obj = n.object;
|
|
6173
|
+
const prop = n.property;
|
|
6174
|
+
const computed = n.computed === true;
|
|
6175
|
+
if (obj?.type === "Identifier" && paramNames.has(obj.name) && !shadowed.has(obj.name) && prop && !computed) {
|
|
6176
|
+
const key = keyOf(prop);
|
|
6177
|
+
const pname = obj.name;
|
|
6178
|
+
if (key !== void 0) {
|
|
6179
|
+
if (!byParam.has(pname)) byParam.set(pname, /* @__PURE__ */ new Set());
|
|
6180
|
+
byParam.get(pname).add(key);
|
|
6181
|
+
}
|
|
6182
|
+
}
|
|
6183
|
+
}
|
|
6184
|
+
for (const k of Object.keys(n)) {
|
|
6185
|
+
if (k === "loc" || k === "start" || k === "end") continue;
|
|
6186
|
+
const child = n[k];
|
|
6187
|
+
if (Array.isArray(child)) {
|
|
6188
|
+
for (const item of child) walk(item, shadowed);
|
|
6189
|
+
} else if (child && typeof child === "object") {
|
|
6190
|
+
walk(child, shadowed);
|
|
6191
|
+
}
|
|
6192
|
+
}
|
|
6193
|
+
};
|
|
6194
|
+
walk(fnNode, /* @__PURE__ */ new Set());
|
|
6195
|
+
if (byParam.size > 0) out.set(fnName, byParam);
|
|
6196
|
+
};
|
|
6197
|
+
const paramSet = (fnNode) => {
|
|
6198
|
+
const names = /* @__PURE__ */ new Set();
|
|
6199
|
+
const params = fnNode.params ?? [];
|
|
6200
|
+
for (const p of params) {
|
|
6201
|
+
if (!p) continue;
|
|
6202
|
+
if (p.type === "Identifier") names.add(p.name);
|
|
6203
|
+
else if (p.type === "AssignmentPattern" && p.left?.type === "Identifier") {
|
|
6204
|
+
names.add(p.left.name);
|
|
6205
|
+
} else if (p.type === "RestElement" && p.argument?.type === "Identifier") {
|
|
6206
|
+
names.add(p.argument.name);
|
|
6207
|
+
} else if (p.type === "ObjectPattern") {
|
|
6208
|
+
for (const prop of p.properties ?? []) {
|
|
6209
|
+
if (prop.type === "ObjectProperty") {
|
|
6210
|
+
const v = prop.value;
|
|
6211
|
+
if (v.type === "Identifier") names.add(v.name);
|
|
6212
|
+
else if (v.type === "AssignmentPattern" && v.left?.type === "Identifier") {
|
|
6213
|
+
names.add(v.left.name);
|
|
6214
|
+
}
|
|
6215
|
+
}
|
|
6216
|
+
}
|
|
6217
|
+
}
|
|
6218
|
+
}
|
|
6219
|
+
return names;
|
|
6220
|
+
};
|
|
6221
|
+
const visitClassMethods = (className, classNode) => {
|
|
6222
|
+
const body = classNode.body?.body ?? [];
|
|
6223
|
+
for (const m of body) {
|
|
6224
|
+
const mem = m;
|
|
6225
|
+
const isMethod = mem.type === "MethodDefinition" || mem.type === "ClassMethod" || mem.type === "TSDeclareMethod";
|
|
6226
|
+
if (!isMethod || mem.static) continue;
|
|
6227
|
+
if (mem.kind && mem.kind !== "method") continue;
|
|
6228
|
+
const keyName = mem.key?.type === "Identifier" ? mem.key.name : void 0;
|
|
6229
|
+
if (!keyName) continue;
|
|
6230
|
+
const methodNode = mem.type === "MethodDefinition" ? mem.value : mem;
|
|
6231
|
+
if (!methodNode) continue;
|
|
6232
|
+
visitFn(`${className}.${keyName}`, methodNode, paramSet(methodNode));
|
|
6233
|
+
}
|
|
6234
|
+
};
|
|
6235
|
+
const considerDecl = (decl, exported) => {
|
|
6236
|
+
if (!decl) return;
|
|
6237
|
+
if (decl.type === "FunctionDeclaration" && decl.id) {
|
|
6238
|
+
const id = decl.id;
|
|
6239
|
+
visitFn(id.name, decl, paramSet(decl));
|
|
6240
|
+
return;
|
|
6241
|
+
}
|
|
6242
|
+
if (decl.type === "ClassDeclaration" && decl.id?.name) {
|
|
6243
|
+
visitClassMethods(decl.id.name, decl);
|
|
6244
|
+
return;
|
|
6245
|
+
}
|
|
6246
|
+
if (decl.type === "VariableDeclaration") {
|
|
6247
|
+
for (const d of decl.declarations ?? []) {
|
|
6248
|
+
const id = d.id;
|
|
6249
|
+
const init = d.init;
|
|
6250
|
+
if (exported && id?.type === "Identifier" && init && (init.type === "ArrowFunctionExpression" || init.type === "FunctionExpression")) {
|
|
6251
|
+
visitFn(id.name, init, paramSet(init));
|
|
6252
|
+
}
|
|
6253
|
+
}
|
|
6254
|
+
}
|
|
6255
|
+
};
|
|
6256
|
+
const program = ast.program;
|
|
6257
|
+
const bodyStmts = program?.body ?? [];
|
|
6258
|
+
for (const stmt of bodyStmts) {
|
|
6259
|
+
if (stmt.type === "ExportNamedDeclaration") {
|
|
6260
|
+
considerDecl(stmt.declaration, true);
|
|
6261
|
+
for (const spec of stmt.specifiers ?? []) {
|
|
6262
|
+
const local = spec.local;
|
|
6263
|
+
if (local?.type !== "Identifier") continue;
|
|
6264
|
+
for (const s2 of bodyStmts) {
|
|
6265
|
+
if (s2.type === "ClassDeclaration" && s2.id?.name === local.name) {
|
|
6266
|
+
visitClassMethods(local.name, s2);
|
|
6267
|
+
}
|
|
6268
|
+
}
|
|
6269
|
+
}
|
|
6270
|
+
} else if (stmt.type === "ExportDefaultDeclaration") {
|
|
6271
|
+
considerDecl(stmt.declaration, true);
|
|
6272
|
+
} else if (stmt.type === "ClassDeclaration") {
|
|
6273
|
+
const id = stmt.id;
|
|
6274
|
+
if (id?.name) visitClassMethods(id.name, stmt);
|
|
6275
|
+
} else if (stmt.type === "FunctionDeclaration") {
|
|
6276
|
+
}
|
|
6277
|
+
}
|
|
6278
|
+
return out;
|
|
6279
|
+
}
|
|
6280
|
+
function caseEvidence(fn) {
|
|
6281
|
+
const callsite = fn.cases.filter((c) => c.source === "callsite");
|
|
6282
|
+
const directive = fn.cases.filter((c) => c.source === "directive");
|
|
6283
|
+
return {
|
|
6284
|
+
paramCases: callsite.length > 0 ? callsite : directive,
|
|
6285
|
+
returnCases: callsite.length > 0 ? callsite : directive.length > 0 ? directive : fn.cases,
|
|
6286
|
+
paramEvidence: callsite.length > 0 ? "callsite" : directive.length > 0 ? "directive" : "none",
|
|
6287
|
+
rawReturnEvidence: callsite.length > 0 ? "callsite" : directive.length > 0 ? "directive" : "none"
|
|
6288
|
+
};
|
|
6289
|
+
}
|
|
6290
|
+
function widenDraftConstraint(c) {
|
|
6291
|
+
const stripEqLits = (preds2) => preds2.filter((p) => !(p.op === "eq" && p.b?.op === "lit"));
|
|
6292
|
+
if (c.members && c.members.length > 0) {
|
|
6293
|
+
const widenedMembers = c.members.map(widenDraftConstraint).filter((m) => m !== void 0);
|
|
6294
|
+
if (widenedMembers.length === 0) {
|
|
6295
|
+
const prim = litPrimOf(c.members[0]);
|
|
6296
|
+
if (!prim) return void 0;
|
|
6297
|
+
return { __nudoConstraint: true, prim, preds: [] };
|
|
6298
|
+
}
|
|
6299
|
+
const prims = new Set(widenedMembers.map((m) => m.prim).filter(Boolean));
|
|
6300
|
+
if (prims.size === 1 && widenedMembers.every((m) => !m.fields && !m.element && !m.members)) {
|
|
6301
|
+
return { __nudoConstraint: true, prim: [...prims][0], preds: [] };
|
|
6302
|
+
}
|
|
6303
|
+
return {
|
|
6304
|
+
__nudoConstraint: true,
|
|
6305
|
+
...c.prim ? { prim: c.prim } : {},
|
|
6306
|
+
preds: stripEqLits(c.preds ?? []),
|
|
6307
|
+
members: widenedMembers
|
|
6308
|
+
};
|
|
6309
|
+
}
|
|
6310
|
+
if (c.fields) {
|
|
6311
|
+
const fields = {};
|
|
6312
|
+
for (const [k, f] of Object.entries(c.fields)) {
|
|
6313
|
+
const w = widenDraftConstraint(f.constraint);
|
|
6314
|
+
if (!w) continue;
|
|
6315
|
+
fields[k] = { constraint: w, ...f.optional ? { optional: true } : {} };
|
|
6316
|
+
}
|
|
6317
|
+
return {
|
|
6318
|
+
__nudoConstraint: true,
|
|
6319
|
+
fields,
|
|
6320
|
+
preds: stripEqLits(c.preds ?? [])
|
|
6321
|
+
};
|
|
6322
|
+
}
|
|
6323
|
+
if (c.element) {
|
|
6324
|
+
const el = widenDraftConstraint(c.element);
|
|
6325
|
+
return {
|
|
6326
|
+
__nudoConstraint: true,
|
|
6327
|
+
preds: stripEqLits(c.preds ?? []),
|
|
6328
|
+
...el ? { element: el } : {}
|
|
6329
|
+
};
|
|
6330
|
+
}
|
|
6331
|
+
if (c.fn) return void 0;
|
|
6332
|
+
const preds = stripEqLits(c.preds ?? []);
|
|
6333
|
+
const hasBounds = preds.length > 0;
|
|
6334
|
+
if (!c.prim && !hasBounds && !isIntFlag(c)) return void 0;
|
|
6335
|
+
return {
|
|
6336
|
+
__nudoConstraint: true,
|
|
6337
|
+
...c.prim ? { prim: c.prim } : {},
|
|
6338
|
+
preds,
|
|
6339
|
+
// builder 上 .int 是链式方法,truthy 恒真;必须经 isIntFlag
|
|
6340
|
+
...isIntFlag(c) ? { int: true } : {}
|
|
6341
|
+
};
|
|
6342
|
+
}
|
|
6343
|
+
function litPrimOf(c) {
|
|
6344
|
+
if (!c) return void 0;
|
|
6345
|
+
if (c.prim === "number" || c.prim === "string" || c.prim === "boolean") return c.prim;
|
|
6346
|
+
const eq = c.preds?.find(
|
|
6347
|
+
(p) => p.op === "eq" && p.b?.op === "lit"
|
|
6348
|
+
);
|
|
6349
|
+
const v = eq && eq.b?.op === "lit" ? eq.b.value : void 0;
|
|
6350
|
+
if (typeof v === "number") return "number";
|
|
6351
|
+
if (typeof v === "string") return "string";
|
|
6352
|
+
if (typeof v === "boolean") return "boolean";
|
|
6353
|
+
return c.members?.[0] ? litPrimOf(c.members[0]) : void 0;
|
|
6354
|
+
}
|
|
6355
|
+
function projectDraftParams(fn, paramCases, bodyByParam, formals) {
|
|
6356
|
+
const bodyFor = (name, index) => {
|
|
6357
|
+
if (!bodyByParam) return void 0;
|
|
6358
|
+
const hit = bodyByParam.get(name);
|
|
6359
|
+
if (hit) return hit;
|
|
6360
|
+
const formal = formals?.[index];
|
|
6361
|
+
if (formal && formal.kind === "pattern") {
|
|
6362
|
+
for (const b of formal.bound) {
|
|
6363
|
+
const boundHit = bodyByParam.get(b);
|
|
6364
|
+
if (boundHit) return boundHit;
|
|
6365
|
+
}
|
|
6366
|
+
}
|
|
6367
|
+
return bodyByParam.get(`_p${index}`);
|
|
6368
|
+
};
|
|
6369
|
+
return fn.paramNames.map((name, i) => {
|
|
6370
|
+
const bodyAccesses = bodyFor(name, i) ? [...bodyFor(name, i)].sort() : void 0;
|
|
6371
|
+
const argAbs = [];
|
|
6372
|
+
for (const c of paramCases) {
|
|
6373
|
+
const a = c.argAbs[i];
|
|
6374
|
+
if (a !== void 0) argAbs.push(a);
|
|
6375
|
+
}
|
|
6376
|
+
if (argAbs.length === 0) {
|
|
6377
|
+
if (bodyAccesses && bodyAccesses.length > 0) {
|
|
6378
|
+
return {
|
|
6379
|
+
name,
|
|
6380
|
+
display: `/* body-read { ${bodyAccesses.join(", ")} } \u2014 fill types when accepting */`,
|
|
6381
|
+
projected: false,
|
|
6382
|
+
bodyAccesses
|
|
6383
|
+
};
|
|
6384
|
+
}
|
|
6385
|
+
return { name, display: "/* no evidence \u2014 tighten */", projected: false };
|
|
6386
|
+
}
|
|
6387
|
+
const raw = joinThenProject2(argAbs);
|
|
6388
|
+
if (raw === void 0) {
|
|
6389
|
+
return {
|
|
6390
|
+
name,
|
|
6391
|
+
display: `/* not projectable: ${argAbs.map((a) => formatShape6(a)).join(" | ")} */`,
|
|
6392
|
+
projected: false,
|
|
6393
|
+
...bodyAccesses ? { bodyAccesses } : {}
|
|
6394
|
+
};
|
|
6395
|
+
}
|
|
6396
|
+
const constraint = widenDraftConstraint(raw);
|
|
6397
|
+
if (constraint === void 0) {
|
|
6398
|
+
const observed2 = formatConstraint3(raw);
|
|
6399
|
+
return {
|
|
6400
|
+
name,
|
|
6401
|
+
display: `/* observed: ${observed2} \u2014 widen/confirm before accepting */`,
|
|
6402
|
+
projected: false,
|
|
6403
|
+
...bodyAccesses ? { bodyAccesses } : {}
|
|
6404
|
+
};
|
|
6405
|
+
}
|
|
6406
|
+
let display = formatConstraint3(constraint);
|
|
6407
|
+
const observed = formatConstraint3(raw);
|
|
6408
|
+
if (observed !== display) {
|
|
6409
|
+
display += ` /* observed: ${observed} */`;
|
|
6410
|
+
}
|
|
6411
|
+
if (bodyAccesses && bodyAccesses.length > 0) {
|
|
6412
|
+
display += ` /* body also reads: ${bodyAccesses.join(", ")} */`;
|
|
6413
|
+
}
|
|
6414
|
+
return {
|
|
6415
|
+
name,
|
|
6416
|
+
constraint,
|
|
6417
|
+
display,
|
|
6418
|
+
projected: true,
|
|
6419
|
+
...bodyAccesses ? { bodyAccesses } : {}
|
|
6420
|
+
};
|
|
6421
|
+
});
|
|
6422
|
+
}
|
|
6423
|
+
function projectDraftReturn(fn, returnCases, source, rawEvidence, loadModule, fromFile) {
|
|
6424
|
+
const retAbs = [];
|
|
6425
|
+
for (const c of returnCases) {
|
|
6426
|
+
if (c.throwsAbs.shape.k !== "never") continue;
|
|
6427
|
+
retAbs.push(c.abs);
|
|
6428
|
+
}
|
|
6429
|
+
if (retAbs.length > 0) {
|
|
6430
|
+
const raw = joinThenProject2(retAbs);
|
|
6431
|
+
if (raw !== void 0) {
|
|
6432
|
+
if (rawEvidence === "none" || rawEvidence === "body") {
|
|
6433
|
+
return {
|
|
6434
|
+
display: `/* observed: ${formatConstraint3(raw)} \u2014 confirm before accepting */`,
|
|
6435
|
+
projected: false,
|
|
6436
|
+
evidence: rawEvidence === "none" ? "body" : rawEvidence
|
|
6437
|
+
};
|
|
6438
|
+
}
|
|
6439
|
+
const constraint = widenDraftConstraint(raw);
|
|
6440
|
+
if (constraint === void 0) {
|
|
6441
|
+
return {
|
|
6442
|
+
display: `/* observed: ${formatConstraint3(raw)} \u2014 widen/confirm */`,
|
|
6443
|
+
projected: false,
|
|
6444
|
+
evidence: rawEvidence
|
|
6445
|
+
};
|
|
6446
|
+
}
|
|
6447
|
+
return {
|
|
6448
|
+
constraint,
|
|
6449
|
+
display: formatConstraint3(constraint),
|
|
6450
|
+
projected: true,
|
|
6451
|
+
evidence: rawEvidence
|
|
6452
|
+
};
|
|
6453
|
+
}
|
|
6454
|
+
const shapeText = fn.combinedAbs ? formatShape6(fn.combinedAbs) : formatShape6(retAbs[0]);
|
|
6455
|
+
return {
|
|
6456
|
+
display: `/* not projectable: ${shapeText} */`,
|
|
6457
|
+
projected: false,
|
|
6458
|
+
evidence: rawEvidence
|
|
6459
|
+
};
|
|
6460
|
+
}
|
|
6461
|
+
try {
|
|
6462
|
+
const g = generalizeFromAst4(fn.name, source, {
|
|
6463
|
+
refine: {
|
|
6464
|
+
...loadModule ? { loadModule } : {},
|
|
6465
|
+
...fromFile ? { fromFile } : {}
|
|
6466
|
+
}
|
|
6467
|
+
});
|
|
6468
|
+
if (g?.symbolic) {
|
|
6469
|
+
return {
|
|
6470
|
+
display: `/* symbolic: ${formatShape6(g.symbolic)}${g.display ? ` \u2014 ${g.display}` : ""} */`,
|
|
6471
|
+
projected: false,
|
|
6472
|
+
evidence: "symbolic"
|
|
6473
|
+
};
|
|
6474
|
+
}
|
|
6475
|
+
} catch {
|
|
6476
|
+
}
|
|
6477
|
+
return { display: "/* no evidence */", projected: false, evidence: "none" };
|
|
6478
|
+
}
|
|
6479
|
+
function toDraftBuilderDsl(display) {
|
|
6480
|
+
const s = display.replace(/\s*\/\*[\s\S]*?\*\/\s*/g, "").trim();
|
|
6481
|
+
return s.replace(
|
|
6482
|
+
/([A-Za-z_$][\w$]*)\?\s*:\s*([^,}\n]+)/g,
|
|
6483
|
+
(_m, name, val) => {
|
|
6484
|
+
const v = val.trim();
|
|
6485
|
+
if (v.includes(".optional()")) return `${name}: ${v}`;
|
|
6486
|
+
return `${name}: ${v}.optional()`;
|
|
6487
|
+
}
|
|
6488
|
+
);
|
|
6489
|
+
}
|
|
6490
|
+
function draftExportName(fnName) {
|
|
6491
|
+
return fnName.includes(".") ? fnName.replace(/\./g, "_") : fnName;
|
|
6492
|
+
}
|
|
6493
|
+
function draftDsl(entry) {
|
|
6494
|
+
const parts = entry.params.filter((p) => p.projected && p.constraint !== void 0).map((p) => {
|
|
6495
|
+
const pure = toDraftBuilderDsl(formatConstraint3(p.constraint));
|
|
6496
|
+
return `${p.name}: ${pure}`;
|
|
6497
|
+
});
|
|
6498
|
+
const obj = parts.length === 0 ? "{}" : `{ ${parts.join(", ")} }`;
|
|
6499
|
+
const ret = entry.returns?.projected && entry.returns.constraint !== void 0 ? toDraftBuilderDsl(formatConstraint3(entry.returns.constraint)) : void 0;
|
|
6500
|
+
return ret === void 0 || ret === "" ? `fn(${obj})` : `fn(${obj}, ${ret})`;
|
|
6501
|
+
}
|
|
6502
|
+
function suggestedBodyDsl(fnName, params) {
|
|
6503
|
+
const withBody = params.filter((p) => p.bodyAccesses && p.bodyAccesses.length > 0 && !p.projected);
|
|
6504
|
+
if (withBody.length === 0) return void 0;
|
|
6505
|
+
const parts = withBody.map((p) => {
|
|
6506
|
+
const fields = p.bodyAccesses.map((k) => `${k}: /* TODO */`).join(", ");
|
|
6507
|
+
return `${p.name}: shape({ ${fields} })`;
|
|
6508
|
+
});
|
|
6509
|
+
return `// suggested (body-read, not a contract): ${fnName} = fn({ ${parts.join(", ")} })`;
|
|
6510
|
+
}
|
|
6511
|
+
async function draftInterface(filePath, opts = {}) {
|
|
6512
|
+
const source = opts.source ?? readFileSync9(filePath, "utf-8");
|
|
6513
|
+
const loadModule = opts.loadModule ?? defaultLoadModule;
|
|
6514
|
+
const sidecarPath = sidecarPathOf4(filePath);
|
|
6515
|
+
const wantBody = opts.bodyAccesses !== false;
|
|
6516
|
+
const analysis = await analyzeFileAsync(filePath, source, void 0, opts.records, loadModule);
|
|
6517
|
+
const exported = localNamedExports3(source);
|
|
6518
|
+
const selected = opts.fnNames && opts.fnNames.length > 0 ? new Set(opts.fnNames) : exported;
|
|
6519
|
+
const bodyMap = wantBody ? collectParamBodyAccesses(source) : /* @__PURE__ */ new Map();
|
|
6520
|
+
const entries = [];
|
|
6521
|
+
for (const fn of analysis.functions) {
|
|
6522
|
+
if (!selected.has(fn.name) && !opts.fnNames?.includes(fn.name)) continue;
|
|
6523
|
+
if (!exported.has(fn.name)) {
|
|
6524
|
+
if (opts.fnNames?.includes(fn.name)) {
|
|
6525
|
+
entries.push({
|
|
6526
|
+
fn: fn.name,
|
|
6527
|
+
params: fn.paramNames.map((n) => ({
|
|
6528
|
+
name: n,
|
|
6529
|
+
display: "/* not an export */",
|
|
6530
|
+
projected: false
|
|
6531
|
+
})),
|
|
6532
|
+
paramEvidence: "none",
|
|
6533
|
+
returnEvidence: "none",
|
|
6534
|
+
skipped: "not-an-export"
|
|
6535
|
+
});
|
|
6536
|
+
}
|
|
6537
|
+
continue;
|
|
6538
|
+
}
|
|
6539
|
+
const eff = effectiveInterface2(source, fn.name, {
|
|
6540
|
+
loadModule,
|
|
6541
|
+
fromFile: filePath,
|
|
6542
|
+
autoBind: true
|
|
6543
|
+
});
|
|
6544
|
+
if (eff?.source === "handwritten") {
|
|
6545
|
+
entries.push({
|
|
6546
|
+
fn: fn.name,
|
|
6547
|
+
params: eff.params.map((p) => ({
|
|
6548
|
+
name: p.param,
|
|
6549
|
+
constraint: p.constraint,
|
|
6550
|
+
display: formatConstraint3(p.constraint),
|
|
6551
|
+
projected: true
|
|
6552
|
+
})),
|
|
6553
|
+
...eff.returns ? {
|
|
6554
|
+
returns: {
|
|
6555
|
+
constraint: eff.returns.constraint,
|
|
6556
|
+
display: formatConstraint3(eff.returns.constraint),
|
|
6557
|
+
projected: true
|
|
6558
|
+
}
|
|
6559
|
+
} : {},
|
|
6560
|
+
paramEvidence: "none",
|
|
6561
|
+
returnEvidence: "none",
|
|
6562
|
+
skipped: "handwritten"
|
|
6563
|
+
});
|
|
6564
|
+
continue;
|
|
6565
|
+
}
|
|
6566
|
+
const { paramCases, returnCases, paramEvidence, rawReturnEvidence } = caseEvidence(fn);
|
|
6567
|
+
const bodyByParam = bodyMap.get(fn.name);
|
|
6568
|
+
const params = projectDraftParams(fn, paramCases, bodyByParam, fn.formals);
|
|
6569
|
+
const ret = projectDraftReturn(fn, returnCases, source, rawReturnEvidence, loadModule, filePath);
|
|
6570
|
+
const { evidence: returnEvidence, ...returns } = ret;
|
|
6571
|
+
let evidence = paramEvidence;
|
|
6572
|
+
if (evidence === "none") {
|
|
6573
|
+
const anyBody = params.some((p) => p.bodyAccesses && p.bodyAccesses.length > 0);
|
|
6574
|
+
if (anyBody) evidence = "body";
|
|
6575
|
+
}
|
|
6576
|
+
entries.push({
|
|
6577
|
+
fn: fn.name,
|
|
6578
|
+
params,
|
|
6579
|
+
returns,
|
|
6580
|
+
paramEvidence: evidence,
|
|
6581
|
+
returnEvidence,
|
|
6582
|
+
dsl: draftDsl({ params, returns })
|
|
6583
|
+
});
|
|
6584
|
+
}
|
|
6585
|
+
const draftSource = formatDraftModule(filePath, entries, sidecarPath);
|
|
6586
|
+
return { file: filePath, entries, draftSource, sidecarPath };
|
|
6587
|
+
}
|
|
6588
|
+
function formatDraftModule(filePath, entries, sidecarPath) {
|
|
6589
|
+
const target = sidecarPath ?? sidecarPathOf4(filePath);
|
|
6590
|
+
const draftName = sidecarDraftPath(filePath).split(/[/\\]/).pop() ?? "*.nudo.draft.js";
|
|
6591
|
+
const draftable = entries.filter((e) => e.dsl !== void 0 && e.skipped === void 0);
|
|
6592
|
+
const BUILDERS = ["fn", "number", "string", "boolean", "any", "shape", "array", "lit", "union"];
|
|
6593
|
+
const used = /* @__PURE__ */ new Set(["fn"]);
|
|
6594
|
+
for (const e of draftable) {
|
|
6595
|
+
const blob = `${e.dsl ?? ""}
|
|
6596
|
+
${suggestedBodyDsl(e.fn, e.params) ?? ""}`;
|
|
6597
|
+
for (const b of BUILDERS) {
|
|
6598
|
+
if (new RegExp(`\\b${b}\\b`).test(blob)) used.add(b);
|
|
6599
|
+
}
|
|
6600
|
+
}
|
|
6601
|
+
const importList = BUILDERS.filter((b) => used.has(b)).join(", ");
|
|
6602
|
+
const lines = [
|
|
6603
|
+
"// @nudo:draft",
|
|
6604
|
+
`// Generated by \`nudo interface --draft\` from ${filePath}`,
|
|
6605
|
+
`// This ${draftName} file is NOT loaded as a sidecar contract.`,
|
|
6606
|
+
`// Review each export, then copy it into ${target} to accept.`,
|
|
6607
|
+
"//",
|
|
6608
|
+
"// Evidence: callsite/directive = observed args; body = fields the",
|
|
6609
|
+
"// implementation reads (suggestion only \u2014 never a check obligation);",
|
|
6610
|
+
"// symbolic = generalize; omitted params = no evidence.",
|
|
6611
|
+
"// Handwritten contracts are never overwritten.",
|
|
6612
|
+
"",
|
|
6613
|
+
`import { ${importList} } from "@nudojs/core";`,
|
|
6614
|
+
""
|
|
6615
|
+
];
|
|
6616
|
+
if (draftable.length === 0) {
|
|
6617
|
+
lines.push("// (no draftable exports \u2014 handwritten / non-export / empty)");
|
|
6618
|
+
lines.push("");
|
|
6619
|
+
}
|
|
6620
|
+
for (const e of draftable) {
|
|
6621
|
+
lines.push(`// ${e.fn} \u2014 param: ${e.paramEvidence}, return: ${e.returnEvidence}`);
|
|
6622
|
+
for (const p of e.params) {
|
|
6623
|
+
if (!p.projected) {
|
|
6624
|
+
lines.push(`// ${p.name}: ${p.display}`);
|
|
6625
|
+
} else if (p.display.includes("/* observed") || p.display.includes("/* body also reads")) {
|
|
6626
|
+
lines.push(`// ${p.name}: ${p.display}`);
|
|
6627
|
+
}
|
|
6628
|
+
}
|
|
6629
|
+
const suggested = suggestedBodyDsl(e.fn, e.params);
|
|
6630
|
+
if (suggested) lines.push(suggested);
|
|
6631
|
+
if (e.returns && !e.returns.projected) {
|
|
6632
|
+
lines.push(`// returns: ${e.returns.display}`);
|
|
6633
|
+
} else if (e.returns?.display.includes("/* observed")) {
|
|
6634
|
+
lines.push(`// returns: ${e.returns.display}`);
|
|
6635
|
+
} else if (e.returnEvidence === "symbolic") {
|
|
6636
|
+
lines.push(`// returns: ${e.returns?.display ?? ""} (symbolic)`);
|
|
6637
|
+
}
|
|
6638
|
+
lines.push(`export const ${draftExportName(e.fn)} = ${e.dsl};`);
|
|
6639
|
+
if (e.fn.includes(".")) {
|
|
6640
|
+
lines.push(`// sidecar key may also be written as \`${e.fn}\` / nested { ${e.fn.split(".")[1]}: \u2026 }`);
|
|
6641
|
+
}
|
|
6642
|
+
lines.push("");
|
|
6643
|
+
}
|
|
6644
|
+
const skipped = entries.filter((e) => e.skipped !== void 0);
|
|
6645
|
+
if (skipped.length > 0) {
|
|
6646
|
+
lines.push("// Skipped:");
|
|
6647
|
+
for (const s of skipped) {
|
|
6648
|
+
lines.push(`// ${s.fn} (${s.skipped})`);
|
|
6649
|
+
}
|
|
6650
|
+
lines.push("");
|
|
6651
|
+
}
|
|
6652
|
+
return lines.join("\n");
|
|
6653
|
+
}
|
|
6654
|
+
function sidecarDraftPath(filePath) {
|
|
6655
|
+
return sidecarPathOf4(filePath).replace(/\.nudo\.([cm]?[jt]s)$/, ".nudo.draft.$1");
|
|
6656
|
+
}
|
|
6657
|
+
function safeRealpath(p) {
|
|
6658
|
+
try {
|
|
6659
|
+
return realpathSync2(p);
|
|
6660
|
+
} catch {
|
|
6661
|
+
try {
|
|
6662
|
+
return join7(realpathSync2(dirname13(p)), basename3(p));
|
|
6663
|
+
} catch {
|
|
6664
|
+
return p;
|
|
6665
|
+
}
|
|
6666
|
+
}
|
|
6667
|
+
}
|
|
6668
|
+
function isDraftableEntry(entries) {
|
|
6669
|
+
return entries.some((e) => e.dsl !== void 0 && e.skipped === void 0);
|
|
6670
|
+
}
|
|
6671
|
+
function writeInterfaceDraft(filePath, draftSource, opts = {}) {
|
|
6672
|
+
const draftPath = sidecarDraftPath(filePath);
|
|
6673
|
+
const formalPath = sidecarPathOf4(filePath);
|
|
6674
|
+
if (draftPath === formalPath) {
|
|
6675
|
+
throw new Error(
|
|
6676
|
+
`draft write refused: draft path equals formal sidecar (${formalPath}); never overwrite handwritten contracts`
|
|
6677
|
+
);
|
|
6678
|
+
}
|
|
6679
|
+
if (/[/\\]node_modules[/\\]/.test(draftPath) || /[/\\]node_modules[/\\]/.test(filePath)) {
|
|
6680
|
+
throw new Error(`draft write refused: path is inside node_modules (${draftPath})`);
|
|
6681
|
+
}
|
|
6682
|
+
if (opts.projectDir) {
|
|
6683
|
+
const rootReal = safeRealpath(opts.projectDir);
|
|
6684
|
+
const draftReal = safeRealpath(draftPath);
|
|
6685
|
+
const rel = relative3(rootReal, draftReal);
|
|
6686
|
+
if (rel.startsWith("..") || isAbsolute2(rel)) {
|
|
6687
|
+
throw new Error(`draft write refused: outside project root ${opts.projectDir}`);
|
|
6688
|
+
}
|
|
6689
|
+
}
|
|
6690
|
+
const draftable = opts.draftable !== void 0 ? opts.draftable : opts.entries !== void 0 ? isDraftableEntry(opts.entries) : /export\s+const\s+[\p{ID_Start}$_][\p{ID_Continue}$]*\s*=/u.test(draftSource);
|
|
6691
|
+
const prev = existsSync11(draftPath) ? readFileSync9(draftPath, "utf-8") : void 0;
|
|
6692
|
+
const changed = prev !== draftSource;
|
|
6693
|
+
const written = !opts.dryRun && changed && draftable;
|
|
6694
|
+
if (written) {
|
|
6695
|
+
writeFileSync3(draftPath, draftSource, "utf-8");
|
|
6696
|
+
}
|
|
6697
|
+
return {
|
|
6698
|
+
draftPath,
|
|
6699
|
+
written,
|
|
6700
|
+
changed,
|
|
6701
|
+
draftable,
|
|
6702
|
+
draftSource
|
|
6703
|
+
};
|
|
6704
|
+
}
|
|
6705
|
+
function formatDraftSummary(sourceRel, draftRel, result, write) {
|
|
6706
|
+
const lines = [sourceRel];
|
|
6707
|
+
for (const e of result.entries) {
|
|
6708
|
+
if (e.skipped === "handwritten") {
|
|
6709
|
+
lines.push(` ${e.fn} [handwritten] skipped (draft never overwrites)`);
|
|
6710
|
+
} else if (e.skipped === "not-an-export") {
|
|
6711
|
+
lines.push(` ${e.fn} [not-an-export] skipped`);
|
|
6712
|
+
} else if (e.dsl) {
|
|
6713
|
+
lines.push(` ${e.fn} [draft ${e.paramEvidence}/${e.returnEvidence}] ${e.dsl}`);
|
|
6714
|
+
}
|
|
6715
|
+
}
|
|
6716
|
+
if (write) {
|
|
6717
|
+
if (write.changed && write.draftable) {
|
|
6718
|
+
lines.push(
|
|
6719
|
+
write.written ? `Draft written \u2192 ${draftRel}` : `[dry-run] would write \u2192 ${draftRel}`
|
|
6720
|
+
);
|
|
6721
|
+
} else if (write.changed && !write.draftable) {
|
|
6722
|
+
if (result.entries.length > 0) {
|
|
6723
|
+
const skipped = result.entries.filter((e) => e.skipped !== void 0).length;
|
|
6724
|
+
lines.push(
|
|
6725
|
+
skipped > 0 ? `Draft not written (${skipped} skipped, no writeable exports); nothing written \u2192 ${draftRel}` : `Draft not written (no writeable exports); nothing written \u2192 ${draftRel}`
|
|
6726
|
+
);
|
|
6727
|
+
} else {
|
|
6728
|
+
lines.push(`Draft empty (no draftable exports); nothing written \u2192 ${draftRel}`);
|
|
6729
|
+
}
|
|
6730
|
+
} else {
|
|
6731
|
+
lines.push(`${draftRel}: draft unchanged`);
|
|
6732
|
+
}
|
|
6733
|
+
lines.push(` review, then copy accepted exports into ${sidecarPathOf4(result.file)}`);
|
|
6734
|
+
} else {
|
|
6735
|
+
lines.push("");
|
|
6736
|
+
lines.push(result.draftSource.trimEnd());
|
|
6737
|
+
}
|
|
6738
|
+
return lines;
|
|
6739
|
+
}
|
|
6740
|
+
|
|
6741
|
+
// src/interface-derivation.ts
|
|
6742
|
+
import { existsSync as existsSync12, readFileSync as readFileSync10, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
6743
|
+
import { basename as basename4, dirname as dirname14, relative as relative4, resolve as resolve9 } from "path";
|
|
6744
|
+
import { parse as parse10 } from "@nudojs/parser";
|
|
4979
6745
|
import {
|
|
4980
6746
|
analyzeFn,
|
|
4981
6747
|
beginDerivationSession,
|
|
4982
6748
|
constraintToEntryAbs,
|
|
4983
6749
|
derivationChain,
|
|
4984
|
-
effectiveInterface as
|
|
6750
|
+
effectiveInterface as effectiveInterface3,
|
|
4985
6751
|
endDerivationSession,
|
|
4986
6752
|
execNudoModule as execNudoModule2,
|
|
4987
|
-
formatConstraint as
|
|
6753
|
+
formatConstraint as formatConstraint4,
|
|
4988
6754
|
getDerivation,
|
|
4989
6755
|
interfaceDiagCount as interfaceDiagCount3,
|
|
4990
6756
|
isNodeModulesPath as isNodeModulesPath2,
|
|
4991
6757
|
isNudoConstraint as isNudoConstraint2,
|
|
4992
|
-
joinThenProject as
|
|
6758
|
+
joinThenProject as joinThenProject3,
|
|
4993
6759
|
parseSource as parseSource3,
|
|
4994
6760
|
projectDerivationDsl,
|
|
4995
6761
|
refineDiagCount as refineDiagCount3,
|
|
4996
6762
|
setAbsCallCollector as setAbsCallCollector2,
|
|
4997
|
-
sidecarPathOf as
|
|
6763
|
+
sidecarPathOf as sidecarPathOf5,
|
|
4998
6764
|
tagDerivationRoot,
|
|
4999
6765
|
takeInterfaceDiagsSince as takeInterfaceDiagsSince3,
|
|
5000
6766
|
takeRefineDiagsSince as takeRefineDiagsSince3,
|
|
@@ -5004,7 +6770,7 @@ function extractFnConstraintSources(sidecarSrc, fnName) {
|
|
|
5004
6770
|
const out = { params: {} };
|
|
5005
6771
|
let ast;
|
|
5006
6772
|
try {
|
|
5007
|
-
ast =
|
|
6773
|
+
ast = parse10(sidecarSrc);
|
|
5008
6774
|
} catch {
|
|
5009
6775
|
return out;
|
|
5010
6776
|
}
|
|
@@ -5075,7 +6841,7 @@ function extractFnConstraintSources(sidecarSrc, fnName) {
|
|
|
5075
6841
|
}
|
|
5076
6842
|
function functionParamNames(source, fnName) {
|
|
5077
6843
|
try {
|
|
5078
|
-
const ast =
|
|
6844
|
+
const ast = parse10(source);
|
|
5079
6845
|
for (const stmt of ast.program.body) {
|
|
5080
6846
|
const d = stmt.type === "ExportNamedDeclaration" ? stmt.declaration ?? void 0 : stmt;
|
|
5081
6847
|
if (!d) continue;
|
|
@@ -5104,7 +6870,7 @@ function paramNameOf(p) {
|
|
|
5104
6870
|
function topLevelFnNames(source) {
|
|
5105
6871
|
const out = [];
|
|
5106
6872
|
try {
|
|
5107
|
-
const ast =
|
|
6873
|
+
const ast = parse10(source);
|
|
5108
6874
|
for (const stmt of ast.program.body) {
|
|
5109
6875
|
const d = stmt.type === "ExportNamedDeclaration" ? stmt.declaration ?? void 0 : stmt;
|
|
5110
6876
|
if (!d) continue;
|
|
@@ -5124,16 +6890,16 @@ function topLevelFnNames(source) {
|
|
|
5124
6890
|
function importLocalMap(source, fromFile) {
|
|
5125
6891
|
const out = /* @__PURE__ */ new Map();
|
|
5126
6892
|
try {
|
|
5127
|
-
const ast =
|
|
5128
|
-
const base =
|
|
6893
|
+
const ast = parse10(source);
|
|
6894
|
+
const base = dirname14(resolve9(fromFile));
|
|
5129
6895
|
for (const stmt of ast.program.body) {
|
|
5130
6896
|
if (stmt.type !== "ImportDeclaration") continue;
|
|
5131
6897
|
const spec = stmt.source.value;
|
|
5132
6898
|
if (!spec.startsWith(".") && !spec.startsWith("/")) continue;
|
|
5133
|
-
const raw =
|
|
6899
|
+
const raw = resolve9(base, spec);
|
|
5134
6900
|
let modulePath = null;
|
|
5135
6901
|
for (const cand of [raw, `${raw}.js`, `${raw}.ts`, `${raw}.mjs`]) {
|
|
5136
|
-
if (
|
|
6902
|
+
if (existsSync12(cand)) {
|
|
5137
6903
|
modulePath = cand;
|
|
5138
6904
|
break;
|
|
5139
6905
|
}
|
|
@@ -5151,14 +6917,14 @@ function importLocalMap(source, fromFile) {
|
|
|
5151
6917
|
return out;
|
|
5152
6918
|
}
|
|
5153
6919
|
function resolveRelImport(fromSpec, fromDir, targetDir) {
|
|
5154
|
-
const abs =
|
|
5155
|
-
let rel =
|
|
6920
|
+
const abs = resolve9(fromDir, fromSpec);
|
|
6921
|
+
let rel = relative4(targetDir, abs);
|
|
5156
6922
|
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
5157
6923
|
return rel.split("\\").join("/");
|
|
5158
6924
|
}
|
|
5159
6925
|
function projectParamSlot(absList, paramName) {
|
|
5160
6926
|
if (absList.length === 0) return void 0;
|
|
5161
|
-
const constraint =
|
|
6927
|
+
const constraint = joinThenProject3(absList);
|
|
5162
6928
|
if (constraint === void 0) return void 0;
|
|
5163
6929
|
if (absList.length === 1) {
|
|
5164
6930
|
const node = getDerivation(absList[0]);
|
|
@@ -5182,7 +6948,7 @@ function projectParamSlot(absList, paramName) {
|
|
|
5182
6948
|
}
|
|
5183
6949
|
return {
|
|
5184
6950
|
constraint,
|
|
5185
|
-
dsl:
|
|
6951
|
+
dsl: formatConstraint4(constraint),
|
|
5186
6952
|
prelude: [],
|
|
5187
6953
|
imports: [],
|
|
5188
6954
|
compositional: false
|
|
@@ -5190,7 +6956,7 @@ function projectParamSlot(absList, paramName) {
|
|
|
5190
6956
|
}
|
|
5191
6957
|
function projectReturnSlot(retAbs, params, paramNames) {
|
|
5192
6958
|
if (retAbs.length === 0) return void 0;
|
|
5193
|
-
const constraint =
|
|
6959
|
+
const constraint = joinThenProject3(retAbs);
|
|
5194
6960
|
if (constraint === void 0) return void 0;
|
|
5195
6961
|
if (retAbs.length === 1) {
|
|
5196
6962
|
const node = getDerivation(retAbs[0]);
|
|
@@ -5219,7 +6985,7 @@ function projectReturnSlot(retAbs, params, paramNames) {
|
|
|
5219
6985
|
}
|
|
5220
6986
|
return {
|
|
5221
6987
|
constraint,
|
|
5222
|
-
dsl:
|
|
6988
|
+
dsl: formatConstraint4(constraint),
|
|
5223
6989
|
prelude: [],
|
|
5224
6990
|
imports: [],
|
|
5225
6991
|
compositional: false
|
|
@@ -5251,18 +7017,18 @@ function projectReturnRelativeToParams(node, params, paramNames) {
|
|
|
5251
7017
|
return void 0;
|
|
5252
7018
|
}
|
|
5253
7019
|
function deriveFromRoot(filePath, opts = {}) {
|
|
5254
|
-
const abs =
|
|
5255
|
-
const source =
|
|
7020
|
+
const abs = resolve9(filePath);
|
|
7021
|
+
const source = readFileSync10(abs, "utf-8");
|
|
5256
7022
|
const since = interfaceDiagCount3();
|
|
5257
|
-
const autoBind = opts.autoBind ?? interfaceConfig(findProjectConfig(
|
|
7023
|
+
const autoBind = opts.autoBind ?? interfaceConfig(findProjectConfig(dirname14(abs))?.config).autoBind;
|
|
5258
7024
|
const loadModule = opts.loadModule ?? defaultLoadModule;
|
|
5259
|
-
const sidecarPath =
|
|
5260
|
-
const sidecarSrc =
|
|
7025
|
+
const sidecarPath = sidecarPathOf5(abs);
|
|
7026
|
+
const sidecarSrc = existsSync12(sidecarPath) && !isNodeModulesPath2(sidecarPath) ? readFileSync10(sidecarPath, "utf-8") : "";
|
|
5261
7027
|
const fnNames = topLevelFnNames(source);
|
|
5262
7028
|
const roots = [];
|
|
5263
7029
|
const plans = [];
|
|
5264
7030
|
for (const fn of fnNames) {
|
|
5265
|
-
const eff =
|
|
7031
|
+
const eff = effectiveInterface3(source, fn, { loadModule, fromFile: abs, autoBind });
|
|
5266
7032
|
if (!eff || eff.source !== "handwritten") continue;
|
|
5267
7033
|
roots.push(fn);
|
|
5268
7034
|
const sources = sidecarSrc ? extractFnConstraintSources(sidecarSrc, fn) : { params: {} };
|
|
@@ -5298,8 +7064,8 @@ function deriveFromRoot(filePath, opts = {}) {
|
|
|
5298
7064
|
const wanted = opts.fnNames && opts.fnNames.length > 0 ? new Set(opts.fnNames) : void 0;
|
|
5299
7065
|
const derived = [];
|
|
5300
7066
|
for (const plan of plans) {
|
|
5301
|
-
const relRoot =
|
|
5302
|
-
const label = `${relRoot === "" || relRoot.startsWith("..") ?
|
|
7067
|
+
const relRoot = relative4(process.cwd(), abs);
|
|
7068
|
+
const label = `${relRoot === "" || relRoot.startsWith("..") ? basename4(abs) : relRoot}:${plan.fnName}`;
|
|
5303
7069
|
const rows = deriveOneRoot(plan, source, abs, modules, importLocals, label);
|
|
5304
7070
|
for (const row of rows) {
|
|
5305
7071
|
if (wanted && !wanted.has(row.fn)) continue;
|
|
@@ -5346,7 +7112,7 @@ function deriveOneRoot(plan, source, file, modules, importLocals, label) {
|
|
|
5346
7112
|
targetFile = file;
|
|
5347
7113
|
}
|
|
5348
7114
|
if (isNodeModulesPath2(targetFile)) continue;
|
|
5349
|
-
if (!
|
|
7115
|
+
if (!existsSync12(targetFile)) continue;
|
|
5350
7116
|
const key = `${targetFile}::${targetExport}`;
|
|
5351
7117
|
let agg = byCallee.get(key);
|
|
5352
7118
|
if (!agg) {
|
|
@@ -5360,7 +7126,7 @@ function deriveOneRoot(plan, source, file, modules, importLocals, label) {
|
|
|
5360
7126
|
for (const agg of byCallee.values()) {
|
|
5361
7127
|
let targetSource;
|
|
5362
7128
|
try {
|
|
5363
|
-
targetSource =
|
|
7129
|
+
targetSource = readFileSync10(agg.targetFile, "utf-8");
|
|
5364
7130
|
} catch {
|
|
5365
7131
|
continue;
|
|
5366
7132
|
}
|
|
@@ -5597,7 +7363,7 @@ function sidecarAssembles(text, fromFile, loadModule) {
|
|
|
5597
7363
|
}
|
|
5598
7364
|
}
|
|
5599
7365
|
function emitDerivedFromRoot(rootFile, opts) {
|
|
5600
|
-
const abs =
|
|
7366
|
+
const abs = resolve9(rootFile);
|
|
5601
7367
|
const derive = deriveFromRoot(abs, {
|
|
5602
7368
|
...opts.loadModule ? { loadModule: opts.loadModule } : {},
|
|
5603
7369
|
...opts.autoBind !== void 0 ? { autoBind: opts.autoBind } : {},
|
|
@@ -5606,10 +7372,10 @@ function emitDerivedFromRoot(rootFile, opts) {
|
|
|
5606
7372
|
if (!derive.hasRoot) {
|
|
5607
7373
|
return { sidecars: [], hasRoot: false, roots: [], entryOnly: true };
|
|
5608
7374
|
}
|
|
5609
|
-
const proj = findProjectConfig(
|
|
7375
|
+
const proj = findProjectConfig(dirname14(abs));
|
|
5610
7376
|
const allow = interfaceConfig(proj?.config).emit;
|
|
5611
7377
|
const projectDir = proj?.projectDir;
|
|
5612
|
-
const rootSidecarDir =
|
|
7378
|
+
const rootSidecarDir = dirname14(sidecarPathOf5(abs));
|
|
5613
7379
|
const wanted = opts.fnNames && opts.fnNames.length > 0 ? new Set(opts.fnNames) : void 0;
|
|
5614
7380
|
const result = {
|
|
5615
7381
|
sidecars: [],
|
|
@@ -5620,7 +7386,7 @@ function emitDerivedFromRoot(rootFile, opts) {
|
|
|
5620
7386
|
for (const row of derive.derived) {
|
|
5621
7387
|
if (wanted && !wanted.has(row.fn)) continue;
|
|
5622
7388
|
if (!matchesEmitAllowlist(row.file, projectDir, allow)) continue;
|
|
5623
|
-
const sp =
|
|
7389
|
+
const sp = sidecarPathOf5(row.file);
|
|
5624
7390
|
if (isNodeModulesPath2(sp)) continue;
|
|
5625
7391
|
const list = bySidecar.get(sp) ?? [];
|
|
5626
7392
|
list.push(row);
|
|
@@ -5628,8 +7394,8 @@ function emitDerivedFromRoot(rootFile, opts) {
|
|
|
5628
7394
|
}
|
|
5629
7395
|
for (const [sidecarPath, rows0] of bySidecar) {
|
|
5630
7396
|
const targetFile = rows0[0].file;
|
|
5631
|
-
const targetSidecarDir =
|
|
5632
|
-
const prevSrc =
|
|
7397
|
+
const targetSidecarDir = dirname14(sidecarPath);
|
|
7398
|
+
const prevSrc = existsSync12(sidecarPath) ? readFileSync10(sidecarPath, "utf-8") : "";
|
|
5633
7399
|
const rows = opts.refreshExistingOnly ? rows0.filter((r) => findGeneratedSectionText(prevSrc, r.fn) !== void 0) : rows0;
|
|
5634
7400
|
if (rows.length === 0) continue;
|
|
5635
7401
|
const handwritten = handwrittenNames(prevSrc);
|
|
@@ -5648,7 +7414,7 @@ function emitDerivedFromRoot(rootFile, opts) {
|
|
|
5648
7414
|
issues.push({
|
|
5649
7415
|
code: "nudo:interface-name-clash",
|
|
5650
7416
|
severity: "error",
|
|
5651
|
-
message: `sidecar already has a handwritten binding '${row.fn}' (${
|
|
7417
|
+
message: `sidecar already has a handwritten binding '${row.fn}' (${relative4(process.cwd(), sidecarPath) || sidecarPath}); handwritten wins \u2014 skipping emit`
|
|
5652
7418
|
});
|
|
5653
7419
|
anySkip = "name-clash";
|
|
5654
7420
|
continue;
|
|
@@ -5676,7 +7442,7 @@ function emitDerivedFromRoot(rootFile, opts) {
|
|
|
5676
7442
|
continue;
|
|
5677
7443
|
}
|
|
5678
7444
|
for (const n of body.usedNames) taken.add(n);
|
|
5679
|
-
const srcRel =
|
|
7445
|
+
const srcRel = relative4(targetSidecarDir, targetFile) || basename4(targetFile);
|
|
5680
7446
|
const section = [
|
|
5681
7447
|
DERIVED_HEADER,
|
|
5682
7448
|
`// source: ${srcRel}:${row.fn}`,
|
|
@@ -5696,8 +7462,8 @@ function emitDerivedFromRoot(rootFile, opts) {
|
|
|
5696
7462
|
for (const n of collectTopLevelNames(prevSection)) taken.add(n);
|
|
5697
7463
|
continue;
|
|
5698
7464
|
}
|
|
5699
|
-
const
|
|
5700
|
-
if (prevSection !== void 0 && normalizeSectionText(prevSection) ===
|
|
7465
|
+
const norm2 = normalizeSectionText(section);
|
|
7466
|
+
if (prevSection !== void 0 && normalizeSectionText(prevSection) === norm2) {
|
|
5701
7467
|
anySkip = "no-change";
|
|
5702
7468
|
accepted.push({ fn: row.fn, text: prevSection, prevText: prevSection });
|
|
5703
7469
|
for (const n of collectTopLevelNames(prevSection)) taken.add(n);
|
|
@@ -5726,7 +7492,7 @@ function emitDerivedFromRoot(rootFile, opts) {
|
|
|
5726
7492
|
issues.push({
|
|
5727
7493
|
code: "nudo:interface-not-projectable",
|
|
5728
7494
|
severity: "error",
|
|
5729
|
-
message: `assembled sidecar failed round-trip (${
|
|
7495
|
+
message: `assembled sidecar failed round-trip (${relative4(process.cwd(), sidecarPath) || sidecarPath}); refusing to write`
|
|
5730
7496
|
});
|
|
5731
7497
|
anySkip = "not-projectable";
|
|
5732
7498
|
result.sidecars.push({
|
|
@@ -5741,15 +7507,15 @@ function emitDerivedFromRoot(rootFile, opts) {
|
|
|
5741
7507
|
continue;
|
|
5742
7508
|
}
|
|
5743
7509
|
const changed = finalContent !== prevSrc;
|
|
5744
|
-
const diff = changed ? unifiedDiff(prevSrc, finalContent,
|
|
7510
|
+
const diff = changed ? unifiedDiff(prevSrc, finalContent, relative4(process.cwd(), sidecarPath) || sidecarPath) : void 0;
|
|
5745
7511
|
if (changed && !opts.dryRun) {
|
|
5746
7512
|
const tmp = `${sidecarPath}.tmp-${process.pid}-${Math.random().toString(16).slice(2, 10)}`;
|
|
5747
7513
|
try {
|
|
5748
|
-
|
|
7514
|
+
writeFileSync4(tmp, finalContent, "utf-8");
|
|
5749
7515
|
renameSync2(tmp, sidecarPath);
|
|
5750
7516
|
} catch (e) {
|
|
5751
7517
|
try {
|
|
5752
|
-
if (
|
|
7518
|
+
if (existsSync12(tmp)) unlinkSync2(tmp);
|
|
5753
7519
|
} catch {
|
|
5754
7520
|
}
|
|
5755
7521
|
throw e;
|
|
@@ -5900,10 +7666,18 @@ function derivedRoundTrips(text, fn, fromFile, loadModule) {
|
|
|
5900
7666
|
}
|
|
5901
7667
|
}
|
|
5902
7668
|
export {
|
|
7669
|
+
ANALYSIS_ABI,
|
|
7670
|
+
DEFAULT_ANALYSIS_MODE,
|
|
7671
|
+
DiskCache,
|
|
7672
|
+
HARVEST_NODE_DEFAULT_MAX_FILES,
|
|
7673
|
+
HARVEST_NODE_DEFAULT_MAX_MS,
|
|
5903
7674
|
SEMANTIC_TOKEN_MODIFIERS,
|
|
5904
7675
|
SEMANTIC_TOKEN_TYPES,
|
|
5905
7676
|
absToTSType,
|
|
5906
7677
|
absToZodSchema,
|
|
7678
|
+
ambientSourcesOfSidecar,
|
|
7679
|
+
analysisConfig,
|
|
7680
|
+
analysisFileCacheKey,
|
|
5907
7681
|
analyzeExportsFromSource,
|
|
5908
7682
|
analyzeFile,
|
|
5909
7683
|
analyzeFileAsync,
|
|
@@ -5913,12 +7687,16 @@ export {
|
|
|
5913
7687
|
buildCaseDirective,
|
|
5914
7688
|
buildModuleGraph,
|
|
5915
7689
|
buildSemanticTokens,
|
|
7690
|
+
checkCacheKey,
|
|
5916
7691
|
clearAbsModuleCache,
|
|
5917
7692
|
clearAnalysisFileCache,
|
|
5918
7693
|
clearAnalysisSessionCaches,
|
|
5919
7694
|
clearBPathCache,
|
|
7695
|
+
clearEnvPathDeps,
|
|
5920
7696
|
clearFnAnalysisCache,
|
|
5921
7697
|
clearHarvestCache,
|
|
7698
|
+
clearNodeHarvestCache,
|
|
7699
|
+
clearPathEnvCaches,
|
|
5922
7700
|
collectAbsBindingsFromGraph,
|
|
5923
7701
|
collectAbsInlays,
|
|
5924
7702
|
collectBPathDiagnostics,
|
|
@@ -5929,14 +7707,20 @@ export {
|
|
|
5929
7707
|
collectDtsFromEntry,
|
|
5930
7708
|
collectEnvGlobals,
|
|
5931
7709
|
collectEnvModules,
|
|
7710
|
+
collectLoadDepContents,
|
|
7711
|
+
collectParamBodyAccesses,
|
|
5932
7712
|
collectStaticImports,
|
|
5933
7713
|
computeDirtySet,
|
|
5934
7714
|
defaultAbsLoadModule,
|
|
5935
7715
|
defaultLoadModule,
|
|
5936
7716
|
deriveFromRoot,
|
|
7717
|
+
diagnosticsLevelForFile,
|
|
7718
|
+
diskCacheRoot,
|
|
7719
|
+
draftInterface,
|
|
5937
7720
|
emitDerivedFromRoot,
|
|
5938
7721
|
emitInterface,
|
|
5939
7722
|
encodeSemanticTokens,
|
|
7723
|
+
envPathDependents,
|
|
5940
7724
|
evalAbsModuleGraph,
|
|
5941
7725
|
evalProgramAbsWithModules,
|
|
5942
7726
|
evictAbsModuleCacheFiles,
|
|
@@ -5945,8 +7729,12 @@ export {
|
|
|
5945
7729
|
evictBPathCacheForFiles,
|
|
5946
7730
|
evictFnAnalysisCacheForFiles,
|
|
5947
7731
|
extractFnConstraintSources,
|
|
7732
|
+
extractNudoImportSpecs,
|
|
7733
|
+
filterDiagnosticsByLevel,
|
|
5948
7734
|
findProjectConfig,
|
|
5949
7735
|
formatDerivedSection,
|
|
7736
|
+
formatDraftModule,
|
|
7737
|
+
formatDraftSummary,
|
|
5950
7738
|
formatEmitSummary,
|
|
5951
7739
|
formatHarvestSummary,
|
|
5952
7740
|
formatInterfaceSurfaceLine,
|
|
@@ -5957,9 +7745,12 @@ export {
|
|
|
5957
7745
|
getAbsAtPosition,
|
|
5958
7746
|
getAbsAtPositionAsync,
|
|
5959
7747
|
getAnalysisFileCacheSize,
|
|
7748
|
+
getAnalysisSession,
|
|
5960
7749
|
getCasesForFile,
|
|
5961
7750
|
getCompletionsAtPosition,
|
|
7751
|
+
getEnvHarvestConflictCollector,
|
|
5962
7752
|
getHoverAtPosition,
|
|
7753
|
+
getNodeHarvestCacheSize,
|
|
5963
7754
|
getTypeAtPosition,
|
|
5964
7755
|
getTypeAtPositionAsync,
|
|
5965
7756
|
harvestNodeTypes,
|
|
@@ -5967,24 +7758,42 @@ export {
|
|
|
5967
7758
|
harvestPackageCached,
|
|
5968
7759
|
harvestToAbsModules,
|
|
5969
7760
|
harvestedValueToAbs,
|
|
7761
|
+
ifaceCacheKey,
|
|
5970
7762
|
insertGeneratedCaseDirectives,
|
|
5971
7763
|
interfaceConfig,
|
|
5972
7764
|
interfaceSurface,
|
|
7765
|
+
interfaceTierModifierBit,
|
|
5973
7766
|
isBPathCapable,
|
|
7767
|
+
isDraftableEntry,
|
|
7768
|
+
isEnvTemplatePath,
|
|
7769
|
+
isHarvestNodeDisabled,
|
|
5974
7770
|
isNudoTargetPath,
|
|
7771
|
+
isProjectConfigPath,
|
|
7772
|
+
isSidecarPath,
|
|
7773
|
+
isWatchRelevantPath,
|
|
5975
7774
|
lookupHarvested,
|
|
5976
7775
|
matchesEmitAllowlist,
|
|
7776
|
+
mergeHarvestUnderEnv,
|
|
5977
7777
|
mockDirectivesToAbsSeeds,
|
|
7778
|
+
noteEnvPathDeps,
|
|
5978
7779
|
packageHarvestToAbsModules,
|
|
7780
|
+
relativizePath,
|
|
5979
7781
|
resetAllAnalysisCaches,
|
|
5980
7782
|
resolvePackageRoot,
|
|
5981
7783
|
serializeCaseArg,
|
|
5982
7784
|
serializeInferJson,
|
|
7785
|
+
setAnalysisSession,
|
|
7786
|
+
setEnvHarvestConflictCollector,
|
|
7787
|
+
sha256Hex,
|
|
7788
|
+
shouldAnalyzeFile,
|
|
7789
|
+
sidecarDraftPath,
|
|
7790
|
+
hasNudoDirectives as sourceHasNudoDirectives,
|
|
5983
7791
|
stripGeneratedCaseDirectives,
|
|
5984
7792
|
summarizeNodeEnv,
|
|
5985
7793
|
topoSortDirty,
|
|
5986
7794
|
tryBPathCall,
|
|
5987
7795
|
tryBPathCallFull,
|
|
5988
7796
|
tryRunBPath,
|
|
5989
|
-
unifiedDiff
|
|
7797
|
+
unifiedDiff,
|
|
7798
|
+
writeInterfaceDraft
|
|
5990
7799
|
};
|