@nudojs/cli 2.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/index.js +102 -115
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -21497,20 +21497,17 @@ function any() {
|
|
|
21497
21497
|
return makeBuilder(void 0, []);
|
|
21498
21498
|
}
|
|
21499
21499
|
function array(item) {
|
|
21500
|
-
|
|
21501
|
-
|
|
21502
|
-
|
|
21500
|
+
return makeBuilder(void 0, [], {
|
|
21501
|
+
element: asNestedConstraint(item, "array(item)")
|
|
21502
|
+
});
|
|
21503
21503
|
}
|
|
21504
21504
|
function shape(fields) {
|
|
21505
21505
|
const mapped = {};
|
|
21506
21506
|
for (const [k, v2] of Object.entries(fields)) {
|
|
21507
|
-
|
|
21508
|
-
throw new Error(
|
|
21509
|
-
`nudo: shape \u5B57\u6BB5 '${k}' \u671F\u671B\u7EA6\u675F\u503C\uFF08number()/string()/\u2026\u6216\u5176\u7EC4\u5408\u5B50\uFF09\uFF0C\u6536\u5230\u975E\u7EA6\u675F`
|
|
21510
|
-
);
|
|
21507
|
+
const constraint = asNestedConstraint(v2, `shape \u5B57\u6BB5 '${k}'`);
|
|
21511
21508
|
mapped[k] = {
|
|
21512
|
-
constraint
|
|
21513
|
-
...
|
|
21509
|
+
constraint,
|
|
21510
|
+
...constraint.isOptional ? { optional: true } : {}
|
|
21514
21511
|
};
|
|
21515
21512
|
}
|
|
21516
21513
|
return makeBuilder(void 0, [], { fields: mapped });
|
|
@@ -21535,19 +21532,32 @@ function lit3(v2) {
|
|
|
21535
21532
|
const prim2 = typeof v2 === "number" ? "number" : typeof v2 === "string" ? "string" : typeof v2 === "boolean" ? "boolean" : void 0;
|
|
21536
21533
|
return makeBuilder(prim2, [eq(selfTerm(), lit(v2))]);
|
|
21537
21534
|
}
|
|
21535
|
+
function asNestedConstraint(x, ctx) {
|
|
21536
|
+
if (isConstraint(x)) return toPlainConstraint(x);
|
|
21537
|
+
if (x === null || x === void 0 || typeof x === "number" || typeof x === "string" || typeof x === "boolean") {
|
|
21538
|
+
return toPlainConstraint(lit3(x));
|
|
21539
|
+
}
|
|
21540
|
+
throw new Error(
|
|
21541
|
+
`nudo: ${ctx} \u671F\u671B\u7EA6\u675F\u503C\uFF08number()/string()/\u2026\uFF09\u6216\u5177\u4F53\u5B57\u9762\u91CF\uFF0C\u6536\u5230\u975E\u7EA6\u675F`
|
|
21542
|
+
);
|
|
21543
|
+
}
|
|
21538
21544
|
function union(...cs) {
|
|
21539
21545
|
if (cs.length === 0)
|
|
21540
21546
|
throw new Error("nudo union(): \u81F3\u5C11\u9700\u8981\u4E00\u4E2A\u6210\u5458\u7EA6\u675F");
|
|
21541
|
-
return makeBuilder(void 0, [], {
|
|
21547
|
+
return makeBuilder(void 0, [], {
|
|
21548
|
+
members: cs.map((c) => asNestedConstraint(c, "union \u6210\u5458"))
|
|
21549
|
+
});
|
|
21542
21550
|
}
|
|
21543
21551
|
function fn(params, returns, opts) {
|
|
21544
21552
|
const normalized = {};
|
|
21545
|
-
for (const [k, v2] of Object.entries(params))
|
|
21553
|
+
for (const [k, v2] of Object.entries(params)) {
|
|
21554
|
+
normalized[k] = asNestedConstraint(v2, `fn \u53C2\u6570 '${k}'`);
|
|
21555
|
+
}
|
|
21546
21556
|
return makeBuilder(void 0, [], {
|
|
21547
21557
|
fn: {
|
|
21548
21558
|
params: normalized,
|
|
21549
|
-
...returns !== void 0 ? { returns:
|
|
21550
|
-
...opts?.throws !== void 0 ? { throws:
|
|
21559
|
+
...returns !== void 0 ? { returns: asNestedConstraint(returns, "fn \u8FD4\u56DE\u503C") } : {},
|
|
21560
|
+
...opts?.throws !== void 0 ? { throws: asNestedConstraint(opts.throws, "fn throws") } : {}
|
|
21551
21561
|
}
|
|
21552
21562
|
});
|
|
21553
21563
|
}
|
|
@@ -60675,10 +60685,6 @@ function absLit(v2) {
|
|
|
60675
60685
|
if (v2 === null) return absNullLit();
|
|
60676
60686
|
return absUndefLit();
|
|
60677
60687
|
}
|
|
60678
|
-
function absUnion(members) {
|
|
60679
|
-
if (members.length === 0) return absExact({ k: "never" });
|
|
60680
|
-
return members.reduce((acc, m) => joinAbs(acc, m));
|
|
60681
|
-
}
|
|
60682
60688
|
function tryParseConstraint(expr) {
|
|
60683
60689
|
const s = expr.trim();
|
|
60684
60690
|
if (!CONSTRAINT_EXPR_RE.test(s)) return void 0;
|
|
@@ -60707,15 +60713,15 @@ function flattenPreds(preds) {
|
|
|
60707
60713
|
function constraintSelfEqLit(c, selfIds) {
|
|
60708
60714
|
const leaves = flattenPreds(c.preds);
|
|
60709
60715
|
const eqs = leaves.filter((p2) => p2.op === "eq");
|
|
60710
|
-
if (eqs.length !== 1) return
|
|
60716
|
+
if (eqs.length !== 1) return { found: false };
|
|
60711
60717
|
const p = eqs[0];
|
|
60712
|
-
if (p.op !== "eq") return
|
|
60718
|
+
if (p.op !== "eq") return { found: false };
|
|
60713
60719
|
const isSelf = (t) => t.op === "var" && typeof t.id === "string" && selfIds.includes(t.id);
|
|
60714
60720
|
const a = p.a;
|
|
60715
60721
|
const b = p.b;
|
|
60716
|
-
if (isSelf(a) && b.op === "lit") return b.value;
|
|
60717
|
-
if (isSelf(b) && a.op === "lit") return a.value;
|
|
60718
|
-
return
|
|
60722
|
+
if (isSelf(a) && b.op === "lit") return { found: true, value: b.value };
|
|
60723
|
+
if (isSelf(b) && a.op === "lit") return { found: true, value: a.value };
|
|
60724
|
+
return { found: false };
|
|
60719
60725
|
}
|
|
60720
60726
|
function constraintToCaseArgAbs(c) {
|
|
60721
60727
|
if (c.members && c.members.length > 0) {
|
|
@@ -60724,10 +60730,32 @@ function constraintToCaseArgAbs(c) {
|
|
|
60724
60730
|
return { shape: { k: "sum", members: parts }, conf: "path" };
|
|
60725
60731
|
}
|
|
60726
60732
|
const selfIds = [SELF, "__arg", "__nudo_self__"];
|
|
60727
|
-
const
|
|
60728
|
-
if (
|
|
60729
|
-
|
|
60730
|
-
|
|
60733
|
+
const litRes = constraintSelfEqLit(c, selfIds);
|
|
60734
|
+
if (litRes.found) {
|
|
60735
|
+
const lv = litRes.value;
|
|
60736
|
+
if (typeof lv === "number" && !Number.isNaN(lv)) return numLit(lv);
|
|
60737
|
+
if (typeof lv === "string") return strLit(lv);
|
|
60738
|
+
if (typeof lv === "boolean") return boolLit(lv);
|
|
60739
|
+
if (lv === null) return absNullLit();
|
|
60740
|
+
return absUndefLit();
|
|
60741
|
+
}
|
|
60742
|
+
if (c.element) {
|
|
60743
|
+
return absExact({ k: "arr", element: constraintToCaseArgAbs(c.element) });
|
|
60744
|
+
}
|
|
60745
|
+
if (c.fields) {
|
|
60746
|
+
const slots = {};
|
|
60747
|
+
for (const [key, field] of Object.entries(c.fields)) {
|
|
60748
|
+
slots[key] = { value: constraintToCaseArgAbs(field.constraint) };
|
|
60749
|
+
}
|
|
60750
|
+
return absExact({ k: "obj", slots });
|
|
60751
|
+
}
|
|
60752
|
+
const bareScalar = !c.fields && !c.element && !c.members && !c.fn && c.int !== true && c.preds.length === 0;
|
|
60753
|
+
if (bareScalar) {
|
|
60754
|
+
if (c.prim === "number" || c.prim === "string" || c.prim === "boolean") {
|
|
60755
|
+
return absExact({ k: "prim", type: c.prim });
|
|
60756
|
+
}
|
|
60757
|
+
if (!c.prim) return absUnknown2();
|
|
60758
|
+
}
|
|
60731
60759
|
return constraintToEntryAbs(c, "__arg");
|
|
60732
60760
|
}
|
|
60733
60761
|
function parseCaseArgExpr(expr) {
|
|
@@ -60739,49 +60767,16 @@ function parseCaseArgExpr(expr) {
|
|
|
60739
60767
|
return absUnknown2();
|
|
60740
60768
|
}
|
|
60741
60769
|
}
|
|
60742
|
-
return
|
|
60770
|
+
return parseLiteralOrStructure(expr);
|
|
60743
60771
|
}
|
|
60744
|
-
function
|
|
60772
|
+
function parseLiteralOrStructure(expr) {
|
|
60745
60773
|
const s = expr.trim();
|
|
60746
|
-
if (s === "T.number") return num();
|
|
60747
|
-
if (s === "T.string") return str();
|
|
60748
|
-
if (s === "T.boolean") return bool();
|
|
60749
|
-
if (s === "T.unknown") return absUnknown2();
|
|
60750
|
-
if (s === "T.never") return absExact({ k: "never" });
|
|
60751
|
-
if (s === "T.null") return absNullLit();
|
|
60752
|
-
if (s === "T.undefined") return absUndefLit();
|
|
60753
60774
|
if (s === "true") return absLit(true);
|
|
60754
60775
|
if (s === "false") return absLit(false);
|
|
60755
60776
|
if (s === "null") return absLit(null);
|
|
60756
60777
|
if (s === "undefined") return absLit(void 0);
|
|
60757
|
-
|
|
60758
|
-
if (
|
|
60759
|
-
return absLit(parsePrimitiveValue(literalMatch[1].trim()));
|
|
60760
|
-
}
|
|
60761
|
-
if (s.startsWith("T.union(") && s.endsWith(")")) {
|
|
60762
|
-
const inner = s.slice("T.union(".length, -1);
|
|
60763
|
-
const args = splitTopLevelArgs(inner);
|
|
60764
|
-
return absUnion(args.map(parseLegacyTypeExpr));
|
|
60765
|
-
}
|
|
60766
|
-
if (s.startsWith("T.array(") && s.endsWith(")")) {
|
|
60767
|
-
const inner = s.slice("T.array(".length, -1);
|
|
60768
|
-
return absExact({ k: "arr", element: parseLegacyTypeExpr(inner) });
|
|
60769
|
-
}
|
|
60770
|
-
if (s.startsWith("T.tuple(") && s.endsWith(")")) {
|
|
60771
|
-
const inner = s.slice("T.tuple(".length, -1).trim();
|
|
60772
|
-
if (inner.startsWith("[") && inner.endsWith("]")) {
|
|
60773
|
-
const elements = splitTopLevelArgs(inner.slice(1, -1));
|
|
60774
|
-
return absExact({ k: "tuple", elements: elements.map(parseLegacyTypeExpr) });
|
|
60775
|
-
}
|
|
60776
|
-
return absExact({ k: "tuple", elements: [] });
|
|
60777
|
-
}
|
|
60778
|
-
if (s.startsWith("T.object(") && s.endsWith(")")) {
|
|
60779
|
-
const inner = s.slice("T.object(".length, -1).trim();
|
|
60780
|
-
if (inner.startsWith("{") && inner.endsWith("}")) {
|
|
60781
|
-
return parseObjectLiteral(inner.slice(1, -1).trim());
|
|
60782
|
-
}
|
|
60783
|
-
return absExact({ k: "obj", slots: {} });
|
|
60784
|
-
}
|
|
60778
|
+
if (s === "unknown" || s === "any") return absUnknown2();
|
|
60779
|
+
if (s === "never") return absExact({ k: "never" });
|
|
60785
60780
|
if (findTopLevelArrow(s) !== -1 || /^function\s*[\w$]*\s*\(/.test(s)) {
|
|
60786
60781
|
const fnExpr = parseArrowFunctionExpr(s);
|
|
60787
60782
|
if (fnExpr) {
|
|
@@ -60799,7 +60794,7 @@ function parseLegacyTypeExpr(expr) {
|
|
|
60799
60794
|
const content = s.slice(1, -1).trim();
|
|
60800
60795
|
if (!content) return absExact({ k: "tuple", elements: [] });
|
|
60801
60796
|
const elements = splitTopLevelArgs(content);
|
|
60802
|
-
return absExact({ k: "tuple", elements: elements.map(
|
|
60797
|
+
return absExact({ k: "tuple", elements: elements.map((e) => parseCaseArgExpr(e)) });
|
|
60803
60798
|
}
|
|
60804
60799
|
return absUnknown2();
|
|
60805
60800
|
}
|
|
@@ -60812,21 +60807,10 @@ function parseObjectLiteral(content) {
|
|
|
60812
60807
|
if (colonIdx === -1) continue;
|
|
60813
60808
|
const key = entry.slice(0, colonIdx).trim().replace(/^["']|["']$/g, "");
|
|
60814
60809
|
const val = entry.slice(colonIdx + 1).trim();
|
|
60815
|
-
slots[key] = { value:
|
|
60810
|
+
slots[key] = { value: parseCaseArgExpr(val) };
|
|
60816
60811
|
}
|
|
60817
60812
|
return absExact({ k: "obj", slots });
|
|
60818
60813
|
}
|
|
60819
|
-
function parsePrimitiveValue(s) {
|
|
60820
|
-
if (s === "true") return true;
|
|
60821
|
-
if (s === "false") return false;
|
|
60822
|
-
if (s === "null") return null;
|
|
60823
|
-
if (s === "undefined") return void 0;
|
|
60824
|
-
if (/^-?\d+(\.\d+)?$/.test(s)) return Number(s);
|
|
60825
|
-
if (s.startsWith('"') && s.endsWith('"') || s.startsWith("'") && s.endsWith("'")) {
|
|
60826
|
-
return s.slice(1, -1);
|
|
60827
|
-
}
|
|
60828
|
-
return s;
|
|
60829
|
-
}
|
|
60830
60814
|
function splitTopLevelArgs(s) {
|
|
60831
60815
|
const result = [];
|
|
60832
60816
|
let depth = 0;
|
|
@@ -61239,7 +61223,7 @@ function findReplaceSeparator(raw) {
|
|
|
61239
61223
|
}
|
|
61240
61224
|
if (ch === " " && depth === 0) {
|
|
61241
61225
|
const rest = raw.slice(i + 1).trimStart();
|
|
61242
|
-
if (
|
|
61226
|
+
if (CONSTRAINT_EXPR_RE.test(rest) || rest.startsWith("{") || rest.startsWith("[") || rest.startsWith('"') || rest.startsWith("'") || rest === "true" || rest === "false" || rest === "null" || rest === "undefined" || rest === "unknown" || rest === "any" || rest === "never" || /^-?\d+(\.\d+)?$/.test(rest) || rest.includes("=>") || /^function\s*[\w$]*\s*\(/.test(rest)) {
|
|
61243
61227
|
return i;
|
|
61244
61228
|
}
|
|
61245
61229
|
}
|
|
@@ -61293,7 +61277,7 @@ var init_directives = __esm({
|
|
|
61293
61277
|
PURE_REGEX = /@nudo:pure\b/g;
|
|
61294
61278
|
SKIP_REGEX = /@nudo:skip(?:\s+(.+))?/g;
|
|
61295
61279
|
SAMPLE_REGEX = /@nudo:sample\s+(\d+)/g;
|
|
61296
|
-
CONSTRAINT_EXPR_RE = /^(number|string|boolean|array|shape|lit|union|fn|partial|pick|omit|record|required|readonly|nonNullable|and)\s*\(/;
|
|
61280
|
+
CONSTRAINT_EXPR_RE = /^(number|string|boolean|any|array|shape|lit|union|fn|partial|pick|omit|record|required|readonly|nonNullable|and)\s*\(/;
|
|
61297
61281
|
AS_REGEX = /^\s*@nudo:as\s+(.+)/;
|
|
61298
61282
|
REPLACE_REGEX = /^\s*@nudo:replace\s+(.+)/;
|
|
61299
61283
|
ENV_REGEX = /^\/\s*@nudo:env\s+(.+)/;
|
|
@@ -275512,7 +275496,7 @@ function absFnSig(paramTypes, returnType) {
|
|
|
275512
275496
|
returnType
|
|
275513
275497
|
});
|
|
275514
275498
|
}
|
|
275515
|
-
function
|
|
275499
|
+
function absUnion(members) {
|
|
275516
275500
|
if (members.length === 0) return absNever();
|
|
275517
275501
|
return members.reduce((acc, m) => joinAbs(acc, m));
|
|
275518
275502
|
}
|
|
@@ -275827,7 +275811,7 @@ function materialize(ctx) {
|
|
|
275827
275811
|
const ret = mapType(ctx, decl.type, 0);
|
|
275828
275812
|
if (!isPlainUnknown(ret)) returns.push(ret);
|
|
275829
275813
|
}
|
|
275830
|
-
sig2.returnType = returns.length > 0 ?
|
|
275814
|
+
sig2.returnType = returns.length > 0 ? absUnion(returns) : absUnknown3();
|
|
275831
275815
|
}
|
|
275832
275816
|
rec[p.name] = value;
|
|
275833
275817
|
continue;
|
|
@@ -275851,7 +275835,7 @@ function materialize(ctx) {
|
|
|
275851
275835
|
if (ctor) {
|
|
275852
275836
|
for (const param of ctor.parameters) {
|
|
275853
275837
|
let type = param.type ? mapType(ctx, param.type, 0) : absUnknown3();
|
|
275854
|
-
if (param.questionToken) type =
|
|
275838
|
+
if (param.questionToken) type = absUnion([type, absUndefLit2()]);
|
|
275855
275839
|
params.push(type);
|
|
275856
275840
|
}
|
|
275857
275841
|
}
|
|
@@ -275901,7 +275885,7 @@ function instanceFor(ctx, key) {
|
|
|
275901
275885
|
const name = memberName(member);
|
|
275902
275886
|
if (name === void 0) continue;
|
|
275903
275887
|
let type = member.type ? mapType(ctx, member.type, 0) : absUnknown3();
|
|
275904
|
-
if (member.questionToken) type =
|
|
275888
|
+
if (member.questionToken) type = absUnion([type, absUndefLit2()]);
|
|
275905
275889
|
properties[name] = type;
|
|
275906
275890
|
} else if (import_typescript.default.isMethodSignature(member) || import_typescript.default.isMethodDeclaration(member)) {
|
|
275907
275891
|
const name = memberName(member);
|
|
@@ -275929,7 +275913,7 @@ function signatureToFnSig(ctx, sig2) {
|
|
|
275929
275913
|
} else {
|
|
275930
275914
|
type = param.type ? mapType(ctx, param.type, 0) : absUnknown3();
|
|
275931
275915
|
}
|
|
275932
|
-
if (param.questionToken && !param.dotDotDotToken) type =
|
|
275916
|
+
if (param.questionToken && !param.dotDotDotToken) type = absUnion([type, absUndefLit2()]);
|
|
275933
275917
|
return type;
|
|
275934
275918
|
});
|
|
275935
275919
|
const returnType = sig2.type ? mapType(ctx, sig2.type, 0) : absUnknown3();
|
|
@@ -275942,7 +275926,7 @@ function mapTypeMembers(ctx, members, depth) {
|
|
|
275942
275926
|
const name = memberName(member);
|
|
275943
275927
|
if (name === void 0) continue;
|
|
275944
275928
|
let type = member.type ? mapType(ctx, member.type, depth) : absUnknown3();
|
|
275945
|
-
if (member.questionToken) type =
|
|
275929
|
+
if (member.questionToken) type = absUnion([type, absUndefLit2()]);
|
|
275946
275930
|
properties[name] = type;
|
|
275947
275931
|
} else if (import_typescript.default.isMethodSignature(member)) {
|
|
275948
275932
|
const name = memberName(member);
|
|
@@ -275984,7 +275968,7 @@ function mapType(ctx, node, depth) {
|
|
|
275984
275968
|
return absUnknown3();
|
|
275985
275969
|
}
|
|
275986
275970
|
if (import_typescript.default.isUnionTypeNode(node)) {
|
|
275987
|
-
return
|
|
275971
|
+
return absUnion(node.types.map((t) => mapType(ctx, t, depth)));
|
|
275988
275972
|
}
|
|
275989
275973
|
if (import_typescript.default.isIntersectionTypeNode(node)) {
|
|
275990
275974
|
const members = node.types.map((t) => mapType(ctx, t, depth));
|
|
@@ -276018,7 +276002,7 @@ function mapType(ctx, node, depth) {
|
|
|
276018
276002
|
const params = [];
|
|
276019
276003
|
for (const param of ctor.parameters) {
|
|
276020
276004
|
let type = param.type ? mapType(ctx, param.type, depth) : absUnknown3();
|
|
276021
|
-
if (param.questionToken) type =
|
|
276005
|
+
if (param.questionToken) type = absUnion([type, absUndefLit2()]);
|
|
276022
276006
|
params.push(type);
|
|
276023
276007
|
}
|
|
276024
276008
|
return absFnSig(params, ctor.type ? mapType(ctx, ctor.type, depth) : absUnknown3());
|
|
@@ -277701,7 +277685,8 @@ function validateMockDirectives(directives, diagnostics) {
|
|
|
277701
277685
|
for (const d of directives) {
|
|
277702
277686
|
if (d.kind !== "mock" || !d.expression) continue;
|
|
277703
277687
|
const expr = d.expression.trim();
|
|
277704
|
-
|
|
277688
|
+
const isTypeExpr = /^(number|string|boolean|any|array|shape|lit|union|fn|partial|pick|omit|record|required|readonly|nonNullable|and)\s*\(/.test(expr) || expr === "unknown" || expr === "any" || expr === "never" || expr === "true" || expr === "false" || expr === "null" || expr === "undefined" || /^-?\d+(\.\d+)?$/.test(expr) || (expr.startsWith('"') && expr.endsWith('"') || expr.startsWith("'") && expr.endsWith("'")) || expr.startsWith("{") || expr.startsWith("[");
|
|
277689
|
+
if (expr.includes("(") && expr.includes(")") && !isTypeExpr && !expr.includes("=>")) {
|
|
277705
277690
|
diagnostics.push({
|
|
277706
277691
|
range: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
|
|
277707
277692
|
severity: "warning",
|
|
@@ -277709,7 +277694,8 @@ function validateMockDirectives(directives, diagnostics) {
|
|
|
277709
277694
|
code: "nudo:mock-invalid",
|
|
277710
277695
|
suggestions: [
|
|
277711
277696
|
"Supported formats: stub(), stub().returns(value), spy(), mock()",
|
|
277712
|
-
"Arrow functions: (args) => expression or (args) => { statements; return value; }"
|
|
277697
|
+
"Arrow functions: (args) => expression or (args) => { statements; return value; }",
|
|
277698
|
+
"Type expressions: number(), string(), shape({...}), union(...), or concrete literals"
|
|
277713
277699
|
]
|
|
277714
277700
|
});
|
|
277715
277701
|
}
|
|
@@ -278685,7 +278671,7 @@ function analyzeFileUncachedInner(filePath, source, activeCases, externalCallRec
|
|
|
278685
278671
|
diagnostics.push({
|
|
278686
278672
|
range: { start: { line: directive.commentLine, column: 0 }, end: { line: directive.commentLine, column: 999 } },
|
|
278687
278673
|
severity: "error",
|
|
278688
|
-
message: `
|
|
278674
|
+
message: `debug "${directive.name}": expected ${formatShape(directive.expected)}, got ${formatShape(caseAbs)}. The inferred return type does not match the expected type declared in the @nudo:case witness`,
|
|
278689
278675
|
code: "nudo:case-expected"
|
|
278690
278676
|
});
|
|
278691
278677
|
}
|
|
@@ -281177,15 +281163,15 @@ function serializeCaseArg(a) {
|
|
|
281177
281163
|
}
|
|
281178
281164
|
switch (s.k) {
|
|
281179
281165
|
case "prim":
|
|
281180
|
-
if (s.type === "number"
|
|
281181
|
-
|
|
281182
|
-
|
|
281166
|
+
if (s.type === "number") return "number()";
|
|
281167
|
+
if (s.type === "string") return "string()";
|
|
281168
|
+
if (s.type === "boolean") return "boolean()";
|
|
281183
281169
|
return null;
|
|
281184
281170
|
case "unknown":
|
|
281185
281171
|
case "any":
|
|
281186
|
-
return "
|
|
281172
|
+
return "any()";
|
|
281187
281173
|
case "never":
|
|
281188
|
-
return "
|
|
281174
|
+
return "never";
|
|
281189
281175
|
case "sum": {
|
|
281190
281176
|
const parts = [];
|
|
281191
281177
|
for (const member of s.members) {
|
|
@@ -281193,11 +281179,11 @@ function serializeCaseArg(a) {
|
|
|
281193
281179
|
if (ser === null) return null;
|
|
281194
281180
|
parts.push(ser);
|
|
281195
281181
|
}
|
|
281196
|
-
return `
|
|
281182
|
+
return `union(${parts.join(", ")})`;
|
|
281197
281183
|
}
|
|
281198
281184
|
case "arr": {
|
|
281199
281185
|
const el = serializeCaseArg(s.element);
|
|
281200
|
-
return el === null ? null : `
|
|
281186
|
+
return el === null ? null : `array(${el})`;
|
|
281201
281187
|
}
|
|
281202
281188
|
case "tuple": {
|
|
281203
281189
|
const parts = [];
|
|
@@ -283843,7 +283829,7 @@ async function runInfer(file, options = {}) {
|
|
|
283843
283829
|
}
|
|
283844
283830
|
}
|
|
283845
283831
|
if (result.functions.length === 0 && !result.externalFunctions?.length) {
|
|
283846
|
-
console.log("No functions
|
|
283832
|
+
console.log("No functions found to analyze.");
|
|
283847
283833
|
return;
|
|
283848
283834
|
}
|
|
283849
283835
|
const dtsLines = [];
|
|
@@ -283867,7 +283853,8 @@ async function runInfer(file, options = {}) {
|
|
|
283867
283853
|
}
|
|
283868
283854
|
for (const c of fn2.cases) {
|
|
283869
283855
|
const argsStr = c.argAbs.map(formatShape).join(", ");
|
|
283870
|
-
|
|
283856
|
+
const label = c.name.startsWith("call@") || c.name.startsWith("entry@") ? c.name : `debug "${c.name}"`;
|
|
283857
|
+
let line = `${label}: (${argsStr}) => ${formatShape(c.abs)}`;
|
|
283871
283858
|
if (c.throwsAbs.shape.k !== "never") line += ` throws ${formatShape(c.throwsAbs)}`;
|
|
283872
283859
|
console.log(line);
|
|
283873
283860
|
if (c.intension?.display) {
|
|
@@ -283888,7 +283875,7 @@ async function runInfer(file, options = {}) {
|
|
|
283888
283875
|
}
|
|
283889
283876
|
if (fn2.cases.length > 1 && fn2.combinedAbs) {
|
|
283890
283877
|
console.log(`
|
|
283891
|
-
|
|
283878
|
+
Observed: ${formatShape(fn2.combinedAbs)}`);
|
|
283892
283879
|
}
|
|
283893
283880
|
console.log();
|
|
283894
283881
|
}
|
|
@@ -283909,13 +283896,14 @@ Combined: ${formatShape(fn2.combinedAbs)}`);
|
|
|
283909
283896
|
`);
|
|
283910
283897
|
for (const c of fn2.cases) {
|
|
283911
283898
|
const argsStr = c.argAbs.map(formatShape).join(", ");
|
|
283912
|
-
|
|
283899
|
+
const label = c.name.startsWith("call@") || c.name.startsWith("entry@") ? c.name : `debug "${c.name}"`;
|
|
283900
|
+
let line = `${label}: (${argsStr}) => ${formatShape(c.abs)}`;
|
|
283913
283901
|
if (c.throwsAbs.shape.k !== "never") line += ` throws ${formatShape(c.throwsAbs)}`;
|
|
283914
283902
|
console.log(line);
|
|
283915
283903
|
}
|
|
283916
283904
|
if (fn2.cases.length > 1 && fn2.combinedAbs) {
|
|
283917
283905
|
console.log(`
|
|
283918
|
-
|
|
283906
|
+
Observed: ${formatShape(fn2.combinedAbs)}`);
|
|
283919
283907
|
}
|
|
283920
283908
|
console.log();
|
|
283921
283909
|
}
|
|
@@ -283971,7 +283959,7 @@ async function runInferJson(file, externalRecords) {
|
|
|
283971
283959
|
const { serializeInferJson: serializeInferJson2 } = await Promise.resolve().then(() => (init_src4(), src_exports2));
|
|
283972
283960
|
console.log(JSON.stringify(serializeInferJson2(result, filePath), null, 2));
|
|
283973
283961
|
}
|
|
283974
|
-
program.command("infer").description("Infer types from a JS/TS file (or a directory
|
|
283962
|
+
program.command("infer").description("Infer types from a JS/TS file (or a directory) by executing observed call sites; optional @nudo:case witnesses are for debug / nudo test only").argument("<file>", "Path to the JS/TS file (or directory)").option("--dts", "Generate .d.ts file").option("--loc", "Show source locations in output").option("--json", "Output as JSON").option("--callsites <paths...>", "Usage-site files (tests/apps) to harvest real call shapes from; their calls to this file's exports become synthesized cases").option("--emit-cases [mode]", "Debug solidification: write synthesized call-site witnesses back as @nudo:case directives (call@ prefix); not the interface product path. mode: update (default: add)").option("--dry-run", "With --emit-cases: print a unified diff instead of writing to disk").option("--exit-on-diff", "With --dry-run: exit with code 1 when the diff is non-empty").action(
|
|
283975
283963
|
async (file, opts) => {
|
|
283976
283964
|
let emit;
|
|
283977
283965
|
if (opts.emitCases !== void 0) {
|
|
@@ -284513,7 +284501,7 @@ program.command("interface").alias("refine").description(
|
|
|
284513
284501
|
}
|
|
284514
284502
|
}
|
|
284515
284503
|
);
|
|
284516
|
-
program.command("test").description("Run @nudo:case directives as assertions (case-as-test); exit 1 on failure").argument("<file>", "Path to the JS/TS file or a directory of them").action(async (file) => {
|
|
284504
|
+
program.command("test").description("Run @nudo:case directives as debug assertions (case-as-test); exit 1 on failure").argument("<file>", "Path to the JS/TS file or a directory of them").action(async (file) => {
|
|
284517
284505
|
const targets = resolveTargets(file);
|
|
284518
284506
|
if (targets.length === 0) return;
|
|
284519
284507
|
for (const filePath of targets) {
|
|
@@ -284657,12 +284645,12 @@ async function runDoctor(paths, opts) {
|
|
|
284657
284645
|
const entryInfo = r.entryOnly > 0 ? `, ${r.entryOnly} entry-only` : "";
|
|
284658
284646
|
console.log(` \xB7 ${r.functions} function(s)${entryInfo}`);
|
|
284659
284647
|
if (r.uncovered.length > 0) {
|
|
284660
|
-
console.log(` \u26A0 uncovered (no
|
|
284648
|
+
console.log(` \u26A0 uncovered (no call-site evidence): ${r.uncovered.join(", ")}`);
|
|
284661
284649
|
}
|
|
284662
284650
|
if (r.drift) {
|
|
284663
284651
|
const refresh = `nudo infer ${r.file} --callsites ${(opts.callsites ?? []).join(" ")} --emit-cases=update`;
|
|
284664
284652
|
console.log(
|
|
284665
|
-
` \u2717 drift: ${r.drift.added + r.drift.removed} directive(s) changed (+${r.drift.added} new, -${r.drift.removed} removed) \u2014 refresh
|
|
284653
|
+
` \u2717 drift: ${r.drift.added + r.drift.removed} debug witness directive(s) changed (+${r.drift.added} new, -${r.drift.removed} removed) \u2014 optional refresh: ${refresh.trim()}`
|
|
284666
284654
|
);
|
|
284667
284655
|
}
|
|
284668
284656
|
if ((r.interfaceDrift ?? 0) > 0) {
|
|
@@ -284809,18 +284797,16 @@ async function runGenerate(file, format, output) {
|
|
|
284809
284797
|
const filePath = resolve12(file);
|
|
284810
284798
|
const source = readFileSync15(filePath, "utf-8");
|
|
284811
284799
|
const result = await analyzeFileAsync(filePath, source);
|
|
284812
|
-
const functions = result.functions.filter(
|
|
284813
|
-
(f) => f.cases.some((c) => c.source === "directive")
|
|
284814
|
-
);
|
|
284800
|
+
const functions = result.functions.filter((f) => f.cases.length > 0);
|
|
284815
284801
|
if (functions.length === 0) {
|
|
284816
|
-
console.log("No functions
|
|
284802
|
+
console.log("No analyzed functions found.");
|
|
284817
284803
|
return;
|
|
284818
284804
|
}
|
|
284819
284805
|
const zodChunks = [];
|
|
284820
284806
|
const guardChunks = [];
|
|
284821
284807
|
const dtsChunks = [];
|
|
284822
284808
|
for (const fn2 of functions) {
|
|
284823
|
-
const caseResults = fn2.cases
|
|
284809
|
+
const caseResults = fn2.cases;
|
|
284824
284810
|
const baseName = fn2.name;
|
|
284825
284811
|
if (format === "zod" || format === "all") {
|
|
284826
284812
|
const lines = [`
|
|
@@ -284828,7 +284814,8 @@ async function runGenerate(file, format, output) {
|
|
|
284828
284814
|
for (const c of caseResults) {
|
|
284829
284815
|
const inputSchemas = c.argAbs.map((a, i) => `arg${i}: ${absToZodSchema(a)}`).join(", ");
|
|
284830
284816
|
const outputSchema = absToZodSchema(c.abs);
|
|
284831
|
-
|
|
284817
|
+
const label = c.name.startsWith("call@") || c.name.startsWith("entry@") ? c.name : `debug "${c.name}"`;
|
|
284818
|
+
lines.push(`// ${label}:`);
|
|
284832
284819
|
lines.push(`// Input: { ${inputSchemas} }`);
|
|
284833
284820
|
lines.push(`// Output: ${outputSchema}`);
|
|
284834
284821
|
}
|
|
@@ -284837,9 +284824,9 @@ async function runGenerate(file, format, output) {
|
|
|
284837
284824
|
if (format === "guard" || format === "all") {
|
|
284838
284825
|
const lines = [`
|
|
284839
284826
|
// === ${baseName} Type Guards ===`];
|
|
284840
|
-
|
|
284841
|
-
|
|
284842
|
-
lines.push(generateGuardFunctionFromAbs(
|
|
284827
|
+
const absForGuard = fn2.combinedAbs ?? caseResults[0]?.abs;
|
|
284828
|
+
if (absForGuard) {
|
|
284829
|
+
lines.push(generateGuardFunctionFromAbs(`is${baseName}Output`, absForGuard));
|
|
284843
284830
|
}
|
|
284844
284831
|
guardChunks.push(lines.join("\n"));
|
|
284845
284832
|
}
|
|
@@ -284938,7 +284925,7 @@ Usage \u2014 add this directive at the top of your JS file:`);
|
|
|
284938
284925
|
const hintPath = resolve12(process.cwd()) === outDir ? basename5(out) : out;
|
|
284939
284926
|
console.log(` /// @nudo:env ${hintPath}`);
|
|
284940
284927
|
}
|
|
284941
|
-
program.command("harvest").description("Convert @types/<pkg> .d.ts declarations into a Nudo env file (
|
|
284928
|
+
program.command("harvest").description("Convert @types/<pkg> .d.ts declarations into a Nudo env file (constraint builders, not T.*)").argument("[pkg]", "Package name under @types (e.g. node)").option("--out <file>", "Output .ts env file (default: ./nudo-harvest-<pkg>.ts)").option("--auto [dir]", "Scan directory (default .) for bare imports and report auto-harvestable @types packages").action(async (pkg, opts) => {
|
|
284942
284929
|
if (opts.auto !== void 0) {
|
|
284943
284930
|
const dir = resolve12(typeof opts.auto === "string" ? opts.auto : ".");
|
|
284944
284931
|
const files = existsSync16(dir) && statSync9(dir).isDirectory() ? collectNudoFiles(dir) : existsSync16(dir) ? [dir] : [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nudojs/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=20"
|
|
6
6
|
},
|
|
@@ -43,11 +43,11 @@
|
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"commander": "^13.0.0",
|
|
46
|
-
"@nudojs/
|
|
47
|
-
"@nudojs/
|
|
48
|
-
"@nudojs/service": "
|
|
49
|
-
"@nudojs/env": "0.4.
|
|
50
|
-
"@nudojs/harvester": "0.2.
|
|
46
|
+
"@nudojs/core": "2.1.0",
|
|
47
|
+
"@nudojs/parser": "1.0.0",
|
|
48
|
+
"@nudojs/service": "4.0.0",
|
|
49
|
+
"@nudojs/env": "0.4.1",
|
|
50
|
+
"@nudojs/harvester": "0.2.7"
|
|
51
51
|
},
|
|
52
52
|
"scripts": {
|
|
53
53
|
"build": "tsup"
|