@nudojs/harvester 0.2.7 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/dist/index.d.ts +280 -2
- package/dist/index.js +1745 -91
- package/package.json +9 -3
package/dist/index.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
// src/
|
|
1
|
+
// src/harvest-dts.ts
|
|
2
2
|
import { readFileSync, statSync } from "fs";
|
|
3
3
|
import ts from "typescript";
|
|
4
4
|
import {
|
|
5
5
|
abs as makeAbs,
|
|
6
6
|
lit as termLit,
|
|
7
|
+
v as termVar,
|
|
7
8
|
numLit,
|
|
8
9
|
strLit,
|
|
9
10
|
boolLit,
|
|
10
11
|
objOf,
|
|
11
|
-
joinAbs
|
|
12
|
+
joinAbs,
|
|
13
|
+
relationFn,
|
|
14
|
+
getFnImpl,
|
|
15
|
+
substAbs
|
|
12
16
|
} from "@nudojs/core";
|
|
13
17
|
var MAX_ALIAS_DEPTH = 8;
|
|
14
18
|
function scopeKey(scope) {
|
|
@@ -46,6 +50,25 @@ function absArr(element) {
|
|
|
46
50
|
function absPromise(inner) {
|
|
47
51
|
return absExact({ k: "eff", eff: "promise", inner });
|
|
48
52
|
}
|
|
53
|
+
function absMapType(name, key, value) {
|
|
54
|
+
return absExact({
|
|
55
|
+
k: "brand",
|
|
56
|
+
name,
|
|
57
|
+
shape: absObj({
|
|
58
|
+
__key: key,
|
|
59
|
+
__value: value
|
|
60
|
+
})
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function absSetType(name, elem) {
|
|
64
|
+
return absExact({
|
|
65
|
+
k: "brand",
|
|
66
|
+
name,
|
|
67
|
+
shape: absObj({
|
|
68
|
+
__elem: elem
|
|
69
|
+
})
|
|
70
|
+
});
|
|
71
|
+
}
|
|
49
72
|
function absObj(properties) {
|
|
50
73
|
const slots = {};
|
|
51
74
|
for (const [k, v] of Object.entries(properties)) {
|
|
@@ -56,14 +79,16 @@ function absObj(properties) {
|
|
|
56
79
|
function absBrand(name, properties) {
|
|
57
80
|
return absExact({ k: "brand", name, shape: absObj(properties) });
|
|
58
81
|
}
|
|
59
|
-
function absFnSig(paramTypes, returnType) {
|
|
60
|
-
return
|
|
61
|
-
k: "fn",
|
|
82
|
+
function absFnSig(paramTypes, returnType, extra) {
|
|
83
|
+
return relationFn(paramTypes, returnType, {
|
|
62
84
|
params: paramTypes.map((_, i) => `_arg${i}`),
|
|
63
|
-
|
|
64
|
-
|
|
85
|
+
conf: "exact",
|
|
86
|
+
...extra ?? {}
|
|
65
87
|
});
|
|
66
88
|
}
|
|
89
|
+
function typeVarAbs(name) {
|
|
90
|
+
return makeAbs({ k: "any" }, termVar(name), void 0, "path");
|
|
91
|
+
}
|
|
67
92
|
function absUnion(members) {
|
|
68
93
|
if (members.length === 0) return absNever();
|
|
69
94
|
return members.reduce((acc, m) => joinAbs(acc, m));
|
|
@@ -71,6 +96,19 @@ function absUnion(members) {
|
|
|
71
96
|
function isPlainUnknown(a) {
|
|
72
97
|
return a.shape.k === "unknown" && !(a.term?.op === "lit");
|
|
73
98
|
}
|
|
99
|
+
function hasTypeVar(a, depth = 0) {
|
|
100
|
+
if (depth > 6) return false;
|
|
101
|
+
if (a.term?.op === "var") return true;
|
|
102
|
+
const s = a.shape;
|
|
103
|
+
if (s.k === "arr") return hasTypeVar(s.element, depth + 1);
|
|
104
|
+
if (s.k === "tuple") return s.elements.some((e) => hasTypeVar(e, depth + 1));
|
|
105
|
+
if (s.k === "sum") return s.members.some((m) => hasTypeVar(m, depth + 1));
|
|
106
|
+
if (s.k === "obj") return Object.values(s.slots).some((sl) => hasTypeVar(sl.value, depth + 1));
|
|
107
|
+
if (s.k === "fn") {
|
|
108
|
+
return (s.paramTypes ?? []).some((p) => hasTypeVar(p, depth + 1)) || (s.returnType ? hasTypeVar(s.returnType, depth + 1) : false);
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
74
112
|
function harvestDts(files, opts) {
|
|
75
113
|
const maxBytes = opts?.maxFileBytes ?? 15e5;
|
|
76
114
|
const deadline = opts?.maxMs !== void 0 ? Date.now() + opts.maxMs : void 0;
|
|
@@ -84,6 +122,7 @@ function harvestDts(files, opts) {
|
|
|
84
122
|
classes: /* @__PURE__ */ new Map(),
|
|
85
123
|
instanceCache: /* @__PURE__ */ new Map(),
|
|
86
124
|
expanding: /* @__PURE__ */ new Set(),
|
|
125
|
+
typeParams: /* @__PURE__ */ new Set(),
|
|
87
126
|
moduleAliases: [],
|
|
88
127
|
importEquals: /* @__PURE__ */ new Map(),
|
|
89
128
|
declaredModules: /* @__PURE__ */ new Set(),
|
|
@@ -141,7 +180,7 @@ function emitEnvModule(env, pkgName) {
|
|
|
141
180
|
constLines.push("");
|
|
142
181
|
}
|
|
143
182
|
const lines = [];
|
|
144
|
-
lines.push("// Auto-generated by
|
|
183
|
+
lines.push("// Auto-generated by @nudojs/harvester \u2014 DO NOT EDIT");
|
|
145
184
|
lines.push(`// Source package: ${pkgName}`);
|
|
146
185
|
lines.push(
|
|
147
186
|
'import { num, str, bool, never as absNever, unknown as absUnknown, numLit, strLit, boolLit, objOf, relationFn, abs as makeAbs } from "@nudojs/core";'
|
|
@@ -222,9 +261,17 @@ function collectStatement(ctx, stmt, scope) {
|
|
|
222
261
|
ctx.skipped++;
|
|
223
262
|
continue;
|
|
224
263
|
}
|
|
225
|
-
const
|
|
226
|
-
if (
|
|
227
|
-
|
|
264
|
+
const ifaceName = decl.type && ts.isTypeReferenceNode(decl.type) ? typeNameString(decl.type.typeName) : void 0;
|
|
265
|
+
if (ifaceName && (ctx.interfaces.has(ifaceName) || ctx.qualifiedInterfaces.has(ifaceName) || true)) {
|
|
266
|
+
ctx.pending.push({
|
|
267
|
+
t: "iface-expand",
|
|
268
|
+
scope,
|
|
269
|
+
localName: decl.name.text,
|
|
270
|
+
typeRefName: ifaceName
|
|
271
|
+
});
|
|
272
|
+
ctx.symbols++;
|
|
273
|
+
ctx.pending.push({ t: "var", scope, name: decl.name.text, typeNode: decl.type });
|
|
274
|
+
ctx.symbols++;
|
|
228
275
|
continue;
|
|
229
276
|
}
|
|
230
277
|
ctx.pending.push({ t: "var", scope, name: decl.name.text, typeNode: decl.type });
|
|
@@ -320,66 +367,122 @@ function collectStatement(ctx, stmt, scope) {
|
|
|
320
367
|
ctx.skipped++;
|
|
321
368
|
}
|
|
322
369
|
function registerTypeDecl(ctx, scope, name, decl) {
|
|
323
|
-
|
|
370
|
+
const push = (map, key) => {
|
|
371
|
+
const list = map.get(key);
|
|
372
|
+
if (list) {
|
|
373
|
+
if (!list.includes(decl)) list.push(decl);
|
|
374
|
+
} else {
|
|
375
|
+
map.set(key, [decl]);
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
push(ctx.interfaces, name);
|
|
324
379
|
if (ts.isClassDeclaration(decl)) return;
|
|
325
380
|
const qualified = [...scope.ns, name].join(".");
|
|
326
|
-
if (qualified !== name
|
|
327
|
-
ctx.qualifiedInterfaces.set(qualified, decl);
|
|
328
|
-
}
|
|
381
|
+
if (qualified !== name) push(ctx.qualifiedInterfaces, qualified);
|
|
329
382
|
}
|
|
330
|
-
function
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
const
|
|
334
|
-
|
|
383
|
+
function interfaceDeclsOf(ctx, typeNode) {
|
|
384
|
+
const name = typeof typeNode === "string" ? typeNode : ts.isTypeReferenceNode(typeNode) ? typeNameString(typeNode.typeName) : void 0;
|
|
385
|
+
if (!name) return [];
|
|
386
|
+
const short = name.includes(".") ? name.split(".").pop() : name;
|
|
387
|
+
const keys = name === short ? [name] : [name, short];
|
|
388
|
+
const seen = /* @__PURE__ */ new Set();
|
|
389
|
+
const out = [];
|
|
390
|
+
for (const key of keys) {
|
|
391
|
+
for (const d of [
|
|
392
|
+
...ctx.interfaces.get(key) ?? [],
|
|
393
|
+
...ctx.qualifiedInterfaces.get(key) ?? []
|
|
394
|
+
]) {
|
|
395
|
+
if (ts.isInterfaceDeclaration(d) && !seen.has(d)) {
|
|
396
|
+
seen.add(d);
|
|
397
|
+
out.push(d);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return out;
|
|
335
402
|
}
|
|
336
|
-
function expandInterfaceAsModuleMembers(ctx, scope,
|
|
337
|
-
for (const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
existing.
|
|
403
|
+
function expandInterfaceAsModuleMembers(ctx, scope, ifaces) {
|
|
404
|
+
for (const iface of ifaces) {
|
|
405
|
+
for (const member of iface.members) {
|
|
406
|
+
if (ts.isMethodSignature(member)) {
|
|
407
|
+
const name = memberName(member);
|
|
408
|
+
if (name === void 0) continue;
|
|
409
|
+
const existing = ctx.pending.find(
|
|
410
|
+
(p) => p.t === "fn" && p.name === name && scopeKey(p.scope) === scopeKey(scope)
|
|
411
|
+
);
|
|
412
|
+
if (existing && existing.t === "fn") {
|
|
413
|
+
existing.decls.push(member);
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
ctx.pending.push({ t: "fn", scope, name, decls: [member] });
|
|
417
|
+
ctx.symbols++;
|
|
346
418
|
continue;
|
|
347
419
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
420
|
+
if (ts.isPropertySignature(member)) {
|
|
421
|
+
const name = memberName(member);
|
|
422
|
+
if (name === void 0) continue;
|
|
423
|
+
if (ctx.pending.some((p) => p.t === "var" && p.name === name && scopeKey(p.scope) === scopeKey(scope))) {
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
ctx.pending.push({ t: "var", scope, name, typeNode: member.type });
|
|
427
|
+
ctx.symbols++;
|
|
428
|
+
}
|
|
357
429
|
}
|
|
358
430
|
}
|
|
359
431
|
}
|
|
360
432
|
function registerAlias(ctx, scope, stmt) {
|
|
361
433
|
const name = stmt.name.text;
|
|
362
|
-
|
|
434
|
+
const push = (map, key) => {
|
|
435
|
+
const list = map.get(key);
|
|
436
|
+
if (list) {
|
|
437
|
+
if (!list.includes(stmt)) list.push(stmt);
|
|
438
|
+
} else {
|
|
439
|
+
map.set(key, [stmt]);
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
push(ctx.aliases, name);
|
|
363
443
|
const qualified = [...scope.ns, name].join(".");
|
|
364
|
-
if (qualified !== name
|
|
365
|
-
ctx.qualifiedAliases.set(qualified, stmt);
|
|
366
|
-
}
|
|
444
|
+
if (qualified !== name) push(ctx.qualifiedAliases, qualified);
|
|
367
445
|
}
|
|
368
446
|
function materialize(ctx) {
|
|
447
|
+
const expanded = [];
|
|
369
448
|
for (const p of ctx.pending) {
|
|
449
|
+
if (p.t !== "iface-expand") {
|
|
450
|
+
expanded.push(p);
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
const ifaces = interfaceDeclsOf(ctx, p.typeRefName);
|
|
454
|
+
if (ifaces.length === 0) {
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
const before = ctx.pending.length;
|
|
458
|
+
expandInterfaceAsModuleMembers(ctx, p.scope, ifaces);
|
|
459
|
+
for (let i = before; i < ctx.pending.length; i++) {
|
|
460
|
+
const n = ctx.pending[i];
|
|
461
|
+
if (n.t === "fn" || n.t === "var") expanded.push(n);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
ctx.pending = expanded;
|
|
465
|
+
for (const p of ctx.pending) {
|
|
466
|
+
if (p.t === "iface-expand") continue;
|
|
370
467
|
const rec = recordFor(ctx, p.scope);
|
|
371
468
|
if (p.t === "fn") {
|
|
372
469
|
if (rec[p.name] !== void 0) continue;
|
|
373
|
-
const
|
|
374
|
-
const
|
|
375
|
-
|
|
470
|
+
const sigs = p.decls.map((d) => signatureToFnSig(ctx, d));
|
|
471
|
+
const value = sigs[0];
|
|
472
|
+
const shape0 = value.shape.k === "fn" ? value.shape : void 0;
|
|
473
|
+
if (shape0 && sigs.length > 1) {
|
|
376
474
|
const returns = [];
|
|
377
|
-
for (const
|
|
378
|
-
if (
|
|
379
|
-
const ret =
|
|
380
|
-
if (!isPlainUnknown(ret)) returns.push(ret);
|
|
475
|
+
for (const s of sigs) {
|
|
476
|
+
if (s.shape.k !== "fn") continue;
|
|
477
|
+
const ret = s.shape.returnType;
|
|
478
|
+
if (ret && !isPlainUnknown(ret)) returns.push(ret);
|
|
381
479
|
}
|
|
382
|
-
|
|
480
|
+
const withVars = returns.filter((r) => hasTypeVar(r));
|
|
481
|
+
const pool = withVars.length > 0 ? withVars : returns;
|
|
482
|
+
const mergedRet = pool.length > 0 ? absUnion(pool) : absUnknown();
|
|
483
|
+
shape0.returnType = mergedRet;
|
|
484
|
+
const rel = getFnImpl(value)?.relation;
|
|
485
|
+
if (rel) rel.returnType = mergedRet;
|
|
383
486
|
}
|
|
384
487
|
rec[p.name] = value;
|
|
385
488
|
continue;
|
|
@@ -434,58 +537,91 @@ function memberName(node) {
|
|
|
434
537
|
if (ts.isIdentifier(n) || ts.isStringLiteral(n) || ts.isNumericLiteral(n)) return n.text;
|
|
435
538
|
return void 0;
|
|
436
539
|
}
|
|
437
|
-
function instanceFor(ctx, key) {
|
|
438
|
-
const
|
|
540
|
+
function instanceFor(ctx, key, typeArgs) {
|
|
541
|
+
const cacheKey = typeArgs && typeArgs.length > 0 ? `${key}::${typeArgs.length}` : key;
|
|
542
|
+
const cached = ctx.instanceCache.get(cacheKey);
|
|
439
543
|
if (cached !== void 0) return cached;
|
|
440
|
-
const
|
|
544
|
+
const decls = interfaceDeclsOf(ctx, key);
|
|
545
|
+
const decl = decls[0];
|
|
441
546
|
if (!decl) return absUnknown();
|
|
442
547
|
if (ctx.expanding.has(key)) {
|
|
443
548
|
return absBrand(decl.name?.text ?? key, {});
|
|
444
549
|
}
|
|
445
550
|
ctx.expanding.add(key);
|
|
551
|
+
const savedTp = ctx.typeParams;
|
|
552
|
+
ctx.typeParams = new Set(savedTp);
|
|
553
|
+
for (const d of decls) {
|
|
554
|
+
for (const tp of d.typeParameters ?? []) ctx.typeParams.add(tp.name.text);
|
|
555
|
+
}
|
|
446
556
|
const properties = {};
|
|
447
557
|
try {
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
558
|
+
for (const d of decls) {
|
|
559
|
+
const members = d.members;
|
|
560
|
+
for (const member of members) {
|
|
561
|
+
const modifiers = member.modifiers;
|
|
562
|
+
if (modifiers?.some((mod) => mod.kind === ts.SyntaxKind.StaticKeyword)) continue;
|
|
563
|
+
if (ts.isPropertySignature(member) || ts.isPropertyDeclaration(member)) {
|
|
564
|
+
const name = memberName(member);
|
|
565
|
+
if (name === void 0) continue;
|
|
566
|
+
let type = member.type ? mapType(ctx, member.type, 0) : absUnknown();
|
|
567
|
+
if (member.questionToken) type = absUnion([type, absUndefLit()]);
|
|
568
|
+
properties[name] = type;
|
|
569
|
+
} else if (ts.isMethodSignature(member) || ts.isMethodDeclaration(member)) {
|
|
570
|
+
const name = memberName(member);
|
|
571
|
+
if (name === void 0 || properties[name] !== void 0) continue;
|
|
572
|
+
properties[name] = signatureToFnSig(ctx, member);
|
|
573
|
+
} else if (ts.isGetAccessorDeclaration(member) || ts.isSetAccessorDeclaration(member)) {
|
|
574
|
+
const name = memberName(member);
|
|
575
|
+
if (name === void 0 || properties[name] !== void 0 || !member.type) continue;
|
|
576
|
+
properties[name] = mapType(ctx, member.type, 0);
|
|
577
|
+
}
|
|
466
578
|
}
|
|
467
579
|
}
|
|
468
580
|
} finally {
|
|
581
|
+
ctx.typeParams = savedTp;
|
|
469
582
|
ctx.expanding.delete(key);
|
|
470
583
|
}
|
|
471
|
-
|
|
472
|
-
|
|
584
|
+
let value = absBrand(decl.name?.text ?? key, properties);
|
|
585
|
+
if (typeArgs && typeArgs.length > 0 && Object.keys(properties).length <= 40) {
|
|
586
|
+
const map = /* @__PURE__ */ new Map();
|
|
587
|
+
const tps = decls[0].typeParameters ?? [];
|
|
588
|
+
tps.forEach((tp, i) => {
|
|
589
|
+
const arg = typeArgs[i];
|
|
590
|
+
if (arg) map.set(tp.name.text, arg);
|
|
591
|
+
});
|
|
592
|
+
if (map.size > 0) value = substAbs(value, map);
|
|
593
|
+
}
|
|
594
|
+
ctx.instanceCache.set(cacheKey, value);
|
|
473
595
|
return value;
|
|
474
596
|
}
|
|
475
597
|
function signatureToFnSig(ctx, sig) {
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
598
|
+
const savedTp = ctx.typeParams;
|
|
599
|
+
ctx.typeParams = new Set(savedTp);
|
|
600
|
+
for (const tp of sig.typeParameters ?? []) {
|
|
601
|
+
ctx.typeParams.add(tp.name.text);
|
|
602
|
+
}
|
|
603
|
+
try {
|
|
604
|
+
const paramTypes = sig.parameters.map((param) => {
|
|
605
|
+
let type;
|
|
606
|
+
if (param.dotDotDotToken) {
|
|
607
|
+
const inner = param.type && ts.isArrayTypeNode(param.type) ? param.type.elementType : param.type;
|
|
608
|
+
type = inner ? absArr(mapType(ctx, inner, 0)) : absUnknown();
|
|
609
|
+
} else {
|
|
610
|
+
type = param.type ? mapType(ctx, param.type, 0) : absUnknown();
|
|
611
|
+
}
|
|
612
|
+
if (param.questionToken && !param.dotDotDotToken) type = absUnion([type, absUndefLit()]);
|
|
613
|
+
return type;
|
|
614
|
+
});
|
|
615
|
+
const returnType = sig.type ? mapType(ctx, sig.type, 0) : absUnknown();
|
|
616
|
+
const cond = ctx.pendingCond;
|
|
617
|
+
ctx.pendingCond = void 0;
|
|
618
|
+
return absFnSig(paramTypes, returnType, cond ? {
|
|
619
|
+
inferFrom: { fromVar: cond.fromVar, via: cond.via, inferVar: cond.inferVar },
|
|
620
|
+
condFallback: cond.fallback
|
|
621
|
+
} : void 0);
|
|
622
|
+
} finally {
|
|
623
|
+
ctx.typeParams = savedTp;
|
|
624
|
+
}
|
|
489
625
|
}
|
|
490
626
|
function mapTypeMembers(ctx, members, depth) {
|
|
491
627
|
const properties = {};
|
|
@@ -507,22 +643,163 @@ function mapTypeMembers(ctx, members, depth) {
|
|
|
507
643
|
function mapTypeRef(ctx, node, depth) {
|
|
508
644
|
const name = typeNameString(node.typeName);
|
|
509
645
|
const args = node.typeArguments ?? [];
|
|
646
|
+
if (ctx.typeParams.has(name) && args.length === 0) {
|
|
647
|
+
return typeVarAbs(name);
|
|
648
|
+
}
|
|
510
649
|
if ((name === "Array" || name === "ReadonlyArray") && args.length >= 1) {
|
|
511
650
|
return absArr(mapType(ctx, args[0], depth));
|
|
512
651
|
}
|
|
652
|
+
if ((name === "List" || name === "ArrayLike" || name === "Collection" || name === "Many" || name === "NumericDictionary" || name === "Dictionary") && args.length >= 1) {
|
|
653
|
+
return absArr(mapType(ctx, args[0], depth));
|
|
654
|
+
}
|
|
513
655
|
if (name === "Promise" && args.length >= 1) {
|
|
514
656
|
return absPromise(mapType(ctx, args[0], depth));
|
|
515
657
|
}
|
|
658
|
+
if ((name === "Map" || name === "ReadonlyMap" || name === "WeakMap") && args.length >= 2) {
|
|
659
|
+
return absMapType(name, mapType(ctx, args[0], depth), mapType(ctx, args[1], depth));
|
|
660
|
+
}
|
|
661
|
+
if ((name === "Set" || name === "ReadonlySet" || name === "WeakSet") && args.length >= 1) {
|
|
662
|
+
return absSetType(name, mapType(ctx, args[0], depth));
|
|
663
|
+
}
|
|
516
664
|
if (name === "Record") {
|
|
517
665
|
return absObj({});
|
|
518
666
|
}
|
|
519
667
|
if (ctx.interfaces.has(name) || ctx.qualifiedInterfaces.has(name)) {
|
|
520
|
-
return instanceFor(ctx, name);
|
|
668
|
+
return instanceFor(ctx, name, args.map((a) => mapType(ctx, a, depth)));
|
|
521
669
|
}
|
|
522
|
-
const
|
|
670
|
+
const aliases = ctx.aliases.get(name) ?? ctx.qualifiedAliases.get(name) ?? [];
|
|
671
|
+
const alias = aliases[0];
|
|
523
672
|
if (alias) return mapType(ctx, alias.type, depth + 1);
|
|
524
673
|
return absUnknown();
|
|
525
674
|
}
|
|
675
|
+
function collectInferNames(node, out = /* @__PURE__ */ new Set()) {
|
|
676
|
+
const visit = (n) => {
|
|
677
|
+
if (!n) return;
|
|
678
|
+
if (ts.isInferTypeNode(n)) {
|
|
679
|
+
out.add(n.typeParameter.name.text);
|
|
680
|
+
visit(n.typeParameter.constraint);
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
n.forEachChild(visit);
|
|
684
|
+
};
|
|
685
|
+
visit(node);
|
|
686
|
+
return out;
|
|
687
|
+
}
|
|
688
|
+
function absExtends(check, ext) {
|
|
689
|
+
if (check.term?.op === "var") return "unknown";
|
|
690
|
+
if (ext.term?.op === "lit" && (ext.term.value === null || ext.term.value === void 0)) {
|
|
691
|
+
if (check.term?.op === "lit") {
|
|
692
|
+
return check.term.value === ext.term.value ? "true" : "false";
|
|
693
|
+
}
|
|
694
|
+
if (check.shape.k === "prim" || check.shape.k === "arr" || check.shape.k === "obj" || check.shape.k === "fn" || check.shape.k === "brand" || check.shape.k === "eff") {
|
|
695
|
+
return "false";
|
|
696
|
+
}
|
|
697
|
+
return "unknown";
|
|
698
|
+
}
|
|
699
|
+
if (ext.shape.k === "unknown") return "true";
|
|
700
|
+
if (ext.shape.k === "any") return "true";
|
|
701
|
+
const cs = check.shape;
|
|
702
|
+
const es = ext.shape;
|
|
703
|
+
if (es.k === "prim" && cs.k === "prim") {
|
|
704
|
+
return cs.type === es.type ? "true" : "false";
|
|
705
|
+
}
|
|
706
|
+
if (es.k === "sum") {
|
|
707
|
+
const flags = es.members.map((m) => absExtends(check, m));
|
|
708
|
+
if (flags.some((f) => f === "true")) return "true";
|
|
709
|
+
if (flags.every((f) => f === "false")) return "false";
|
|
710
|
+
return "unknown";
|
|
711
|
+
}
|
|
712
|
+
if (cs.k === "sum") {
|
|
713
|
+
const flags = cs.members.map((m) => absExtends(m, ext));
|
|
714
|
+
if (flags.every((f) => f === "true")) return "true";
|
|
715
|
+
if (flags.every((f) => f === "false")) return "false";
|
|
716
|
+
return "unknown";
|
|
717
|
+
}
|
|
718
|
+
if (cs.k === "arr" && es.k === "arr") {
|
|
719
|
+
return absExtends(cs.element, es.element);
|
|
720
|
+
}
|
|
721
|
+
if (cs.k === "eff" && es.k === "eff" && cs.eff === es.eff) {
|
|
722
|
+
return absExtends(cs.inner, es.inner);
|
|
723
|
+
}
|
|
724
|
+
if (cs.k === "brand" && es.k === "brand") {
|
|
725
|
+
return cs.name === es.name ? "true" : "false";
|
|
726
|
+
}
|
|
727
|
+
if (cs.k === "never") return "true";
|
|
728
|
+
return "unknown";
|
|
729
|
+
}
|
|
730
|
+
function matchExtendsInfer(pattern, actual, inferMap, depth = 0) {
|
|
731
|
+
if (depth > 6) return "unknown";
|
|
732
|
+
if (ts.isInferTypeNode(pattern)) {
|
|
733
|
+
const name = pattern.typeParameter.name.text;
|
|
734
|
+
if (!inferMap.has(name)) inferMap.set(name, actual);
|
|
735
|
+
return "true";
|
|
736
|
+
}
|
|
737
|
+
if (ts.isArrayTypeNode(pattern) && actual.shape.k === "arr") {
|
|
738
|
+
return matchExtendsInfer(pattern.elementType, actual.shape.element, inferMap, depth + 1);
|
|
739
|
+
}
|
|
740
|
+
if (ts.isTypeReferenceNode(pattern)) {
|
|
741
|
+
const name = typeNameString(pattern.typeName);
|
|
742
|
+
const args = pattern.typeArguments ?? [];
|
|
743
|
+
if ((name === "Array" || name === "ReadonlyArray" || name === "List" || name === "ArrayLike") && args[0] && actual.shape.k === "arr") {
|
|
744
|
+
return matchExtendsInfer(args[0], actual.shape.element, inferMap, depth + 1);
|
|
745
|
+
}
|
|
746
|
+
if (name === "Promise" && args[0] && actual.shape.k === "eff" && actual.shape.eff === "promise") {
|
|
747
|
+
return matchExtendsInfer(args[0], actual.shape.inner, inferMap, depth + 1);
|
|
748
|
+
}
|
|
749
|
+
return "unknown";
|
|
750
|
+
}
|
|
751
|
+
if (ts.isUnionTypeNode(pattern)) {
|
|
752
|
+
const flags = pattern.types.map((t) => matchExtendsInfer(t, actual, inferMap, depth + 1));
|
|
753
|
+
if (flags.some((f) => f === "true")) return "true";
|
|
754
|
+
if (flags.every((f) => f === "false")) return "false";
|
|
755
|
+
return "unknown";
|
|
756
|
+
}
|
|
757
|
+
return "unknown";
|
|
758
|
+
}
|
|
759
|
+
function mapConditionalType(ctx, node, depth) {
|
|
760
|
+
const inferMap = /* @__PURE__ */ new Map();
|
|
761
|
+
const inferNames = collectInferNames(node.extendsType);
|
|
762
|
+
const check = mapType(ctx, node.checkType, depth + 1);
|
|
763
|
+
const astMatch = matchExtendsInfer(node.extendsType, check, inferMap);
|
|
764
|
+
const extAbs = mapType(ctx, node.extendsType, depth + 1);
|
|
765
|
+
const absMatch = astMatch !== "unknown" ? astMatch : absExtends(check, extAbs);
|
|
766
|
+
const condMeta = analyzeCondPattern(ctx, node, check);
|
|
767
|
+
const savedTp = ctx.typeParams;
|
|
768
|
+
ctx.typeParams = new Set(savedTp);
|
|
769
|
+
for (const n of inferNames) ctx.typeParams.add(n);
|
|
770
|
+
try {
|
|
771
|
+
const trueAbs = inferMap.size > 0 ? substAbs(mapType(ctx, node.trueType, depth + 1), inferMap) : mapType(ctx, node.trueType, depth + 1);
|
|
772
|
+
const falseAbs = mapType(ctx, node.falseType, depth + 1);
|
|
773
|
+
if (absMatch === "true") return trueAbs;
|
|
774
|
+
if (absMatch === "false") return falseAbs;
|
|
775
|
+
if (condMeta && check.term?.op === "var") {
|
|
776
|
+
ctx.pendingCond = { ...condMeta, fallback: falseAbs };
|
|
777
|
+
return typeVarAbs(condMeta.inferVar);
|
|
778
|
+
}
|
|
779
|
+
return absUnion([trueAbs, falseAbs]);
|
|
780
|
+
} finally {
|
|
781
|
+
ctx.typeParams = savedTp;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
function analyzeCondPattern(_ctx, node, check) {
|
|
785
|
+
if (check.term?.op !== "var") return void 0;
|
|
786
|
+
const fromVar = check.term.id;
|
|
787
|
+
const ext = node.extendsType;
|
|
788
|
+
const names = collectInferNames(ext);
|
|
789
|
+
const inferVar = [...names][0];
|
|
790
|
+
if (!inferVar) return void 0;
|
|
791
|
+
if (ts.isArrayTypeNode(ext)) {
|
|
792
|
+
return { fromVar, via: "arr", inferVar };
|
|
793
|
+
}
|
|
794
|
+
if (ts.isTypeReferenceNode(ext)) {
|
|
795
|
+
const n = typeNameString(ext.typeName);
|
|
796
|
+
if (n === "Promise" || n === "PromiseLike") return { fromVar, via: "promise", inferVar };
|
|
797
|
+
if (n === "Array" || n === "ReadonlyArray" || n === "List" || n === "ArrayLike") {
|
|
798
|
+
return { fromVar, via: "arr", inferVar };
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
return void 0;
|
|
802
|
+
}
|
|
526
803
|
function mapType(ctx, node, depth) {
|
|
527
804
|
if (!node || depth > MAX_ALIAS_DEPTH) return absUnknown();
|
|
528
805
|
if (ts.isParenthesizedTypeNode(node)) return mapType(ctx, node.type, depth);
|
|
@@ -536,7 +813,12 @@ function mapType(ctx, node, depth) {
|
|
|
536
813
|
return absUnknown();
|
|
537
814
|
}
|
|
538
815
|
if (ts.isUnionTypeNode(node)) {
|
|
539
|
-
|
|
816
|
+
const members = node.types.map((t) => mapType(ctx, t, depth));
|
|
817
|
+
const hasLit = members.some((m) => m.term?.op === "lit");
|
|
818
|
+
if (hasLit && members.length > 1) {
|
|
819
|
+
return absExact({ k: "sum", members });
|
|
820
|
+
}
|
|
821
|
+
return absUnion(members);
|
|
540
822
|
}
|
|
541
823
|
if (ts.isIntersectionTypeNode(node)) {
|
|
542
824
|
const members = node.types.map((t) => mapType(ctx, t, depth));
|
|
@@ -558,6 +840,7 @@ function mapType(ctx, node, depth) {
|
|
|
558
840
|
if (ts.isNumericLiteral(lit)) return absLit(Number(lit.text));
|
|
559
841
|
if (lit.kind === ts.SyntaxKind.TrueKeyword) return absLit(true);
|
|
560
842
|
if (lit.kind === ts.SyntaxKind.FalseKeyword) return absLit(false);
|
|
843
|
+
if (lit.kind === ts.SyntaxKind.NullKeyword) return absNullLit();
|
|
561
844
|
if (ts.isPrefixUnaryExpression(lit) && lit.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(lit.operand)) {
|
|
562
845
|
return absLit(-Number(lit.operand.text));
|
|
563
846
|
}
|
|
@@ -580,6 +863,12 @@ function mapType(ctx, node, depth) {
|
|
|
580
863
|
if (ts.isTypeOperatorNode(node)) {
|
|
581
864
|
return node.operator === ts.SyntaxKind.ReadonlyKeyword ? mapType(ctx, node.type, depth) : absUnknown();
|
|
582
865
|
}
|
|
866
|
+
if (ts.isConditionalTypeNode(node)) {
|
|
867
|
+
return mapConditionalType(ctx, node, depth);
|
|
868
|
+
}
|
|
869
|
+
if (ts.isInferTypeNode(node)) {
|
|
870
|
+
return typeVarAbs(node.typeParameter.name.text);
|
|
871
|
+
}
|
|
583
872
|
switch (node.kind) {
|
|
584
873
|
case ts.SyntaxKind.StringKeyword:
|
|
585
874
|
return absPrim("string");
|
|
@@ -684,7 +973,1372 @@ function emitAbs(a, expanding) {
|
|
|
684
973
|
return "absUnknown";
|
|
685
974
|
}
|
|
686
975
|
}
|
|
976
|
+
|
|
977
|
+
// src/dts-contract-draft.ts
|
|
978
|
+
import { readFileSync as readFileSync2, existsSync, statSync as statSync2, readdirSync } from "fs";
|
|
979
|
+
import { basename, dirname, join } from "path";
|
|
980
|
+
import ts2 from "typescript";
|
|
981
|
+
var BUILDERS = [
|
|
982
|
+
"fn",
|
|
983
|
+
"number",
|
|
984
|
+
"string",
|
|
985
|
+
"boolean",
|
|
986
|
+
"any",
|
|
987
|
+
"shape",
|
|
988
|
+
"array",
|
|
989
|
+
"lit",
|
|
990
|
+
"union"
|
|
991
|
+
];
|
|
992
|
+
function keywordDsl(node) {
|
|
993
|
+
switch (node.kind) {
|
|
994
|
+
case ts2.SyntaxKind.NumberKeyword:
|
|
995
|
+
return "number()";
|
|
996
|
+
case ts2.SyntaxKind.StringKeyword:
|
|
997
|
+
return "string()";
|
|
998
|
+
case ts2.SyntaxKind.BooleanKeyword:
|
|
999
|
+
return "boolean()";
|
|
1000
|
+
case ts2.SyntaxKind.AnyKeyword:
|
|
1001
|
+
case ts2.SyntaxKind.UnknownKeyword:
|
|
1002
|
+
case ts2.SyntaxKind.VoidKeyword:
|
|
1003
|
+
case ts2.SyntaxKind.UndefinedKeyword:
|
|
1004
|
+
case ts2.SyntaxKind.NullKeyword:
|
|
1005
|
+
case ts2.SyntaxKind.ObjectKeyword:
|
|
1006
|
+
case ts2.SyntaxKind.NeverKeyword:
|
|
1007
|
+
return "any()";
|
|
1008
|
+
case ts2.SyntaxKind.BigIntKeyword:
|
|
1009
|
+
case ts2.SyntaxKind.SymbolKeyword:
|
|
1010
|
+
return void 0;
|
|
1011
|
+
default:
|
|
1012
|
+
return void 0;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
function litDsl(node) {
|
|
1016
|
+
const lit = node.literal;
|
|
1017
|
+
if (ts2.isStringLiteral(lit)) return `lit(${JSON.stringify(lit.text)})`;
|
|
1018
|
+
if (ts2.isNumericLiteral(lit)) return `lit(${lit.text})`;
|
|
1019
|
+
if (lit.kind === ts2.SyntaxKind.TrueKeyword) return "lit(true)";
|
|
1020
|
+
if (lit.kind === ts2.SyntaxKind.FalseKeyword) return "lit(false)";
|
|
1021
|
+
return void 0;
|
|
1022
|
+
}
|
|
1023
|
+
function typeRefName(node) {
|
|
1024
|
+
const n = node.typeName;
|
|
1025
|
+
if (ts2.isIdentifier(n)) return n.text;
|
|
1026
|
+
if (ts2.isQualifiedName(n)) {
|
|
1027
|
+
const parts = [];
|
|
1028
|
+
let cur = n;
|
|
1029
|
+
while (ts2.isQualifiedName(cur)) {
|
|
1030
|
+
parts.unshift(cur.right.text);
|
|
1031
|
+
cur = cur.left;
|
|
1032
|
+
}
|
|
1033
|
+
if (ts2.isIdentifier(cur)) parts.unshift(cur.text);
|
|
1034
|
+
return parts.join(".");
|
|
1035
|
+
}
|
|
1036
|
+
return "?";
|
|
1037
|
+
}
|
|
1038
|
+
function typeToDsl(node, depth = 0) {
|
|
1039
|
+
if (!node || depth > 6) return "any()";
|
|
1040
|
+
const kw = keywordDsl(node);
|
|
1041
|
+
if (kw !== void 0) return kw;
|
|
1042
|
+
if (ts2.isLiteralTypeNode(node)) {
|
|
1043
|
+
return litDsl(node) ?? "any()";
|
|
1044
|
+
}
|
|
1045
|
+
if (ts2.isArrayTypeNode(node)) {
|
|
1046
|
+
const el = typeToDsl(node.elementType, depth + 1) ?? "any()";
|
|
1047
|
+
return `array(${el})`;
|
|
1048
|
+
}
|
|
1049
|
+
if (ts2.isUnionTypeNode(node)) {
|
|
1050
|
+
const parts = node.types.map((t) => typeToDsl(t, depth + 1) ?? "any()");
|
|
1051
|
+
if (parts.length === 0) return "any()";
|
|
1052
|
+
if (parts.length === 1) return parts[0];
|
|
1053
|
+
return `union(${parts.join(", ")})`;
|
|
1054
|
+
}
|
|
1055
|
+
if (ts2.isParenthesizedTypeNode(node)) {
|
|
1056
|
+
return typeToDsl(node.type, depth + 1);
|
|
1057
|
+
}
|
|
1058
|
+
if (ts2.isTypeLiteralNode(node)) {
|
|
1059
|
+
const fields = [];
|
|
1060
|
+
for (const m of node.members) {
|
|
1061
|
+
if (!ts2.isPropertySignature(m)) continue;
|
|
1062
|
+
const key = m.name && (ts2.isIdentifier(m.name) || ts2.isStringLiteral(m.name)) ? ts2.isIdentifier(m.name) ? m.name.text : m.name.text : void 0;
|
|
1063
|
+
if (!key) continue;
|
|
1064
|
+
const optional = m.questionToken ? true : false;
|
|
1065
|
+
const val = typeToDsl(m.type, depth + 1) ?? "any()";
|
|
1066
|
+
fields.push(optional ? `${key}: ${val}.optional()` : `${key}: ${val}`);
|
|
1067
|
+
}
|
|
1068
|
+
return fields.length === 0 ? "shape({})" : `shape({ ${fields.join(", ")} })`;
|
|
1069
|
+
}
|
|
1070
|
+
if (ts2.isFunctionTypeNode(node) || ts2.isConstructorTypeNode(node)) {
|
|
1071
|
+
return fnSignatureDsl(node.parameters, node.type, depth);
|
|
1072
|
+
}
|
|
1073
|
+
if (ts2.isTypeReferenceNode(node)) {
|
|
1074
|
+
const name = typeRefName(node);
|
|
1075
|
+
const args = node.typeArguments ?? [];
|
|
1076
|
+
if (name === "Array" || name === "ReadonlyArray" || name === "readonly") {
|
|
1077
|
+
const el = args[0] ? typeToDsl(args[0], depth + 1) : "any()";
|
|
1078
|
+
return `array(${el ?? "any()"})`;
|
|
1079
|
+
}
|
|
1080
|
+
if (name === "Promise") {
|
|
1081
|
+
return args[0] ? typeToDsl(args[0], depth + 1) : "any()";
|
|
1082
|
+
}
|
|
1083
|
+
if (name === "Record") {
|
|
1084
|
+
const val = args[1] ? typeToDsl(args[1], depth + 1) : "any()";
|
|
1085
|
+
return `shape({}) /* open Record \u2192 ${val ?? "any()"} */`;
|
|
1086
|
+
}
|
|
1087
|
+
if (name === "Partial" && args[0]) {
|
|
1088
|
+
const inner = typeToDsl(args[0], depth + 1);
|
|
1089
|
+
return inner ?? "any()";
|
|
1090
|
+
}
|
|
1091
|
+
return void 0;
|
|
1092
|
+
}
|
|
1093
|
+
if (ts2.isExpressionWithTypeArguments(node)) {
|
|
1094
|
+
return void 0;
|
|
1095
|
+
}
|
|
1096
|
+
return void 0;
|
|
1097
|
+
}
|
|
1098
|
+
function fnSignatureDsl(params, ret, depth) {
|
|
1099
|
+
const fields = [];
|
|
1100
|
+
const notes = [];
|
|
1101
|
+
params.forEach((p, i) => {
|
|
1102
|
+
let name;
|
|
1103
|
+
if (p.name && ts2.isIdentifier(p.name)) name = p.name.text;
|
|
1104
|
+
else name = `p${i}`;
|
|
1105
|
+
if (p.dotDotDotToken) {
|
|
1106
|
+
notes.push(`${name}: rest \u2014 array()`);
|
|
1107
|
+
fields.push(`${name}: array(any())`);
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
const optional = Boolean(p.questionToken) || p.initializer;
|
|
1111
|
+
let dsl = typeToDsl(p.type, depth + 1);
|
|
1112
|
+
if (dsl === void 0) {
|
|
1113
|
+
notes.push(`${name}: unmodeled type \u2192 any()`);
|
|
1114
|
+
dsl = "any()";
|
|
1115
|
+
}
|
|
1116
|
+
fields.push(optional ? `${name}: ${dsl}.optional()` : `${name}: ${dsl}`);
|
|
1117
|
+
});
|
|
1118
|
+
let retDsl = typeToDsl(ret, depth + 1);
|
|
1119
|
+
if (retDsl === void 0) {
|
|
1120
|
+
notes.push(`returns: unmodeled \u2192 any()`);
|
|
1121
|
+
retDsl = "any()";
|
|
1122
|
+
}
|
|
1123
|
+
const obj = fields.length === 0 ? "{}" : `{ ${fields.join(", ")} }`;
|
|
1124
|
+
return `fn(${obj}, ${retDsl})`;
|
|
1125
|
+
}
|
|
1126
|
+
function pickName(decl) {
|
|
1127
|
+
return decl.name && ts2.isIdentifier(decl.name) ? decl.name.text : void 0;
|
|
1128
|
+
}
|
|
1129
|
+
function collectExports(sourceFile) {
|
|
1130
|
+
const out = [];
|
|
1131
|
+
const push = (e) => {
|
|
1132
|
+
out.push(e);
|
|
1133
|
+
};
|
|
1134
|
+
for (const stmt of sourceFile.statements) {
|
|
1135
|
+
if ((ts2.isFunctionDeclaration(stmt) || ts2.isMethodSignature(stmt)) && stmt.modifiers?.some((m) => m.kind === ts2.SyntaxKind.ExportKeyword)) {
|
|
1136
|
+
const name = pickName(stmt);
|
|
1137
|
+
if (!name) continue;
|
|
1138
|
+
const fn = stmt;
|
|
1139
|
+
const dsl = fnSignatureDsl(fn.parameters, fn.type, 0);
|
|
1140
|
+
push({ name, dsl, kind: "function" });
|
|
1141
|
+
continue;
|
|
1142
|
+
}
|
|
1143
|
+
if (ts2.isFunctionDeclaration(stmt) && pickName(stmt)) {
|
|
1144
|
+
const isExport = stmt.modifiers?.some((m) => m.kind === ts2.SyntaxKind.ExportKeyword) ?? false;
|
|
1145
|
+
if (!isExport) continue;
|
|
1146
|
+
const name = pickName(stmt);
|
|
1147
|
+
push({ name, dsl: fnSignatureDsl(stmt.parameters, stmt.type, 0), kind: "function" });
|
|
1148
|
+
continue;
|
|
1149
|
+
}
|
|
1150
|
+
if (ts2.isVariableStatement(stmt)) {
|
|
1151
|
+
const isExport = stmt.modifiers?.some((m) => m.kind === ts2.SyntaxKind.ExportKeyword) ?? false;
|
|
1152
|
+
if (!isExport) continue;
|
|
1153
|
+
for (const d of stmt.declarationList.declarations) {
|
|
1154
|
+
const name = pickName(d);
|
|
1155
|
+
if (!name) continue;
|
|
1156
|
+
const dsl = typeToDsl(d.type, 0);
|
|
1157
|
+
if (dsl === void 0) {
|
|
1158
|
+
push({ name, note: "unmodeled type", kind: "value" });
|
|
1159
|
+
} else if (dsl.startsWith("fn(")) {
|
|
1160
|
+
push({ name, dsl, kind: "function" });
|
|
1161
|
+
} else {
|
|
1162
|
+
push({ name, dsl, kind: "value" });
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
continue;
|
|
1166
|
+
}
|
|
1167
|
+
if (ts2.isClassDeclaration(stmt)) {
|
|
1168
|
+
const isExport = stmt.modifiers?.some((m) => m.kind === ts2.SyntaxKind.ExportKeyword) ?? false;
|
|
1169
|
+
if (!isExport) continue;
|
|
1170
|
+
const name = pickName(stmt);
|
|
1171
|
+
if (!name) {
|
|
1172
|
+
push({ name: "default", note: "anonymous class", kind: "class" });
|
|
1173
|
+
continue;
|
|
1174
|
+
}
|
|
1175
|
+
let anyMethod = false;
|
|
1176
|
+
for (const m of stmt.members) {
|
|
1177
|
+
if (ts2.isMethodDeclaration(m) || ts2.isMethodSignature(m)) {
|
|
1178
|
+
const mname = pickName(m);
|
|
1179
|
+
if (!mname) continue;
|
|
1180
|
+
anyMethod = true;
|
|
1181
|
+
push({
|
|
1182
|
+
name: `${name}.${mname}`,
|
|
1183
|
+
dsl: fnSignatureDsl(m.parameters, m.type, 0),
|
|
1184
|
+
kind: "function"
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
if (!anyMethod) {
|
|
1189
|
+
push({ name, note: "class without public methods projected", kind: "class" });
|
|
1190
|
+
}
|
|
1191
|
+
continue;
|
|
1192
|
+
}
|
|
1193
|
+
if (ts2.isExportAssignment(stmt)) {
|
|
1194
|
+
push({ name: "default", note: "export assignment \u2014 bind via sidecar default", kind: "other" });
|
|
1195
|
+
continue;
|
|
1196
|
+
}
|
|
1197
|
+
if (ts2.isInterfaceDeclaration(stmt) || ts2.isTypeAliasDeclaration(stmt)) {
|
|
1198
|
+
continue;
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
return out;
|
|
1202
|
+
}
|
|
1203
|
+
function draftExportName(name) {
|
|
1204
|
+
return name.includes(".") ? name.replace(/\./g, "_") : name;
|
|
1205
|
+
}
|
|
1206
|
+
function formatDtsContractDraft(label, files, exports) {
|
|
1207
|
+
const draftable = exports.filter((e) => e.dsl !== void 0);
|
|
1208
|
+
const used = /* @__PURE__ */ new Set(["fn"]);
|
|
1209
|
+
for (const e of draftable) {
|
|
1210
|
+
for (const b of BUILDERS) {
|
|
1211
|
+
if (new RegExp(`\\b${b}\\b`).test(e.dsl)) used.add(b);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
if (draftable.some((e) => /\bany\s*\(/.test(e.dsl))) used.add("any");
|
|
1215
|
+
const importList = BUILDERS.filter((b) => used.has(b)).join(", ");
|
|
1216
|
+
const lines = [
|
|
1217
|
+
"// @nudo:draft",
|
|
1218
|
+
`// Generated by \`nudo contract --from-dts\` from ${label}`,
|
|
1219
|
+
`// Sources: ${files.map((f) => basename(f)).join(", ")}`,
|
|
1220
|
+
"//",
|
|
1221
|
+
"// Reverse-engineered from TypeScript declarations \u2014 NOT a sidecar contract.",
|
|
1222
|
+
"// This file is never loaded for check. Review each export, then copy it",
|
|
1223
|
+
"// into <target>.nudo.js to accept (that is when obligations go live).",
|
|
1224
|
+
"//",
|
|
1225
|
+
"// After accept: strengthen with Pred builders (number().gt(0) \u2026) \u2014",
|
|
1226
|
+
"// dts cannot express algebraic implications (x>0 \u21D2 x+1>1).",
|
|
1227
|
+
"",
|
|
1228
|
+
`import { ${importList} } from "@nudojs/core";`,
|
|
1229
|
+
""
|
|
1230
|
+
];
|
|
1231
|
+
if (draftable.length === 0) {
|
|
1232
|
+
lines.push("// (no projectable exports)");
|
|
1233
|
+
lines.push("");
|
|
1234
|
+
}
|
|
1235
|
+
for (const e of exports) {
|
|
1236
|
+
if (e.dsl === void 0) {
|
|
1237
|
+
lines.push(`// ${e.name} \u2014 skipped (${e.note ?? "unmodeled"})`);
|
|
1238
|
+
continue;
|
|
1239
|
+
}
|
|
1240
|
+
if (e.note) lines.push(`// ${e.name} \u2014 ${e.note}`);
|
|
1241
|
+
else lines.push(`// ${e.name} \u2014 from TypeScript`);
|
|
1242
|
+
lines.push(`export const ${draftExportName(e.name)} = ${e.dsl};`);
|
|
1243
|
+
if (e.name.includes(".")) {
|
|
1244
|
+
lines.push(`// sidecar key may also be written as \`${e.name}\``);
|
|
1245
|
+
}
|
|
1246
|
+
lines.push("");
|
|
1247
|
+
}
|
|
1248
|
+
return lines.join("\n");
|
|
1249
|
+
}
|
|
1250
|
+
function readDtsFiles(paths, maxFiles = 16) {
|
|
1251
|
+
const out = [];
|
|
1252
|
+
const walk = (dir, depth) => {
|
|
1253
|
+
if (depth > 6 || out.length >= maxFiles * 3) return;
|
|
1254
|
+
let entries;
|
|
1255
|
+
try {
|
|
1256
|
+
entries = readdirSync(dir);
|
|
1257
|
+
} catch {
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
for (const name of entries) {
|
|
1261
|
+
if (name === "node_modules" || name === "test" || name === "__tests__") continue;
|
|
1262
|
+
const p = join(dir, name);
|
|
1263
|
+
let st;
|
|
1264
|
+
try {
|
|
1265
|
+
st = statSync2(p);
|
|
1266
|
+
} catch {
|
|
1267
|
+
continue;
|
|
1268
|
+
}
|
|
1269
|
+
if (st.isDirectory()) walk(p, depth + 1);
|
|
1270
|
+
else if (name.endsWith(".d.ts") && !name.endsWith(".d.ts.map")) out.push(p);
|
|
1271
|
+
}
|
|
1272
|
+
};
|
|
1273
|
+
for (const p of paths) {
|
|
1274
|
+
if (!existsSync(p)) continue;
|
|
1275
|
+
const st = statSync2(p);
|
|
1276
|
+
if (st.isDirectory()) walk(p, 0);
|
|
1277
|
+
else if (/\.d\.ts$/.test(p) || /\.(m)?ts$/.test(p)) {
|
|
1278
|
+
if (!p.endsWith(".d.ts.map")) out.push(p);
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
return out.slice(0, maxFiles);
|
|
1282
|
+
}
|
|
1283
|
+
function dtsToContractDraft(dtsFiles, label) {
|
|
1284
|
+
const files = dtsFiles.filter((f) => existsSync(f));
|
|
1285
|
+
const exports = [];
|
|
1286
|
+
let skippedFiles = 0;
|
|
1287
|
+
for (const file of files) {
|
|
1288
|
+
let content;
|
|
1289
|
+
try {
|
|
1290
|
+
content = readFileSync2(file, "utf8");
|
|
1291
|
+
} catch {
|
|
1292
|
+
skippedFiles++;
|
|
1293
|
+
continue;
|
|
1294
|
+
}
|
|
1295
|
+
const sf = ts2.createSourceFile(file, content, ts2.ScriptTarget.Latest, true);
|
|
1296
|
+
for (const e of collectExports(sf)) {
|
|
1297
|
+
if (exports.some((x) => x.name === e.name && x.dsl)) continue;
|
|
1298
|
+
exports.push(e);
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
const name = label ?? (files[0] ? basename(files[0]).replace(/\.d\.ts$/, "") : "dts");
|
|
1302
|
+
const draftSource = formatDtsContractDraft(name, files, exports);
|
|
1303
|
+
return {
|
|
1304
|
+
label: name,
|
|
1305
|
+
files,
|
|
1306
|
+
exports,
|
|
1307
|
+
draftSource,
|
|
1308
|
+
stats: {
|
|
1309
|
+
files: files.length - skippedFiles,
|
|
1310
|
+
exports: exports.filter((e) => e.dsl !== void 0).length,
|
|
1311
|
+
skipped: exports.filter((e) => e.dsl === void 0).length + skippedFiles
|
|
1312
|
+
}
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
function dtsPathToContractDraft(path, label) {
|
|
1316
|
+
const files = readDtsFiles([path]);
|
|
1317
|
+
return dtsToContractDraft(files, label ?? basename(path).replace(/\.d\.ts$/, ""));
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
// src/harvest-package.ts
|
|
1321
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, statSync as statSync3, readFileSync as readFileSync3 } from "fs";
|
|
1322
|
+
import { join as join2, resolve, dirname as dirname2, basename as basename2 } from "path";
|
|
1323
|
+
import { formatShape as formatShape2 } from "@nudojs/core";
|
|
1324
|
+
function resolvePackageRoot(pkg, fromDir = process.cwd()) {
|
|
1325
|
+
let dir = resolve(fromDir);
|
|
1326
|
+
const bare = pkg.replace(/^@/, "").replace(/\//g, "__");
|
|
1327
|
+
for (let i = 0; i < 8; i++) {
|
|
1328
|
+
const typesPath = join2(dir, "node_modules", "@types", bare);
|
|
1329
|
+
if (existsSync2(typesPath)) return typesPath;
|
|
1330
|
+
const pkgPath = join2(dir, "node_modules", pkg);
|
|
1331
|
+
if (existsSync2(pkgPath)) return pkgPath;
|
|
1332
|
+
const parent = resolve(dir, "..");
|
|
1333
|
+
if (parent === dir) break;
|
|
1334
|
+
dir = parent;
|
|
1335
|
+
}
|
|
1336
|
+
return void 0;
|
|
1337
|
+
}
|
|
1338
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "test", "tests", "docs", "doc", "examples", "__tests__"]);
|
|
1339
|
+
var MAX_DTS_BYTES = 15e5;
|
|
1340
|
+
function dtsPriority(p, root) {
|
|
1341
|
+
const base = basename2(p);
|
|
1342
|
+
const rel = p.slice(root.length);
|
|
1343
|
+
if (base === "index.d.ts") return 0;
|
|
1344
|
+
if (base === "index.d.mts") return 1;
|
|
1345
|
+
if (rel.includes("/types/") && base === "index.d.ts") return 2;
|
|
1346
|
+
if (base.endsWith(".d.ts") && !base.includes(".")) return 3;
|
|
1347
|
+
return 10;
|
|
1348
|
+
}
|
|
1349
|
+
function collectDtsFiles(root, maxFiles = 8) {
|
|
1350
|
+
const out = [];
|
|
1351
|
+
const walk = (dir) => {
|
|
1352
|
+
if (out.length >= maxFiles * 3) return;
|
|
1353
|
+
let entries;
|
|
1354
|
+
try {
|
|
1355
|
+
entries = readdirSync2(dir);
|
|
1356
|
+
} catch {
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
for (const name of entries) {
|
|
1360
|
+
if (out.length >= maxFiles * 3) return;
|
|
1361
|
+
if (SKIP_DIRS.has(name)) continue;
|
|
1362
|
+
const p = join2(dir, name);
|
|
1363
|
+
let st;
|
|
1364
|
+
try {
|
|
1365
|
+
st = statSync3(p);
|
|
1366
|
+
} catch {
|
|
1367
|
+
continue;
|
|
1368
|
+
}
|
|
1369
|
+
if (st.isDirectory()) walk(p);
|
|
1370
|
+
else if (name.endsWith(".d.ts") && !name.endsWith(".d.ts.map")) {
|
|
1371
|
+
if (st.size > MAX_DTS_BYTES) continue;
|
|
1372
|
+
out.push(p);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
};
|
|
1376
|
+
walk(root);
|
|
1377
|
+
out.sort((a, b) => dtsPriority(a, root) - dtsPriority(b, root) || a.length - b.length);
|
|
1378
|
+
return out.slice(0, maxFiles);
|
|
1379
|
+
}
|
|
1380
|
+
function entryDtsFromPackageJson(root) {
|
|
1381
|
+
const pj = join2(root, "package.json");
|
|
1382
|
+
if (!existsSync2(pj)) return void 0;
|
|
1383
|
+
try {
|
|
1384
|
+
const raw = JSON.parse(readFileSync3(pj, "utf8"));
|
|
1385
|
+
const entry = raw.types ?? raw.typings;
|
|
1386
|
+
if (typeof entry === "string" && entry.endsWith(".d.ts")) {
|
|
1387
|
+
const p = resolve(root, entry);
|
|
1388
|
+
if (existsSync2(p)) {
|
|
1389
|
+
const st = statSync3(p);
|
|
1390
|
+
if (st.size <= MAX_DTS_BYTES) return p;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
} catch {
|
|
1394
|
+
}
|
|
1395
|
+
return void 0;
|
|
1396
|
+
}
|
|
1397
|
+
function collectDtsFromEntry(entry, maxFiles = 200) {
|
|
1398
|
+
const files = [];
|
|
1399
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1400
|
+
const queue = [entry];
|
|
1401
|
+
const REFERENCE_PATH_REGEX = /<reference\s+path=["']([^"']+)["']\s*\/>/g;
|
|
1402
|
+
const RELATIVE_FROM_REGEX = /\bfrom\s+["'](\.[^"']+)["']/g;
|
|
1403
|
+
while (queue.length > 0 && files.length < maxFiles) {
|
|
1404
|
+
const current = queue.shift();
|
|
1405
|
+
if (seen.has(current) || !existsSync2(current)) continue;
|
|
1406
|
+
seen.add(current);
|
|
1407
|
+
if (!current.endsWith(".d.ts")) continue;
|
|
1408
|
+
files.push(current);
|
|
1409
|
+
let text;
|
|
1410
|
+
try {
|
|
1411
|
+
text = readFileSync3(current, "utf-8");
|
|
1412
|
+
} catch {
|
|
1413
|
+
continue;
|
|
1414
|
+
}
|
|
1415
|
+
const dir = dirname2(current);
|
|
1416
|
+
for (const match of text.matchAll(REFERENCE_PATH_REGEX)) {
|
|
1417
|
+
queue.push(resolve(dir, match[1]));
|
|
1418
|
+
}
|
|
1419
|
+
for (const match of text.matchAll(RELATIVE_FROM_REGEX)) {
|
|
1420
|
+
const base = resolve(dir, match[1]);
|
|
1421
|
+
for (const candidate of [`${base}.d.ts`, join2(base, "index.d.ts")]) {
|
|
1422
|
+
if (existsSync2(candidate)) {
|
|
1423
|
+
queue.push(candidate);
|
|
1424
|
+
break;
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
return files;
|
|
1430
|
+
}
|
|
1431
|
+
function harvestPackage(pkg, fromDir, maxFiles = 24) {
|
|
1432
|
+
const root = resolvePackageRoot(pkg, fromDir);
|
|
1433
|
+
if (!root) return { error: `package not found: ${pkg}` };
|
|
1434
|
+
const entry = entryDtsFromPackageJson(root) ?? join2(root, "index.d.ts");
|
|
1435
|
+
let dtsFiles = existsSync2(entry) ? collectDtsFromEntry(entry, maxFiles) : [];
|
|
1436
|
+
if (dtsFiles.length === 0) {
|
|
1437
|
+
const collected = collectDtsFiles(root, maxFiles);
|
|
1438
|
+
dtsFiles = entry && existsSync2(entry) && !collected.includes(entry) ? [entry, ...collected.filter((f) => f !== entry)].slice(0, maxFiles) : collected;
|
|
1439
|
+
}
|
|
1440
|
+
if (dtsFiles.length === 0) {
|
|
1441
|
+
return { error: `no .d.ts under ${root}` };
|
|
1442
|
+
}
|
|
1443
|
+
const env = harvestDts(dtsFiles);
|
|
1444
|
+
return { pkg, root, dtsFiles, env };
|
|
1445
|
+
}
|
|
1446
|
+
function formatHarvestSummary(h) {
|
|
1447
|
+
const lines = [];
|
|
1448
|
+
lines.push(`${h.pkg}: ${h.dtsFiles.length} d.ts, ${h.env.stats.symbols} symbols`);
|
|
1449
|
+
const mods = Object.keys(h.env.modules);
|
|
1450
|
+
for (const m of mods.slice(0, 5)) {
|
|
1451
|
+
const exports = h.env.modules[m];
|
|
1452
|
+
const names = Object.keys(exports).slice(0, 12);
|
|
1453
|
+
lines.push(` module "${m}": ${names.join(", ")}${Object.keys(exports).length > 12 ? "\u2026" : ""}`);
|
|
1454
|
+
}
|
|
1455
|
+
if (mods.length > 5) lines.push(` \u2026 ${mods.length - 5} more modules`);
|
|
1456
|
+
return lines.join("\n");
|
|
1457
|
+
}
|
|
1458
|
+
function lookupHarvested(h, moduleName, exportName) {
|
|
1459
|
+
const mod = h.env.modules[moduleName];
|
|
1460
|
+
const a = mod?.[exportName] ?? h.env.globals[exportName];
|
|
1461
|
+
if (!a) return void 0;
|
|
1462
|
+
return formatShape2(a);
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
// src/harvest-auto.ts
|
|
1466
|
+
import { parse } from "@nudojs/parser";
|
|
1467
|
+
|
|
1468
|
+
// src/collect-specs.ts
|
|
1469
|
+
import { foldStaticStringExpr } from "@nudojs/core";
|
|
1470
|
+
function collectDependencySpecs(ast) {
|
|
1471
|
+
const specs = [];
|
|
1472
|
+
for (const stmt of ast.program.body) {
|
|
1473
|
+
if (stmt.type === "ImportDeclaration") {
|
|
1474
|
+
specs.push(stmt.source.value);
|
|
1475
|
+
}
|
|
1476
|
+
if (stmt.type === "ExportNamedDeclaration" && stmt.source) {
|
|
1477
|
+
specs.push(stmt.source.value);
|
|
1478
|
+
}
|
|
1479
|
+
if (stmt.type === "ExportAllDeclaration" && stmt.source) {
|
|
1480
|
+
specs.push(stmt.source.value);
|
|
1481
|
+
}
|
|
1482
|
+
collectRequires(stmt, specs);
|
|
1483
|
+
}
|
|
1484
|
+
return specs;
|
|
1485
|
+
}
|
|
1486
|
+
function collectRequires(node, out) {
|
|
1487
|
+
const visit = (n) => {
|
|
1488
|
+
if (!n || typeof n !== "object") return;
|
|
1489
|
+
const obj = n;
|
|
1490
|
+
if (obj.type === "CallExpression" && obj.callee) {
|
|
1491
|
+
const c = obj.callee;
|
|
1492
|
+
let spec;
|
|
1493
|
+
if (c.type === "Identifier" && c.name === "require") {
|
|
1494
|
+
spec = foldStaticStringExpr(obj.arguments?.[0]);
|
|
1495
|
+
} else if (c.type === "MemberExpression" && !c.computed && c.object?.type === "Identifier" && c.object.name === "require" && c.property?.type === "Identifier" && c.property.name === "resolve") {
|
|
1496
|
+
spec = foldStaticStringExpr(obj.arguments?.[0]);
|
|
1497
|
+
}
|
|
1498
|
+
if (spec !== void 0) out.push(spec);
|
|
1499
|
+
}
|
|
1500
|
+
for (const key of Object.keys(obj)) {
|
|
1501
|
+
if (key === "loc" || key === "start" || key === "end") continue;
|
|
1502
|
+
const v = obj[key];
|
|
1503
|
+
if (Array.isArray(v)) v.forEach(visit);
|
|
1504
|
+
else if (v && typeof v === "object") visit(v);
|
|
1505
|
+
}
|
|
1506
|
+
};
|
|
1507
|
+
visit(node);
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
// src/harvest-disk.ts
|
|
1511
|
+
import { createHash as createHash2 } from "crypto";
|
|
1512
|
+
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
|
|
1513
|
+
import { homedir } from "os";
|
|
1514
|
+
import { join as join3, dirname as dirname3 } from "path";
|
|
1515
|
+
|
|
1516
|
+
// src/harvest-json.ts
|
|
1517
|
+
import { createHash } from "crypto";
|
|
1518
|
+
import {
|
|
1519
|
+
abs,
|
|
1520
|
+
confJoin,
|
|
1521
|
+
relationFn as relationFn2,
|
|
1522
|
+
getFnImpl as getFnImpl2
|
|
1523
|
+
} from "@nudojs/core";
|
|
1524
|
+
var HARVEST_DISK_ABI = "nudo-harvest-disk-v7";
|
|
1525
|
+
function sha256Hex(data) {
|
|
1526
|
+
return createHash("sha256").update(data).digest("hex");
|
|
1527
|
+
}
|
|
1528
|
+
function absToHarvestSig(a) {
|
|
1529
|
+
if (a.term?.op === "var") {
|
|
1530
|
+
return { k: "tvar", name: a.term.id };
|
|
1531
|
+
}
|
|
1532
|
+
const s = a.shape;
|
|
1533
|
+
switch (s.k) {
|
|
1534
|
+
case "prim":
|
|
1535
|
+
return { k: "prim", type: s.type };
|
|
1536
|
+
case "unknown":
|
|
1537
|
+
return { k: "unknown" };
|
|
1538
|
+
case "any":
|
|
1539
|
+
return { k: "any" };
|
|
1540
|
+
case "never":
|
|
1541
|
+
return { k: "never" };
|
|
1542
|
+
case "arr":
|
|
1543
|
+
return { k: "arr", element: absToHarvestSig(s.element) };
|
|
1544
|
+
case "tuple":
|
|
1545
|
+
return { k: "tuple", elements: s.elements.map(absToHarvestSig) };
|
|
1546
|
+
case "obj": {
|
|
1547
|
+
const slots = {};
|
|
1548
|
+
for (const [key, slot] of Object.entries(s.slots)) {
|
|
1549
|
+
slots[key] = {
|
|
1550
|
+
v: absToHarvestSig(slot.value),
|
|
1551
|
+
...slot.optional ? { opt: true } : {}
|
|
1552
|
+
};
|
|
1553
|
+
}
|
|
1554
|
+
return { k: "obj", slots };
|
|
1555
|
+
}
|
|
1556
|
+
case "fn": {
|
|
1557
|
+
const rel = getFnImpl2(a)?.relation;
|
|
1558
|
+
const pts = rel?.paramTypes ?? s.paramTypes ?? s.params.map(() => ({ shape: { k: "unknown" }, conf: "mock" }));
|
|
1559
|
+
const paramTypes = pts.map(absToHarvestSig);
|
|
1560
|
+
const retSrc = rel?.returnType ?? s.returnType ?? { shape: { k: "unknown" }, conf: "mock" };
|
|
1561
|
+
return {
|
|
1562
|
+
k: "fn",
|
|
1563
|
+
params: s.params,
|
|
1564
|
+
paramTypes,
|
|
1565
|
+
returns: absToHarvestSig(retSrc)
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
case "sum":
|
|
1569
|
+
return { k: "sum", members: s.members.map(absToHarvestSig) };
|
|
1570
|
+
case "eff":
|
|
1571
|
+
return {
|
|
1572
|
+
k: s.eff === "generator" ? "generator" : "promise",
|
|
1573
|
+
value: absToHarvestSig(s.inner)
|
|
1574
|
+
};
|
|
1575
|
+
case "brand":
|
|
1576
|
+
return { k: "brand", name: s.name, shape: absToHarvestSig(s.shape) };
|
|
1577
|
+
default:
|
|
1578
|
+
return { k: "unknown" };
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
function harvestSigToAbs(sig) {
|
|
1582
|
+
const mark = (a) => {
|
|
1583
|
+
a.conf = confJoin(a.conf, "mock");
|
|
1584
|
+
return a;
|
|
1585
|
+
};
|
|
1586
|
+
switch (sig.k) {
|
|
1587
|
+
case "prim":
|
|
1588
|
+
return mark(
|
|
1589
|
+
abs(
|
|
1590
|
+
{ k: "prim", type: sig.type },
|
|
1591
|
+
void 0,
|
|
1592
|
+
void 0,
|
|
1593
|
+
"mock"
|
|
1594
|
+
)
|
|
1595
|
+
);
|
|
1596
|
+
case "unknown":
|
|
1597
|
+
return mark(abs({ k: "unknown" }, void 0, void 0, "mock"));
|
|
1598
|
+
case "any":
|
|
1599
|
+
return mark(abs({ k: "any" }, void 0, void 0, "mock"));
|
|
1600
|
+
case "never":
|
|
1601
|
+
return mark(abs({ k: "never" }, void 0, void 0, "mock"));
|
|
1602
|
+
case "lit": {
|
|
1603
|
+
const t = typeof sig.value === "number" ? "number" : typeof sig.value === "string" ? "string" : typeof sig.value === "boolean" ? "boolean" : typeof sig.value === "bigint" ? "bigint" : null;
|
|
1604
|
+
if (sig.value === null || t === null) {
|
|
1605
|
+
return mark(
|
|
1606
|
+
abs({ k: "unknown" }, { op: "lit", value: sig.value }, void 0, "mock")
|
|
1607
|
+
);
|
|
1608
|
+
}
|
|
1609
|
+
return mark(
|
|
1610
|
+
abs(
|
|
1611
|
+
{ k: "prim", type: t },
|
|
1612
|
+
{ op: "lit", value: sig.value },
|
|
1613
|
+
void 0,
|
|
1614
|
+
"mock"
|
|
1615
|
+
)
|
|
1616
|
+
);
|
|
1617
|
+
}
|
|
1618
|
+
case "arr":
|
|
1619
|
+
return mark(abs({ k: "arr", element: harvestSigToAbs(sig.element) }, void 0, void 0, "mock"));
|
|
1620
|
+
case "tuple":
|
|
1621
|
+
return mark(
|
|
1622
|
+
abs({ k: "tuple", elements: sig.elements.map(harvestSigToAbs) }, void 0, void 0, "mock")
|
|
1623
|
+
);
|
|
1624
|
+
case "obj": {
|
|
1625
|
+
const slots = {};
|
|
1626
|
+
for (const [key, slot] of Object.entries(sig.slots)) {
|
|
1627
|
+
slots[key] = {
|
|
1628
|
+
value: harvestSigToAbs(slot.v),
|
|
1629
|
+
...slot.opt ? { optional: true } : {}
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1632
|
+
return mark(abs({ k: "obj", slots }, void 0, void 0, "mock"));
|
|
1633
|
+
}
|
|
1634
|
+
case "tvar":
|
|
1635
|
+
return mark(abs({ k: "any" }, { op: "var", id: sig.name }, void 0, "mock"));
|
|
1636
|
+
case "fn": {
|
|
1637
|
+
const paramTypes = sig.paramTypes.map(harvestSigToAbs);
|
|
1638
|
+
const returnType = harvestSigToAbs(sig.returns);
|
|
1639
|
+
return mark(
|
|
1640
|
+
relationFn2(paramTypes, returnType, {
|
|
1641
|
+
params: sig.params,
|
|
1642
|
+
conf: "mock"
|
|
1643
|
+
})
|
|
1644
|
+
);
|
|
1645
|
+
}
|
|
1646
|
+
case "sum":
|
|
1647
|
+
return mark(
|
|
1648
|
+
abs({ k: "sum", members: sig.members.map(harvestSigToAbs) }, void 0, void 0, "mock")
|
|
1649
|
+
);
|
|
1650
|
+
case "promise":
|
|
1651
|
+
case "generator":
|
|
1652
|
+
return mark(
|
|
1653
|
+
abs(
|
|
1654
|
+
{
|
|
1655
|
+
k: "eff",
|
|
1656
|
+
eff: sig.k === "generator" ? "generator" : "promise",
|
|
1657
|
+
inner: harvestSigToAbs(sig.value)
|
|
1658
|
+
},
|
|
1659
|
+
void 0,
|
|
1660
|
+
void 0,
|
|
1661
|
+
"mock"
|
|
1662
|
+
)
|
|
1663
|
+
);
|
|
1664
|
+
case "brand":
|
|
1665
|
+
return mark(
|
|
1666
|
+
abs(
|
|
1667
|
+
{ k: "brand", name: sig.name, shape: harvestSigToAbs(sig.shape) },
|
|
1668
|
+
void 0,
|
|
1669
|
+
void 0,
|
|
1670
|
+
"mock"
|
|
1671
|
+
)
|
|
1672
|
+
);
|
|
1673
|
+
default:
|
|
1674
|
+
return mark(abs({ k: "unknown" }, void 0, void 0, "mock"));
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
function serializeHarvestJson(pkg, env, meta) {
|
|
1678
|
+
const modules = {};
|
|
1679
|
+
for (const [mod, rec] of Object.entries(env.modules)) {
|
|
1680
|
+
const named = {};
|
|
1681
|
+
for (const [k, v] of Object.entries(rec)) named[k] = absToHarvestSig(v);
|
|
1682
|
+
modules[mod] = named;
|
|
1683
|
+
}
|
|
1684
|
+
const globals = {};
|
|
1685
|
+
for (const [k, v] of Object.entries(env.globals)) globals[k] = absToHarvestSig(v);
|
|
1686
|
+
return {
|
|
1687
|
+
v: 1,
|
|
1688
|
+
pkg,
|
|
1689
|
+
...meta.pkgVersion ? { pkgVersion: meta.pkgVersion } : {},
|
|
1690
|
+
knobs: { maxFiles: meta.maxFiles, abi: HARVEST_DISK_ABI },
|
|
1691
|
+
dtsHash: meta.dtsHash,
|
|
1692
|
+
modules,
|
|
1693
|
+
globals,
|
|
1694
|
+
stats: env.stats
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
function materializeHarvestJson(j) {
|
|
1698
|
+
if (!j || typeof j !== "object") return null;
|
|
1699
|
+
const h = j;
|
|
1700
|
+
if (h.v !== 1) return null;
|
|
1701
|
+
if (h.knobs?.abi !== HARVEST_DISK_ABI) return null;
|
|
1702
|
+
if (!h.modules || typeof h.modules !== "object") return null;
|
|
1703
|
+
try {
|
|
1704
|
+
const modules = {};
|
|
1705
|
+
for (const [mod, rec] of Object.entries(h.modules)) {
|
|
1706
|
+
const named = {};
|
|
1707
|
+
for (const [k, sig] of Object.entries(rec)) named[k] = harvestSigToAbs(sig);
|
|
1708
|
+
modules[mod] = named;
|
|
1709
|
+
}
|
|
1710
|
+
const globals = {};
|
|
1711
|
+
for (const [k, sig] of Object.entries(h.globals ?? {})) {
|
|
1712
|
+
globals[k] = harvestSigToAbs(sig);
|
|
1713
|
+
}
|
|
1714
|
+
return {
|
|
1715
|
+
globals,
|
|
1716
|
+
modules,
|
|
1717
|
+
stats: h.stats ?? { files: 0, symbols: 0, skipped: 0 }
|
|
1718
|
+
};
|
|
1719
|
+
} catch {
|
|
1720
|
+
return null;
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
function harvestCacheKey(pkg, meta) {
|
|
1724
|
+
const raw = [
|
|
1725
|
+
HARVEST_DISK_ABI,
|
|
1726
|
+
pkg,
|
|
1727
|
+
meta.pkgVersion ?? "-",
|
|
1728
|
+
String(meta.maxFiles),
|
|
1729
|
+
meta.dtsHash
|
|
1730
|
+
].join("\0");
|
|
1731
|
+
return sha256Hex(raw);
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
// src/harvest-disk.ts
|
|
1735
|
+
function depsCacheRoot() {
|
|
1736
|
+
const env = process.env.NUDO_DEPS_CACHE_DIR;
|
|
1737
|
+
if (env === "off" || env === "0") return void 0;
|
|
1738
|
+
if (env && env.length > 0) return env;
|
|
1739
|
+
try {
|
|
1740
|
+
return join3(homedir(), ".cache", "nudo", "deps");
|
|
1741
|
+
} catch {
|
|
1742
|
+
return void 0;
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
function readPkgVersion(root) {
|
|
1746
|
+
try {
|
|
1747
|
+
const raw = JSON.parse(readFileSync4(join3(root, "package.json"), "utf8"));
|
|
1748
|
+
return raw.version;
|
|
1749
|
+
} catch {
|
|
1750
|
+
return void 0;
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
function dtsClosureHash(files) {
|
|
1754
|
+
const h = createHash2("sha256");
|
|
1755
|
+
for (const f of [...files].sort()) {
|
|
1756
|
+
h.update(f.replace(/\\/g, "/"));
|
|
1757
|
+
try {
|
|
1758
|
+
h.update(readFileSync4(f));
|
|
1759
|
+
} catch {
|
|
1760
|
+
h.update("miss");
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
return h.digest("hex");
|
|
1764
|
+
}
|
|
1765
|
+
function diskPath(root, key) {
|
|
1766
|
+
return join3(root, key.slice(0, 2), `${key}.json`);
|
|
1767
|
+
}
|
|
1768
|
+
function readHarvestDisk(root, key) {
|
|
1769
|
+
try {
|
|
1770
|
+
const p = diskPath(root, key);
|
|
1771
|
+
if (!existsSync3(p)) return void 0;
|
|
1772
|
+
const parsed = JSON.parse(readFileSync4(p, "utf8"));
|
|
1773
|
+
if (parsed?.abi !== HARVEST_DISK_ABI) return void 0;
|
|
1774
|
+
return parsed.value;
|
|
1775
|
+
} catch {
|
|
1776
|
+
return void 0;
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
function writeHarvestDisk(root, key, value) {
|
|
1780
|
+
try {
|
|
1781
|
+
const p = diskPath(root, key);
|
|
1782
|
+
mkdirSync(dirname3(p), { recursive: true });
|
|
1783
|
+
writeFileSync(p, JSON.stringify({ abi: HARVEST_DISK_ABI, value }), "utf8");
|
|
1784
|
+
} catch {
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
function listPackageDts(pkg, fromDir, maxFiles) {
|
|
1788
|
+
const root = resolvePackageRoot(pkg, fromDir);
|
|
1789
|
+
if (!root) return null;
|
|
1790
|
+
const entry = (() => {
|
|
1791
|
+
try {
|
|
1792
|
+
const raw = JSON.parse(readFileSync4(join3(root, "package.json"), "utf8"));
|
|
1793
|
+
const e = raw.types ?? raw.typings;
|
|
1794
|
+
return typeof e === "string" && e.endsWith(".d.ts") ? join3(root, e) : void 0;
|
|
1795
|
+
} catch {
|
|
1796
|
+
return void 0;
|
|
1797
|
+
}
|
|
1798
|
+
})();
|
|
1799
|
+
let dtsFiles = entry && existsSync3(entry) ? collectDtsFromEntry(entry, Math.max(maxFiles, 24)) : [];
|
|
1800
|
+
if (dtsFiles.length === 0) {
|
|
1801
|
+
const collected = collectDtsFiles(root, maxFiles);
|
|
1802
|
+
dtsFiles = entry && existsSync3(entry) && !collected.includes(entry) ? [entry, ...collected.filter((f) => f !== entry)].slice(0, maxFiles) : collected;
|
|
1803
|
+
}
|
|
1804
|
+
if (dtsFiles.length === 0) return null;
|
|
1805
|
+
return { root, dtsFiles, pkgVersion: readPkgVersion(root) };
|
|
1806
|
+
}
|
|
1807
|
+
function harvestPackageWithDisk(pkg, fromDir, maxFiles = 24) {
|
|
1808
|
+
const cacheRoot = depsCacheRoot();
|
|
1809
|
+
const listed = listPackageDts(pkg, fromDir, maxFiles);
|
|
1810
|
+
if (!listed) return null;
|
|
1811
|
+
const key = harvestCacheKey(pkg, {
|
|
1812
|
+
dtsHash: dtsClosureHash(listed.dtsFiles),
|
|
1813
|
+
maxFiles,
|
|
1814
|
+
pkgVersion: listed.pkgVersion
|
|
1815
|
+
});
|
|
1816
|
+
if (cacheRoot) {
|
|
1817
|
+
const hit = readHarvestDisk(cacheRoot, key);
|
|
1818
|
+
if (hit) {
|
|
1819
|
+
const env = materializeHarvestJson(hit);
|
|
1820
|
+
if (env) {
|
|
1821
|
+
return { pkg, root: listed.root, dtsFiles: listed.dtsFiles, env };
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
let h = null;
|
|
1826
|
+
try {
|
|
1827
|
+
const raw = harvestPackage(pkg, fromDir, maxFiles);
|
|
1828
|
+
if (!("error" in raw)) h = raw;
|
|
1829
|
+
} catch {
|
|
1830
|
+
h = null;
|
|
1831
|
+
}
|
|
1832
|
+
if (!h) return null;
|
|
1833
|
+
if (cacheRoot) {
|
|
1834
|
+
writeHarvestDisk(
|
|
1835
|
+
cacheRoot,
|
|
1836
|
+
key,
|
|
1837
|
+
serializeHarvestJson(pkg, h.env, {
|
|
1838
|
+
dtsHash: dtsClosureHash(h.dtsFiles),
|
|
1839
|
+
maxFiles,
|
|
1840
|
+
pkgVersion: listed.pkgVersion
|
|
1841
|
+
})
|
|
1842
|
+
);
|
|
1843
|
+
}
|
|
1844
|
+
return h;
|
|
1845
|
+
}
|
|
1846
|
+
function loadHarvestEnvFromDisk(pkg, meta) {
|
|
1847
|
+
const root = depsCacheRoot();
|
|
1848
|
+
if (!root) return null;
|
|
1849
|
+
const key = harvestCacheKey(pkg, meta);
|
|
1850
|
+
const hit = readHarvestDisk(root, key);
|
|
1851
|
+
return hit ? materializeHarvestJson(hit) : null;
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
// src/lru-map.ts
|
|
1855
|
+
var BoundedLruMap = class {
|
|
1856
|
+
map = /* @__PURE__ */ new Map();
|
|
1857
|
+
max;
|
|
1858
|
+
constructor(max) {
|
|
1859
|
+
this.max = max;
|
|
1860
|
+
}
|
|
1861
|
+
get size() {
|
|
1862
|
+
return this.map.size;
|
|
1863
|
+
}
|
|
1864
|
+
/** 当前上限(可动态收紧;收紧时立刻按 LRU 压到新上限) */
|
|
1865
|
+
getMax() {
|
|
1866
|
+
return this.max;
|
|
1867
|
+
}
|
|
1868
|
+
setMax(max) {
|
|
1869
|
+
this.max = max;
|
|
1870
|
+
this.trim();
|
|
1871
|
+
}
|
|
1872
|
+
has(key) {
|
|
1873
|
+
return this.map.has(key);
|
|
1874
|
+
}
|
|
1875
|
+
get(key) {
|
|
1876
|
+
const hit = this.map.get(key);
|
|
1877
|
+
if (hit === void 0) return void 0;
|
|
1878
|
+
this.map.delete(key);
|
|
1879
|
+
this.map.set(key, hit);
|
|
1880
|
+
return hit;
|
|
1881
|
+
}
|
|
1882
|
+
/** 命中不改变 LRU 序(只读探测) */
|
|
1883
|
+
peek(key) {
|
|
1884
|
+
return this.map.get(key);
|
|
1885
|
+
}
|
|
1886
|
+
set(key, value) {
|
|
1887
|
+
if (this.max <= 0) return;
|
|
1888
|
+
while (this.map.size >= this.max && !this.map.has(key)) {
|
|
1889
|
+
const oldest = this.map.keys().next().value;
|
|
1890
|
+
if (oldest === void 0) break;
|
|
1891
|
+
this.map.delete(oldest);
|
|
1892
|
+
}
|
|
1893
|
+
this.map.delete(key);
|
|
1894
|
+
this.map.set(key, value);
|
|
1895
|
+
}
|
|
1896
|
+
delete(key) {
|
|
1897
|
+
return this.map.delete(key);
|
|
1898
|
+
}
|
|
1899
|
+
clear() {
|
|
1900
|
+
this.map.clear();
|
|
1901
|
+
}
|
|
1902
|
+
keys() {
|
|
1903
|
+
return this.map.keys();
|
|
1904
|
+
}
|
|
1905
|
+
values() {
|
|
1906
|
+
return this.map.values();
|
|
1907
|
+
}
|
|
1908
|
+
entries() {
|
|
1909
|
+
return this.map.entries();
|
|
1910
|
+
}
|
|
1911
|
+
/** 压到当前 max(调低上限时收内存) */
|
|
1912
|
+
trim() {
|
|
1913
|
+
while (this.map.size > this.max) {
|
|
1914
|
+
const oldest = this.map.keys().next().value;
|
|
1915
|
+
if (oldest === void 0) break;
|
|
1916
|
+
this.map.delete(oldest);
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
};
|
|
1920
|
+
|
|
1921
|
+
// src/harvest-auto.ts
|
|
1922
|
+
var NODE_BUILTINS = /* @__PURE__ */ new Set([
|
|
1923
|
+
"assert",
|
|
1924
|
+
"async_hooks",
|
|
1925
|
+
"buffer",
|
|
1926
|
+
"child_process",
|
|
1927
|
+
"cluster",
|
|
1928
|
+
"console",
|
|
1929
|
+
"constants",
|
|
1930
|
+
"crypto",
|
|
1931
|
+
"dgram",
|
|
1932
|
+
"diagnostics_channel",
|
|
1933
|
+
"dns",
|
|
1934
|
+
"domain",
|
|
1935
|
+
"events",
|
|
1936
|
+
"fs",
|
|
1937
|
+
"http",
|
|
1938
|
+
"http2",
|
|
1939
|
+
"https",
|
|
1940
|
+
"inspector",
|
|
1941
|
+
"module",
|
|
1942
|
+
"net",
|
|
1943
|
+
"os",
|
|
1944
|
+
"path",
|
|
1945
|
+
"perf_hooks",
|
|
1946
|
+
"process",
|
|
1947
|
+
"punycode",
|
|
1948
|
+
"querystring",
|
|
1949
|
+
"readline",
|
|
1950
|
+
"repl",
|
|
1951
|
+
"stream",
|
|
1952
|
+
"string_decoder",
|
|
1953
|
+
"timers",
|
|
1954
|
+
"tls",
|
|
1955
|
+
"trace_events",
|
|
1956
|
+
"tty",
|
|
1957
|
+
"url",
|
|
1958
|
+
"util",
|
|
1959
|
+
"v8",
|
|
1960
|
+
"vm",
|
|
1961
|
+
"wasi",
|
|
1962
|
+
"worker_threads",
|
|
1963
|
+
"zlib"
|
|
1964
|
+
]);
|
|
1965
|
+
function barePackageName(spec) {
|
|
1966
|
+
if (!spec) return void 0;
|
|
1967
|
+
if (spec.startsWith(".") || spec.startsWith("/") || spec.startsWith("node:")) return void 0;
|
|
1968
|
+
const parts = spec.split("/");
|
|
1969
|
+
if (spec.startsWith("@")) {
|
|
1970
|
+
return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : void 0;
|
|
1971
|
+
}
|
|
1972
|
+
const name = parts[0];
|
|
1973
|
+
if (NODE_BUILTINS.has(name)) return void 0;
|
|
1974
|
+
return name;
|
|
1975
|
+
}
|
|
1976
|
+
function collectBarePackages(source) {
|
|
1977
|
+
try {
|
|
1978
|
+
const ast = parse(source);
|
|
1979
|
+
const names = /* @__PURE__ */ new Set();
|
|
1980
|
+
for (const spec of collectDependencySpecs(ast)) {
|
|
1981
|
+
const pkg = barePackageName(spec);
|
|
1982
|
+
if (pkg) names.add(pkg);
|
|
1983
|
+
}
|
|
1984
|
+
return [...names];
|
|
1985
|
+
} catch {
|
|
1986
|
+
return [];
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
var HARVEST_CACHE_MAX = 128;
|
|
1990
|
+
var harvestCache = new BoundedLruMap(HARVEST_CACHE_MAX);
|
|
1991
|
+
function getHarvestCacheSize() {
|
|
1992
|
+
return harvestCache.size;
|
|
1993
|
+
}
|
|
1994
|
+
function harvestPackageCached(pkg, fromDir) {
|
|
1995
|
+
const key = `${fromDir}::${pkg}`;
|
|
1996
|
+
if (harvestCache.has(key)) return harvestCache.get(key);
|
|
1997
|
+
let result = null;
|
|
1998
|
+
try {
|
|
1999
|
+
result = harvestPackageWithDisk(pkg, fromDir);
|
|
2000
|
+
} catch {
|
|
2001
|
+
result = null;
|
|
2002
|
+
}
|
|
2003
|
+
harvestCache.set(key, result);
|
|
2004
|
+
return result;
|
|
2005
|
+
}
|
|
2006
|
+
function clearHarvestCache() {
|
|
2007
|
+
harvestCache.clear();
|
|
2008
|
+
}
|
|
2009
|
+
function autoHarvestModules(source, fromDir) {
|
|
2010
|
+
const packages = collectBarePackages(source);
|
|
2011
|
+
if (packages.length === 0) return {};
|
|
2012
|
+
const modules = {};
|
|
2013
|
+
for (const pkg of packages) {
|
|
2014
|
+
const h = harvestPackageCached(pkg, fromDir);
|
|
2015
|
+
if (!h) continue;
|
|
2016
|
+
for (const [mod, exports] of Object.entries(h.env.modules)) {
|
|
2017
|
+
modules[mod] = exports;
|
|
2018
|
+
if (!modules[pkg]) modules[pkg] = exports;
|
|
2019
|
+
const base = mod.replace(/^@types\//, "").replace(/\\/g, "/");
|
|
2020
|
+
if (base && !modules[base]) modules[base] = exports;
|
|
2021
|
+
}
|
|
2022
|
+
for (const [k, v] of Object.entries(h.env.globals)) {
|
|
2023
|
+
if (!modules[pkg]) modules[pkg] = {};
|
|
2024
|
+
if (modules[pkg][k] === void 0) modules[pkg][k] = v;
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
return modules;
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
// src/harvest-to-abs.ts
|
|
2031
|
+
import {
|
|
2032
|
+
confJoin as confJoin2,
|
|
2033
|
+
absFunction,
|
|
2034
|
+
getFnImpl as getFnImpl3,
|
|
2035
|
+
instantiateReturn
|
|
2036
|
+
} from "@nudojs/core";
|
|
2037
|
+
import { dirname as dirname4 } from "path";
|
|
2038
|
+
function markMockConf(a) {
|
|
2039
|
+
a.conf = confJoin2(a.conf, "mock");
|
|
2040
|
+
return a;
|
|
2041
|
+
}
|
|
2042
|
+
function harvestedValueToAbs(a) {
|
|
2043
|
+
if (a.shape.k === "fn") {
|
|
2044
|
+
const ret = a.shape.returnType ?? { shape: { k: "unknown" }, conf: "mock" };
|
|
2045
|
+
const relation = getFnImpl3(a)?.relation;
|
|
2046
|
+
const params = a.shape.params.length > 0 ? a.shape.params : a.shape.paramTypes?.map((_, i) => `_arg${i}`) ?? ["...args"];
|
|
2047
|
+
const dummyBody = {
|
|
2048
|
+
type: "BlockStatement",
|
|
2049
|
+
body: [],
|
|
2050
|
+
directives: []
|
|
2051
|
+
};
|
|
2052
|
+
return markMockConf(
|
|
2053
|
+
absFunction(params, {
|
|
2054
|
+
body: dummyBody,
|
|
2055
|
+
apply: (args) => {
|
|
2056
|
+
if (relation) {
|
|
2057
|
+
return markMockConf(instantiateReturn(a, args));
|
|
2058
|
+
}
|
|
2059
|
+
return markMockConf({ ...ret });
|
|
2060
|
+
},
|
|
2061
|
+
...relation ? { relation } : {}
|
|
2062
|
+
})
|
|
2063
|
+
);
|
|
2064
|
+
}
|
|
2065
|
+
return markMockConf({ ...a });
|
|
2066
|
+
}
|
|
2067
|
+
function harvestToAbsModules(pkg, fromDir) {
|
|
2068
|
+
const h = harvestPackageCached(pkg, fromDir);
|
|
2069
|
+
if (!h) return {};
|
|
2070
|
+
return packageHarvestToAbsModules(pkg, h);
|
|
2071
|
+
}
|
|
2072
|
+
function packageHarvestToAbsModules(pkg, h) {
|
|
2073
|
+
const out = {};
|
|
2074
|
+
const convertRecord = (rec) => {
|
|
2075
|
+
const named = {};
|
|
2076
|
+
for (const [k, v] of Object.entries(rec)) {
|
|
2077
|
+
named[k] = harvestedValueToAbs(v);
|
|
2078
|
+
}
|
|
2079
|
+
return named;
|
|
2080
|
+
};
|
|
2081
|
+
const register = (key, exports) => {
|
|
2082
|
+
if (!out[key]) out[key] = exports;
|
|
2083
|
+
};
|
|
2084
|
+
for (const [mod, rec] of Object.entries(h.env.modules)) {
|
|
2085
|
+
const named = convertRecord(rec);
|
|
2086
|
+
const exports = { named };
|
|
2087
|
+
const def = rec["default"];
|
|
2088
|
+
if (def) exports.default = harvestedValueToAbs(def);
|
|
2089
|
+
register(mod, exports);
|
|
2090
|
+
register(pkg, exports);
|
|
2091
|
+
const stripped = mod.replace(/^@types\//, "").replace(/\\/g, "/");
|
|
2092
|
+
if (stripped) register(stripped, exports);
|
|
2093
|
+
const base = stripped.split("/").filter(Boolean).pop();
|
|
2094
|
+
if (base && base !== pkg) register(base, exports);
|
|
2095
|
+
}
|
|
2096
|
+
const globalNamed = convertRecord(h.env.globals);
|
|
2097
|
+
if (Object.keys(globalNamed).length > 0) {
|
|
2098
|
+
const existing = out[pkg];
|
|
2099
|
+
if (existing) {
|
|
2100
|
+
out[pkg] = { named: { ...globalNamed, ...existing.named }, default: existing.default };
|
|
2101
|
+
} else {
|
|
2102
|
+
out[pkg] = { named: globalNamed };
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
return out;
|
|
2106
|
+
}
|
|
2107
|
+
function bareSpecToAbsModules(spec, fromFile) {
|
|
2108
|
+
if (!spec || spec.startsWith(".") || spec.startsWith("/") || spec.startsWith("node:")) {
|
|
2109
|
+
return void 0;
|
|
2110
|
+
}
|
|
2111
|
+
const parts = spec.split("/");
|
|
2112
|
+
const pkg = spec.startsWith("@") ? parts.length >= 2 ? `${parts[0]}/${parts[1]}` : void 0 : parts[0];
|
|
2113
|
+
if (!pkg) return void 0;
|
|
2114
|
+
const table = harvestToAbsModules(pkg, dirname4(fromFile));
|
|
2115
|
+
return table[spec] ?? table[pkg];
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
// src/harvest-node.ts
|
|
2119
|
+
import { existsSync as existsSync4, statSync as statSync4, readFileSync as readFileSync5 } from "fs";
|
|
2120
|
+
import { join as join4 } from "path";
|
|
2121
|
+
import { createEnvironment } from "@nudojs/core";
|
|
2122
|
+
import { defineEnv as defineNodeEnv } from "@nudojs/env/node";
|
|
2123
|
+
import { defineEnv as defineEsEnv } from "@nudojs/env/es";
|
|
2124
|
+
var HARVEST_NODE_DEFAULT_MAX_FILES = 12;
|
|
2125
|
+
var HARVEST_NODE_DEFAULT_MAX_MS = 2500;
|
|
2126
|
+
var NODE_HARVEST_CACHE_MAX = 32;
|
|
2127
|
+
var nodeHarvestCache = new BoundedLruMap(NODE_HARVEST_CACHE_MAX);
|
|
2128
|
+
function notFoundCacheKey(fromDir) {
|
|
2129
|
+
return `not-found|@types/node|${fromDir ?? ""}`;
|
|
2130
|
+
}
|
|
2131
|
+
function nodeHarvestCacheKey(root, maxFiles, maxMs) {
|
|
2132
|
+
let sig = "nostat";
|
|
2133
|
+
try {
|
|
2134
|
+
const st = statSync4(join4(root, "package.json"));
|
|
2135
|
+
sig = `${st.size}:${Math.floor(st.mtimeMs)}`;
|
|
2136
|
+
} catch {
|
|
2137
|
+
}
|
|
2138
|
+
return `${root}|${maxFiles}|${maxMs}|${sig}`;
|
|
2139
|
+
}
|
|
2140
|
+
function clearNodeHarvestCache() {
|
|
2141
|
+
nodeHarvestCache.clear();
|
|
2142
|
+
}
|
|
2143
|
+
function getNodeHarvestCacheSize() {
|
|
2144
|
+
return nodeHarvestCache.size;
|
|
2145
|
+
}
|
|
2146
|
+
function isHarvestNodeDisabled(env = process.env) {
|
|
2147
|
+
return env.NUDO_HARVEST_NODE === "off";
|
|
2148
|
+
}
|
|
2149
|
+
function readPkgVersion2(root) {
|
|
2150
|
+
try {
|
|
2151
|
+
const raw = JSON.parse(readFileSync5(join4(root, "package.json"), "utf8"));
|
|
2152
|
+
return raw.version;
|
|
2153
|
+
} catch {
|
|
2154
|
+
return void 0;
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
function handwrittenNodeEnv() {
|
|
2158
|
+
try {
|
|
2159
|
+
const globalEnv = createEnvironment();
|
|
2160
|
+
const allModules = {};
|
|
2161
|
+
const allGlobals = {};
|
|
2162
|
+
for (const def of [defineEsEnv(), defineNodeEnv()]) {
|
|
2163
|
+
for (const [key, value] of Object.entries(def.globals)) {
|
|
2164
|
+
allGlobals[key] = value;
|
|
2165
|
+
globalEnv.bind(key, value);
|
|
2166
|
+
}
|
|
2167
|
+
if (def.modules) {
|
|
2168
|
+
for (const [modName, exports] of Object.entries(def.modules)) {
|
|
2169
|
+
allModules[modName] = { ...allModules[modName], ...exports };
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
return {
|
|
2174
|
+
modules: allModules,
|
|
2175
|
+
globals: allGlobals,
|
|
2176
|
+
stats: {
|
|
2177
|
+
files: 0,
|
|
2178
|
+
symbols: Object.keys(allGlobals).length,
|
|
2179
|
+
skipped: 0
|
|
2180
|
+
}
|
|
2181
|
+
};
|
|
2182
|
+
} catch {
|
|
2183
|
+
return null;
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
function degradeToHandwritten(error, reason, cached = false) {
|
|
2187
|
+
const env = handwrittenNodeEnv();
|
|
2188
|
+
if (env) {
|
|
2189
|
+
return {
|
|
2190
|
+
ok: true,
|
|
2191
|
+
env,
|
|
2192
|
+
root: "@nudojs/env",
|
|
2193
|
+
files: 0,
|
|
2194
|
+
stats: env.stats,
|
|
2195
|
+
cached,
|
|
2196
|
+
degraded: true
|
|
2197
|
+
};
|
|
2198
|
+
}
|
|
2199
|
+
return { ok: false, error, reason, ...cached ? { cached: true } : {} };
|
|
2200
|
+
}
|
|
2201
|
+
function harvestNodeTypes(fromDir, maxFiles = HARVEST_NODE_DEFAULT_MAX_FILES, maxMs = HARVEST_NODE_DEFAULT_MAX_MS) {
|
|
2202
|
+
if (isHarvestNodeDisabled()) {
|
|
2203
|
+
return {
|
|
2204
|
+
ok: false,
|
|
2205
|
+
error: "harvest disabled via NUDO_HARVEST_NODE=off",
|
|
2206
|
+
reason: "disabled"
|
|
2207
|
+
};
|
|
2208
|
+
}
|
|
2209
|
+
const root = resolvePackageRoot("@types/node", fromDir) ?? resolvePackageRoot("node", fromDir);
|
|
2210
|
+
if (!root || !existsSync4(root)) {
|
|
2211
|
+
const missKey = notFoundCacheKey(fromDir);
|
|
2212
|
+
const miss = nodeHarvestCache.get(missKey);
|
|
2213
|
+
if (miss) {
|
|
2214
|
+
return miss.ok ? { ...miss, cached: true } : { ...miss, cached: true };
|
|
2215
|
+
}
|
|
2216
|
+
const result = degradeToHandwritten("@types/node not found", "not-found");
|
|
2217
|
+
nodeHarvestCache.set(missKey, result);
|
|
2218
|
+
return result;
|
|
2219
|
+
}
|
|
2220
|
+
const key = nodeHarvestCacheKey(root, maxFiles, maxMs);
|
|
2221
|
+
const hit = nodeHarvestCache.get(key);
|
|
2222
|
+
if (hit) {
|
|
2223
|
+
return hit.ok ? { ...hit, cached: true } : { ...hit, cached: true };
|
|
2224
|
+
}
|
|
2225
|
+
const dts = collectDtsFiles(root, maxFiles);
|
|
2226
|
+
if (dts.length === 0) {
|
|
2227
|
+
const result = degradeToHandwritten(`no .d.ts under ${root}`, "no-dts");
|
|
2228
|
+
nodeHarvestCache.set(key, result);
|
|
2229
|
+
return result;
|
|
2230
|
+
}
|
|
2231
|
+
const diskRoot = depsCacheRoot();
|
|
2232
|
+
const dtsHash = dtsClosureHash(dts);
|
|
2233
|
+
const pkgVersion = readPkgVersion2(root);
|
|
2234
|
+
const diskKey = harvestCacheKey("@types/node", {
|
|
2235
|
+
dtsHash,
|
|
2236
|
+
maxFiles,
|
|
2237
|
+
...pkgVersion !== void 0 ? { pkgVersion } : {}
|
|
2238
|
+
});
|
|
2239
|
+
if (diskRoot) {
|
|
2240
|
+
const diskHit = readHarvestDisk(diskRoot, diskKey);
|
|
2241
|
+
const env = diskHit ? materializeHarvestJson(diskHit) : null;
|
|
2242
|
+
if (env) {
|
|
2243
|
+
const stats = {
|
|
2244
|
+
files: env.stats.files,
|
|
2245
|
+
symbols: env.stats.symbols,
|
|
2246
|
+
skipped: env.stats.skipped
|
|
2247
|
+
};
|
|
2248
|
+
const result = {
|
|
2249
|
+
ok: true,
|
|
2250
|
+
env,
|
|
2251
|
+
root,
|
|
2252
|
+
files: stats.files,
|
|
2253
|
+
stats,
|
|
2254
|
+
cached: true
|
|
2255
|
+
};
|
|
2256
|
+
nodeHarvestCache.set(key, result);
|
|
2257
|
+
return result;
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
try {
|
|
2261
|
+
const env = harvestDts(dts, { maxMs });
|
|
2262
|
+
const stats = {
|
|
2263
|
+
files: env.stats.files,
|
|
2264
|
+
symbols: env.stats.symbols,
|
|
2265
|
+
skipped: env.stats.skipped
|
|
2266
|
+
};
|
|
2267
|
+
if (diskRoot) {
|
|
2268
|
+
writeHarvestDisk(
|
|
2269
|
+
diskRoot,
|
|
2270
|
+
diskKey,
|
|
2271
|
+
serializeHarvestJson("@types/node", env, {
|
|
2272
|
+
dtsHash,
|
|
2273
|
+
maxFiles,
|
|
2274
|
+
...pkgVersion !== void 0 ? { pkgVersion } : {}
|
|
2275
|
+
})
|
|
2276
|
+
);
|
|
2277
|
+
}
|
|
2278
|
+
const result = {
|
|
2279
|
+
ok: true,
|
|
2280
|
+
env,
|
|
2281
|
+
root,
|
|
2282
|
+
files: stats.files,
|
|
2283
|
+
stats,
|
|
2284
|
+
cached: false
|
|
2285
|
+
};
|
|
2286
|
+
nodeHarvestCache.set(key, result);
|
|
2287
|
+
return result;
|
|
2288
|
+
} catch (e) {
|
|
2289
|
+
const result = degradeToHandwritten(
|
|
2290
|
+
`harvest failed: ${e.message}`,
|
|
2291
|
+
"failed"
|
|
2292
|
+
);
|
|
2293
|
+
nodeHarvestCache.set(key, result);
|
|
2294
|
+
return result;
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
function summarizeNodeEnv(env) {
|
|
2298
|
+
return {
|
|
2299
|
+
modules: Object.keys(env.modules).sort(),
|
|
2300
|
+
globals: Object.keys(env.globals).sort().slice(0, 50),
|
|
2301
|
+
symbolCount: env.stats.symbols
|
|
2302
|
+
};
|
|
2303
|
+
}
|
|
687
2304
|
export {
|
|
2305
|
+
HARVEST_NODE_DEFAULT_MAX_FILES,
|
|
2306
|
+
HARVEST_NODE_DEFAULT_MAX_MS,
|
|
2307
|
+
absToHarvestSig,
|
|
2308
|
+
autoHarvestModules,
|
|
2309
|
+
barePackageName,
|
|
2310
|
+
bareSpecToAbsModules,
|
|
2311
|
+
clearHarvestCache,
|
|
2312
|
+
clearNodeHarvestCache,
|
|
2313
|
+
collectBarePackages,
|
|
2314
|
+
collectDtsFromEntry,
|
|
2315
|
+
depsCacheRoot,
|
|
2316
|
+
dtsClosureHash,
|
|
2317
|
+
dtsPathToContractDraft,
|
|
2318
|
+
dtsToContractDraft,
|
|
688
2319
|
emitEnvModule,
|
|
689
|
-
|
|
2320
|
+
formatDtsContractDraft,
|
|
2321
|
+
formatHarvestSummary,
|
|
2322
|
+
getHarvestCacheSize,
|
|
2323
|
+
getNodeHarvestCacheSize,
|
|
2324
|
+
handwrittenNodeEnv,
|
|
2325
|
+
harvestCacheKey,
|
|
2326
|
+
harvestDts,
|
|
2327
|
+
harvestNodeTypes,
|
|
2328
|
+
harvestPackage,
|
|
2329
|
+
harvestPackageCached,
|
|
2330
|
+
harvestPackageWithDisk,
|
|
2331
|
+
harvestSigToAbs,
|
|
2332
|
+
harvestToAbsModules,
|
|
2333
|
+
harvestedValueToAbs,
|
|
2334
|
+
isHarvestNodeDisabled,
|
|
2335
|
+
loadHarvestEnvFromDisk,
|
|
2336
|
+
lookupHarvested,
|
|
2337
|
+
materializeHarvestJson,
|
|
2338
|
+
packageHarvestToAbsModules,
|
|
2339
|
+
readHarvestDisk,
|
|
2340
|
+
resolvePackageRoot,
|
|
2341
|
+
serializeHarvestJson,
|
|
2342
|
+
summarizeNodeEnv,
|
|
2343
|
+
writeHarvestDisk
|
|
690
2344
|
};
|