@nudojs/core 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-BU4A6XJ3.js +6856 -0
- package/dist/exec.d.ts +2 -0
- package/dist/exec.js +168 -0
- package/dist/index-CVYjXnwU.d.ts +902 -0
- package/dist/index.d.ts +64 -710
- package/dist/index.js +3258 -8426
- package/package.json +18 -5
- package/CHANGELOG.md +0 -34
- package/src/__tests__/environment.test.ts +0 -48
- package/src/__tests__/fn-sig-impl.test.ts +0 -57
- package/src/__tests__/ops-refined.test.ts +0 -196
- package/src/__tests__/ops.test.ts +0 -84
- package/src/__tests__/type-value-phase2.test.ts +0 -117
- package/src/__tests__/type-value-phase3.test.ts +0 -94
- package/src/__tests__/type-value-refined.test.ts +0 -210
- package/src/__tests__/type-value.test.ts +0 -319
- package/src/algebra/__tests__/abs-native.test.ts +0 -176
- package/src/algebra/__tests__/arithmetic.test.ts +0 -170
- package/src/algebra/__tests__/ast-eval.test.ts +0 -154
- package/src/algebra/__tests__/bridge.test.ts +0 -160
- package/src/algebra/__tests__/call-budget.test.ts +0 -117
- package/src/algebra/__tests__/check-case-consistency.test.ts +0 -69
- package/src/algebra/__tests__/check-gold.test.ts +0 -290
- package/src/algebra/__tests__/check-json.test.ts +0 -57
- package/src/algebra/__tests__/check-mini-repo.test.ts +0 -49
- package/src/algebra/__tests__/check-real-commander.test.ts +0 -43
- package/src/algebra/__tests__/check-real-packages.test.ts +0 -165
- package/src/algebra/__tests__/check-recall-gold.test.ts +0 -1024
- package/src/algebra/__tests__/check-require-dep-memo.test.ts +0 -45
- package/src/algebra/__tests__/check-return-contract.test.ts +0 -90
- package/src/algebra/__tests__/check-shape-gold.test.ts +0 -303
- package/src/algebra/__tests__/check-source-memo.test.ts +0 -204
- package/src/algebra/__tests__/check.test.ts +0 -69
- package/src/algebra/__tests__/denote.test.ts +0 -77
- package/src/algebra/__tests__/diagnostics.test.ts +0 -64
- package/src/algebra/__tests__/differential.test.ts +0 -124
- package/src/algebra/__tests__/div-mod.test.ts +0 -38
- package/src/algebra/__tests__/example0.test.ts +0 -103
- package/src/algebra/__tests__/exec-analyze-throws.test.ts +0 -60
- package/src/algebra/__tests__/exec-async.test.ts +0 -46
- package/src/algebra/__tests__/exec-bpath.test.ts +0 -193
- package/src/algebra/__tests__/exec-class.test.ts +0 -56
- package/src/algebra/__tests__/exec-e2e.test.ts +0 -97
- package/src/algebra/__tests__/exec-generator.test.ts +0 -53
- package/src/algebra/__tests__/exec-inherit-default.test.ts +0 -111
- package/src/algebra/__tests__/exec-optional-nested.test.ts +0 -87
- package/src/algebra/__tests__/exec-rest.test.ts +0 -55
- package/src/algebra/__tests__/exec-spread-forof.test.ts +0 -85
- package/src/algebra/__tests__/exec-static-computed.test.ts +0 -61
- package/src/algebra/__tests__/exec-switch.test.ts +0 -74
- package/src/algebra/__tests__/exec-trycatch.test.ts +0 -71
- package/src/algebra/__tests__/fn-fp-memo.test.ts +0 -166
- package/src/algebra/__tests__/fn-fp-multidecl.test.ts +0 -49
- package/src/algebra/__tests__/format.test.ts +0 -34
- package/src/algebra/__tests__/generalize-memo.test.ts +0 -285
- package/src/algebra/__tests__/generalize.test.ts +0 -113
- package/src/algebra/__tests__/hash-source.test.ts +0 -19
- package/src/algebra/__tests__/hof.test.ts +0 -80
- package/src/algebra/__tests__/inlay.test.ts +0 -30
- package/src/algebra/__tests__/language.test.ts +0 -77
- package/src/algebra/__tests__/leq.test.ts +0 -109
- package/src/algebra/__tests__/load-deps-fp-memo.test.ts +0 -132
- package/src/algebra/__tests__/methods.test.ts +0 -37
- package/src/algebra/__tests__/node-types.test.ts +0 -26
- package/src/algebra/__tests__/nudo-constraints.ts +0 -46
- package/src/algebra/__tests__/objects.test.ts +0 -117
- package/src/algebra/__tests__/parse-source-cache.test.ts +0 -42
- package/src/algebra/__tests__/refine-import.test.ts +0 -109
- package/src/algebra/__tests__/stable-source-key-block.test.ts +0 -23
- package/src/algebra/__tests__/stable-source-key.test.ts +0 -31
- package/src/algebra/__tests__/surface.test.ts +0 -41
- package/src/algebra/__tests__/this-inherit.test.ts +0 -130
- package/src/algebra/abs-fn.ts +0 -49
- package/src/algebra/abs-modules.ts +0 -139
- package/src/algebra/abs.ts +0 -206
- package/src/algebra/arithmetic.ts +0 -691
- package/src/algebra/ast-eval.ts +0 -1763
- package/src/algebra/bridge.ts +0 -350
- package/src/algebra/builtins.ts +0 -373
- package/src/algebra/check-report.ts +0 -150
- package/src/algebra/check.ts +0 -1867
- package/src/algebra/constraint.ts +0 -304
- package/src/algebra/denote.ts +0 -166
- package/src/algebra/diagnostics.ts +0 -210
- package/src/algebra/exec/call.ts +0 -29
- package/src/algebra/exec/calls.ts +0 -96
- package/src/algebra/exec/class-registry.ts +0 -29
- package/src/algebra/exec/class.ts +0 -329
- package/src/algebra/exec/index.ts +0 -6
- package/src/algebra/exec/member-diag.ts +0 -141
- package/src/algebra/exec/run.ts +0 -341
- package/src/algebra/exec/runtime.ts +0 -583
- package/src/algebra/exec/transpile.ts +0 -993
- package/src/algebra/fn-fp.ts +0 -294
- package/src/algebra/format.ts +0 -91
- package/src/algebra/generalize.ts +0 -776
- package/src/algebra/hash-source.ts +0 -27
- package/src/algebra/index.ts +0 -34
- package/src/algebra/inlay.ts +0 -198
- package/src/algebra/language.ts +0 -289
- package/src/algebra/leak.ts +0 -68
- package/src/algebra/leq.ts +0 -301
- package/src/algebra/load-deps-fp.ts +0 -97
- package/src/algebra/methods.ts +0 -128
- package/src/algebra/objects.ts +0 -251
- package/src/algebra/parse-source.ts +0 -64
- package/src/algebra/phi.ts +0 -42
- package/src/algebra/pred.ts +0 -395
- package/src/algebra/refine.ts +0 -227
- package/src/algebra/stable-source-key.ts +0 -81
- package/src/algebra/surface.ts +0 -206
- package/src/algebra/template.ts +0 -143
- package/src/algebra/term.ts +0 -119
- package/src/environment.ts +0 -74
- package/src/index.ts +0 -73
- package/src/mock-helpers.ts +0 -166
- package/src/ops.ts +0 -191
- package/src/refinements/range.ts +0 -86
- package/src/refinements/template-predicates.ts +0 -11
- package/src/refinements/template.ts +0 -176
- package/src/strip-types.ts +0 -236
- package/src/type-value.ts +0 -513
- package/tsconfig.json +0 -8
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { leqAbs } from "../leq.ts";
|
|
3
|
-
import { abs, numLit, strLit, boolLit, num, str, unknown, never } from "../abs.ts";
|
|
4
|
-
import { v, lit } from "../term.ts";
|
|
5
|
-
import { gt, ge, pTrue } from "../pred.ts";
|
|
6
|
-
|
|
7
|
-
function numWithPred(n: number, op: "gt" | "ge" = "gt") {
|
|
8
|
-
return abs(num().shape, v("x"), op === "gt" ? gt(v("x"), lit(n)) : ge(v("x"), lit(n)), "path");
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
describe("leqAbs structural assignability", () => {
|
|
12
|
-
it("never and unknown poles", () => {
|
|
13
|
-
expect(leqAbs(never, num()).ok).toBe(true);
|
|
14
|
-
expect(leqAbs(numLit(1), unknown).ok).toBe(true);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it("prim and literal", () => {
|
|
18
|
-
expect(leqAbs(numLit(1), num()).ok).toBe(true);
|
|
19
|
-
expect(leqAbs(strLit("a"), str()).ok).toBe(true);
|
|
20
|
-
expect(leqAbs(numLit(1), str()).ok).toBe(false);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it("object width and depth", () => {
|
|
24
|
-
const src = abs(
|
|
25
|
-
{
|
|
26
|
-
k: "obj",
|
|
27
|
-
slots: { a: { value: numLit(1) }, b: { value: strLit("x") } },
|
|
28
|
-
},
|
|
29
|
-
undefined,
|
|
30
|
-
undefined,
|
|
31
|
-
"exact",
|
|
32
|
-
);
|
|
33
|
-
const tgt = abs({ k: "obj", slots: { a: { value: num() } } }, undefined, undefined, "exact");
|
|
34
|
-
expect(leqAbs(src, tgt).ok).toBe(true);
|
|
35
|
-
|
|
36
|
-
const missing = abs({ k: "obj", slots: { z: { value: num() } } }, undefined, undefined, "exact");
|
|
37
|
-
expect(leqAbs(src, missing).ok).toBe(false);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it("optional slot may be missing", () => {
|
|
41
|
-
const src = abs({ k: "obj", slots: { a: { value: numLit(1) } } }, undefined, undefined, "exact");
|
|
42
|
-
const tgt = abs(
|
|
43
|
-
{ k: "obj", slots: { a: { value: num() }, b: { value: str(), optional: true } } },
|
|
44
|
-
undefined,
|
|
45
|
-
undefined,
|
|
46
|
-
"exact",
|
|
47
|
-
);
|
|
48
|
-
expect(leqAbs(src, tgt).ok).toBe(true);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("arr and tuple", () => {
|
|
52
|
-
const tup = abs({ k: "tuple", elements: [numLit(1), numLit(2)] }, undefined, undefined, "exact");
|
|
53
|
-
const arr = abs({ k: "arr", element: num() }, undefined, undefined, "exact");
|
|
54
|
-
expect(leqAbs(tup, arr).ok).toBe(true);
|
|
55
|
-
expect(leqAbs(arr, tup).ok).toBe(false);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("brand is nominal", () => {
|
|
59
|
-
const a = abs(
|
|
60
|
-
{ k: "brand", name: "A", shape: abs({ k: "obj", slots: {} }, undefined, undefined, "exact") },
|
|
61
|
-
undefined,
|
|
62
|
-
undefined,
|
|
63
|
-
"exact",
|
|
64
|
-
);
|
|
65
|
-
const b = abs(
|
|
66
|
-
{ k: "brand", name: "B", shape: abs({ k: "obj", slots: {} }, undefined, undefined, "exact") },
|
|
67
|
-
undefined,
|
|
68
|
-
undefined,
|
|
69
|
-
"exact",
|
|
70
|
-
);
|
|
71
|
-
expect(leqAbs(a, a).ok).toBe(true);
|
|
72
|
-
expect(leqAbs(a, b).ok).toBe(false);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("numeric pred: stricter source is assignable to wider target", () => {
|
|
76
|
-
const x5 = numWithPred(5, "gt");
|
|
77
|
-
const x0 = numWithPred(0, "gt");
|
|
78
|
-
expect(leqAbs(x5, x0).ok).toBe(true);
|
|
79
|
-
expect(leqAbs(x0, x5).ok).toBe(false);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it("eff promise inner", () => {
|
|
83
|
-
const p1 = abs(
|
|
84
|
-
{ k: "eff", eff: "promise", inner: numLit(1) },
|
|
85
|
-
undefined,
|
|
86
|
-
undefined,
|
|
87
|
-
"exact",
|
|
88
|
-
);
|
|
89
|
-
const p2 = abs({ k: "eff", eff: "promise", inner: num() }, undefined, undefined, "exact");
|
|
90
|
-
expect(leqAbs(p1, p2).ok).toBe(true);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it("sum source requires all members assignable", () => {
|
|
94
|
-
const sum = abs(
|
|
95
|
-
{ k: "sum", members: [numLit(1), numLit(2)] },
|
|
96
|
-
undefined,
|
|
97
|
-
undefined,
|
|
98
|
-
"exact",
|
|
99
|
-
);
|
|
100
|
-
expect(leqAbs(sum, num()).ok).toBe(true);
|
|
101
|
-
expect(leqAbs(sum, str()).ok).toBe(false);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it("fail reason is nudo-style", () => {
|
|
105
|
-
const r = leqAbs(numLit(1), str());
|
|
106
|
-
expect(r.ok).toBe(false);
|
|
107
|
-
expect(r.reason).toContain("prim");
|
|
108
|
-
});
|
|
109
|
-
});
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
checkSource,
|
|
4
|
-
generalizeFromAst,
|
|
5
|
-
resetCheckSourceMemo,
|
|
6
|
-
resetGeneralizeMemo,
|
|
7
|
-
getCheckSourceMemoSize,
|
|
8
|
-
getGeneralizeMemoSize,
|
|
9
|
-
pTrue,
|
|
10
|
-
} from "../index.ts";
|
|
11
|
-
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
resetCheckSourceMemo();
|
|
14
|
-
resetGeneralizeMemo();
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
/** 生成 >64 个可达 require 依赖的源码 */
|
|
18
|
-
function manyDepsSource(n: number): { src: string; deps: Record<string, string> } {
|
|
19
|
-
const deps: Record<string, string> = {};
|
|
20
|
-
let src = "";
|
|
21
|
-
for (let i = 0; i < n; i++) {
|
|
22
|
-
const name = `./d${i}.js`;
|
|
23
|
-
deps[`/t/d${i}.js`] = `module.exports = { n: ${i} };\n`;
|
|
24
|
-
src += `const m${i} = require("${name}");\n`;
|
|
25
|
-
}
|
|
26
|
-
src += `function id(x) { return x; }\nid(m0.n);\n`;
|
|
27
|
-
return { src, deps };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function makeLoad(deps: Record<string, string>) {
|
|
31
|
-
return (spec: string, fromFile: string) => {
|
|
32
|
-
const i = fromFile.lastIndexOf("/");
|
|
33
|
-
const base = i >= 0 ? fromFile.slice(0, i) : "";
|
|
34
|
-
const joined = spec.startsWith(".")
|
|
35
|
-
? `${base}/${spec.replace(/^\.\//, "")}`
|
|
36
|
-
: spec;
|
|
37
|
-
return deps[joined];
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
describe("check memo fail-open on truncated dep fingerprint", () => {
|
|
42
|
-
it("does not memoize when loadable deps exceed the node cap", () => {
|
|
43
|
-
const { src, deps } = manyDepsSource(70);
|
|
44
|
-
const loadModule = makeLoad(deps);
|
|
45
|
-
const opts = { loadModule, fromFile: "/t/a.js" };
|
|
46
|
-
const r1 = checkSource("/t/a.js", src, pTrue, opts);
|
|
47
|
-
expect(r1).toBeDefined();
|
|
48
|
-
// 截断指纹不可信 → 不得写入整文件 memo
|
|
49
|
-
expect(getCheckSourceMemoSize()).toBe(0);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it("still re-checks after an uncached dep changes (no stale hit)", () => {
|
|
53
|
-
const { src, deps } = manyDepsSource(70);
|
|
54
|
-
// 假设 64 节点 cap 后 d69 可能未进指纹;改它再 check 必须重算
|
|
55
|
-
deps["/t/d69.js"] = `module.exports = { n: 999 };\n`;
|
|
56
|
-
const loadModule = makeLoad(deps);
|
|
57
|
-
const opts = { loadModule, fromFile: "/t/a.js" };
|
|
58
|
-
const r = checkSource("/t/a.js", src, pTrue, opts);
|
|
59
|
-
expect(r).toBeDefined();
|
|
60
|
-
expect(getCheckSourceMemoSize()).toBe(0);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("memoizes normally under the node cap", () => {
|
|
64
|
-
const { src, deps } = manyDepsSource(3);
|
|
65
|
-
const loadModule = makeLoad(deps);
|
|
66
|
-
const opts = { loadModule, fromFile: "/t/a.js" };
|
|
67
|
-
checkSource("/t/a.js", src, pTrue, opts);
|
|
68
|
-
expect(getCheckSourceMemoSize()).toBe(1);
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
describe("generalize L0 fingerprints ordinary require deps", () => {
|
|
73
|
-
it("misses L0 when a plain require target changes (parent source unchanged)", () => {
|
|
74
|
-
let dep = `module.exports = { add1: (x) => x + 1 };\n`;
|
|
75
|
-
const loadModule = (spec: string) =>
|
|
76
|
-
spec === "./util.js" ? dep : undefined;
|
|
77
|
-
const src = `
|
|
78
|
-
const util = require("./util.js");
|
|
79
|
-
function go(x) {
|
|
80
|
-
return util.add1(x);
|
|
81
|
-
}
|
|
82
|
-
`;
|
|
83
|
-
const opts = {
|
|
84
|
-
refine: { loadModule, fromFile: "/t/a.js" },
|
|
85
|
-
};
|
|
86
|
-
const g0 = generalizeFromAst("go", src, opts);
|
|
87
|
-
expect(g0).toBeDefined();
|
|
88
|
-
expect(getGeneralizeMemoSize()).toBe(1);
|
|
89
|
-
|
|
90
|
-
dep = `module.exports = { add1: (x) => x + 10 };\n`;
|
|
91
|
-
const g1 = generalizeFromAst("go", src, opts);
|
|
92
|
-
expect(g1).toBeDefined();
|
|
93
|
-
// dep 内容变了 → L0 不得复用旧 PolyFn
|
|
94
|
-
expect(g1).not.toBe(g0);
|
|
95
|
-
expect(getGeneralizeMemoSize()).toBe(2);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it("still hits L0 when require target is unchanged", () => {
|
|
99
|
-
const dep = `module.exports = { add1: (x) => x + 1 };\n`;
|
|
100
|
-
const loadModule = (spec: string) =>
|
|
101
|
-
spec === "./util.js" ? dep : undefined;
|
|
102
|
-
const src = `
|
|
103
|
-
const util = require("./util.js");
|
|
104
|
-
function go(x) {
|
|
105
|
-
return util.add1(x);
|
|
106
|
-
}
|
|
107
|
-
`;
|
|
108
|
-
const opts = {
|
|
109
|
-
refine: { loadModule, fromFile: "/t/a.js" },
|
|
110
|
-
};
|
|
111
|
-
const g0 = generalizeFromAst("go", src, opts);
|
|
112
|
-
const g1 = generalizeFromAst("go", src, opts);
|
|
113
|
-
expect(g1).toBe(g0);
|
|
114
|
-
expect(getGeneralizeMemoSize()).toBe(1);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it("does not write L0 when deps fingerprint is truncated", () => {
|
|
118
|
-
const deps: Record<string, string> = {};
|
|
119
|
-
let src = "";
|
|
120
|
-
for (let i = 0; i < 70; i++) {
|
|
121
|
-
deps[`/t/d${i}.js`] = `module.exports = { n: ${i} };\n`;
|
|
122
|
-
src += `const m${i} = require("./d${i}.js");\n`;
|
|
123
|
-
}
|
|
124
|
-
src += `function id(x) { return x; }\nid(m0.n);\n`;
|
|
125
|
-
const loadModule = makeLoad(deps);
|
|
126
|
-
const g0 = generalizeFromAst("id", src, {
|
|
127
|
-
refine: { loadModule, fromFile: "/t/a.js" },
|
|
128
|
-
});
|
|
129
|
-
expect(g0).toBeDefined();
|
|
130
|
-
expect(getGeneralizeMemoSize()).toBe(0);
|
|
131
|
-
});
|
|
132
|
-
});
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { callAbsMethod, getAbsProperty } from "../methods.ts";
|
|
3
|
-
import { createTemplateAbs } from "../template.ts";
|
|
4
|
-
import { strLit, numLit, abs } from "../abs.ts";
|
|
5
|
-
import { typeValueToString } from "../../type-value.ts";
|
|
6
|
-
import { absToTypeValue } from "../bridge.ts";
|
|
7
|
-
import { analyzeFn } from "../ast-eval.ts";
|
|
8
|
-
|
|
9
|
-
describe("Abs method table", () => {
|
|
10
|
-
it("template startsWith with known prefix", () => {
|
|
11
|
-
// `xy${string}`
|
|
12
|
-
const tmpl = createTemplateAbs([strLit("xy"), abs({ k: "prim", type: "string" }, undefined, undefined, "exact")]);
|
|
13
|
-
const r = callAbsMethod(tmpl, "startsWith", [strLit("x")]);
|
|
14
|
-
expect(r && typeValueToString(absToTypeValue(r))).toBe("true");
|
|
15
|
-
const r2 = callAbsMethod(tmpl, "startsWith", [strLit("a")]);
|
|
16
|
-
expect(r2 && typeValueToString(absToTypeValue(r2))).toBe("false");
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it("template length with all-literal parts", () => {
|
|
20
|
-
// createTemplateAbs with two lits folds to single lit; use mixed
|
|
21
|
-
const tmpl = createTemplateAbs([strLit("ab"), abs({ k: "prim", type: "string" }, undefined, undefined, "exact")]);
|
|
22
|
-
const len = getAbsProperty(tmpl, "length");
|
|
23
|
-
expect(len).toBeDefined();
|
|
24
|
-
expect(len!.shape.k).toBe("prim");
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("analyzeFn uses method table", () => {
|
|
28
|
-
const src = `
|
|
29
|
-
function f(x) {
|
|
30
|
-
const s = "xy" + x;
|
|
31
|
-
return s.startsWith("x");
|
|
32
|
-
}
|
|
33
|
-
`;
|
|
34
|
-
const r = analyzeFn(src, "f", [abs({ k: "prim", type: "string" }, undefined, undefined, "exact")]);
|
|
35
|
-
expect(typeValueToString(absToTypeValue(r))).toBe("true");
|
|
36
|
-
});
|
|
37
|
-
});
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { collectAbsNodeTypes, findAbsAtPosition } from "../ast-eval.ts";
|
|
3
|
-
import { formatAbs } from "../format.ts";
|
|
4
|
-
|
|
5
|
-
describe("Abs node type map", () => {
|
|
6
|
-
it("collects node → Abs for expressions", () => {
|
|
7
|
-
const source = `const x = 1 + 2;\nconst y = x * 3;\n`;
|
|
8
|
-
const map = collectAbsNodeTypes(source);
|
|
9
|
-
expect(map.size).toBeGreaterThan(0);
|
|
10
|
-
const values = [...map.values()].map((a) => formatAbs(a));
|
|
11
|
-
expect(values.some((s) => s.includes("3"))).toBe(true);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it("findAbsAtPosition picks tightest node", () => {
|
|
15
|
-
// `const x = 1 + 2;`
|
|
16
|
-
// col 10 = `1`, col 14 = `2` (0-based)
|
|
17
|
-
const source = `const x = 1 + 2;\n`;
|
|
18
|
-
const map = collectAbsNodeTypes(source);
|
|
19
|
-
const at1 = findAbsAtPosition(map, 1, 10);
|
|
20
|
-
expect(at1).toBeDefined();
|
|
21
|
-
expect(formatAbs(at1!)).toContain("1");
|
|
22
|
-
const at2 = findAbsAtPosition(map, 1, 14);
|
|
23
|
-
expect(at2).toBeDefined();
|
|
24
|
-
expect(formatAbs(at2!)).toContain("2");
|
|
25
|
-
});
|
|
26
|
-
});
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 测试用标准约束库(*.nudo.js 形态)。
|
|
3
|
-
* 金标统一:@nudo:refine <param> <name>
|
|
4
|
-
*/
|
|
5
|
-
export const STD_NUDO_SRC = `
|
|
6
|
-
export const positive = number().gt(0);
|
|
7
|
-
export const delay = number().gt(0);
|
|
8
|
-
export const nonNeg = number().ge(0);
|
|
9
|
-
export const percent = number().ge(0).le(100);
|
|
10
|
-
export const port = number().ge(1).le(65535);
|
|
11
|
-
export const small = number().lt(10);
|
|
12
|
-
export const atLeast1 = number().ge(1);
|
|
13
|
-
export const max100 = number().le(100);
|
|
14
|
-
export const intId = number().int().gt(0);
|
|
15
|
-
export const shortName = string().min(1).max(20);
|
|
16
|
-
export const positives = array(number().gt(0));
|
|
17
|
-
export const userShape = shape({
|
|
18
|
-
id: number().gt(0),
|
|
19
|
-
name: string(),
|
|
20
|
-
});
|
|
21
|
-
export const configShape = shape({
|
|
22
|
-
retries: number().ge(0).le(5),
|
|
23
|
-
label: string().optional(),
|
|
24
|
-
});
|
|
25
|
-
export const orderShape = shape({
|
|
26
|
-
user: shape({ id: number().gt(0), name: string() }),
|
|
27
|
-
tags: array(string().min(1)),
|
|
28
|
-
});
|
|
29
|
-
`;
|
|
30
|
-
|
|
31
|
-
/** 给无 import 的测试源补上标准 import 行 */
|
|
32
|
-
export function withStdImport(source: string): string {
|
|
33
|
-
if (source.includes("@nudo:import")) return source;
|
|
34
|
-
return `/// @nudo:import { positive, delay, nonNeg, percent, port, small, atLeast1, max100, intId, shortName, positives, userShape, configShape, orderShape } from "./std.nudo.js"\n${source}`;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** checkSource 用的 loadModule */
|
|
38
|
-
export function stdLoadModule(spec: string): string | undefined {
|
|
39
|
-
if (spec.includes("std.nudo") || spec.endsWith(".nudo.js")) return STD_NUDO_SRC;
|
|
40
|
-
return undefined;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export const stdOpts = {
|
|
44
|
-
loadModule: stdLoadModule,
|
|
45
|
-
fromFile: "/test/file.js",
|
|
46
|
-
};
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
analyzeFn,
|
|
4
|
-
objOf,
|
|
5
|
-
spread,
|
|
6
|
-
joinAbs,
|
|
7
|
-
joinObjects,
|
|
8
|
-
collapseToOptional,
|
|
9
|
-
numLit,
|
|
10
|
-
strLit,
|
|
11
|
-
boolLit,
|
|
12
|
-
numVar,
|
|
13
|
-
gtNum,
|
|
14
|
-
v,
|
|
15
|
-
lit,
|
|
16
|
-
litValue,
|
|
17
|
-
formatAbs,
|
|
18
|
-
formatShape,
|
|
19
|
-
isObj,
|
|
20
|
-
} from "../index.ts";
|
|
21
|
-
|
|
22
|
-
describe("objects: spread", () => {
|
|
23
|
-
it("defaults ⊕ {port:3000} keeps host and overwrites port", () => {
|
|
24
|
-
const defaults = objOf({
|
|
25
|
-
host: { value: strLit("localhost") },
|
|
26
|
-
port: { value: numLit(8080) },
|
|
27
|
-
debug: { value: boolLit(false) },
|
|
28
|
-
});
|
|
29
|
-
const over = objOf({
|
|
30
|
-
port: { value: numLit(3000) },
|
|
31
|
-
debug: { value: boolLit(true) },
|
|
32
|
-
});
|
|
33
|
-
const r = spread(defaults, over);
|
|
34
|
-
expect(isObj(r)).toBe(true);
|
|
35
|
-
if (!isObj(r)) return;
|
|
36
|
-
expect(litValue(r.shape.slots.host!.value)).toBe("localhost");
|
|
37
|
-
expect(litValue(r.shape.slots.port!.value)).toBe(3000);
|
|
38
|
-
expect(litValue(r.shape.slots.debug!.value)).toBe(true);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("createConfig from source keeps literal results per call site", () => {
|
|
42
|
-
const src = `
|
|
43
|
-
function createConfig(options) {
|
|
44
|
-
return {
|
|
45
|
-
host: "localhost",
|
|
46
|
-
port: 8080,
|
|
47
|
-
debug: false,
|
|
48
|
-
...options,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
`;
|
|
52
|
-
const r1 = analyzeFn(src, "createConfig", [
|
|
53
|
-
objOf({ port: { value: numLit(3000) }, debug: { value: boolLit(true) } }),
|
|
54
|
-
]);
|
|
55
|
-
expect(isObj(r1)).toBe(true);
|
|
56
|
-
if (!isObj(r1)) return;
|
|
57
|
-
expect(litValue(r1.shape.slots.host!.value)).toBe("localhost");
|
|
58
|
-
expect(litValue(r1.shape.slots.port!.value)).toBe(3000);
|
|
59
|
-
expect(litValue(r1.shape.slots.debug!.value)).toBe(true);
|
|
60
|
-
|
|
61
|
-
const r2 = analyzeFn(src, "createConfig", [objOf({})]);
|
|
62
|
-
if (!isObj(r2)) return;
|
|
63
|
-
expect(litValue(r2.shape.slots.port!.value)).toBe(8080);
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
describe("objects: join", () => {
|
|
68
|
-
it("same keys, different literal values → join drops term, keeps prim", () => {
|
|
69
|
-
const a = objOf({ a: { value: numLit(1) } });
|
|
70
|
-
const b = objOf({ a: { value: numLit(2) } });
|
|
71
|
-
const r = joinObjects(a, b);
|
|
72
|
-
expect(isObj(r)).toBe(true);
|
|
73
|
-
if (!isObj(r)) return;
|
|
74
|
-
expect(r.shape.slots.a!.value.shape).toEqual({ k: "prim", type: "number" });
|
|
75
|
-
expect(r.shape.slots.a!.value.term).toBeUndefined();
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it("different key sets → sum, NOT optional collapse", () => {
|
|
79
|
-
const a = objOf({ port: { value: numLit(3000) } });
|
|
80
|
-
const b = objOf({});
|
|
81
|
-
const r = joinAbs(a, b);
|
|
82
|
-
// 应是 sum 而不是 { port?: number }
|
|
83
|
-
expect(r.shape.k).toBe("sum");
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it("collapseToOptional is explicit loss and #widened", () => {
|
|
87
|
-
const a = objOf({ port: { value: numLit(3000) }, debug: { value: boolLit(true) } });
|
|
88
|
-
const b = objOf({ host: { value: strLit("x") } });
|
|
89
|
-
const sum = joinAbs(a, b);
|
|
90
|
-
expect(sum.shape.k).toBe("sum");
|
|
91
|
-
const collapsed = collapseToOptional(sum);
|
|
92
|
-
expect(collapsed.conf).toBe("widened");
|
|
93
|
-
expect(collapsed.shape.k).toBe("obj");
|
|
94
|
-
if (collapsed.shape.k !== "obj") return;
|
|
95
|
-
expect(collapsed.shape.slots.port!.optional).toBe(true);
|
|
96
|
-
expect(collapsed.shape.slots.host!.optional).toBe(true);
|
|
97
|
-
expect(collapsed.shape.slots.debug!.optional).toBe(true);
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
describe("format", () => {
|
|
102
|
-
it("formats literal number", () => {
|
|
103
|
-
expect(formatShape(numLit(4))).toBe("4");
|
|
104
|
-
});
|
|
105
|
-
it("formats with term and pred", () => {
|
|
106
|
-
const phi = gtNum(v("x"), 0);
|
|
107
|
-
const src = `
|
|
108
|
-
const add = (a, b) => a + b;
|
|
109
|
-
function scale(x) { return add(x, 1); }
|
|
110
|
-
`;
|
|
111
|
-
const r = analyzeFn(src, "scale", [numVar("x", gtNum(v("x"), 0))], phi);
|
|
112
|
-
const s = formatAbs(r);
|
|
113
|
-
expect(s).toContain("number");
|
|
114
|
-
expect(s).toContain("(x + 1)");
|
|
115
|
-
expect(s).toContain(">");
|
|
116
|
-
});
|
|
117
|
-
});
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
parseSource,
|
|
4
|
-
resetParseSourceCache,
|
|
5
|
-
getParseSourceCacheSize,
|
|
6
|
-
} from "../index.ts";
|
|
7
|
-
|
|
8
|
-
const SRC = "function add(a, b) { return a + b; }\n";
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
resetParseSourceCache();
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
describe("parseSource AST LRU", () => {
|
|
15
|
-
it("returns same File for identical source", () => {
|
|
16
|
-
const a = parseSource(SRC);
|
|
17
|
-
const b = parseSource(SRC);
|
|
18
|
-
expect(b).toBe(a);
|
|
19
|
-
expect(getParseSourceCacheSize()).toBe(1);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it("misses when source changes", () => {
|
|
23
|
-
const a = parseSource(SRC);
|
|
24
|
-
const b = parseSource(SRC + "\n// t\n");
|
|
25
|
-
expect(b).not.toBe(a);
|
|
26
|
-
expect(getParseSourceCacheSize()).toBe(2);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("does not cache errorRecovery parses", () => {
|
|
30
|
-
parseSource(SRC, { errorRecovery: true });
|
|
31
|
-
expect(getParseSourceCacheSize()).toBe(0);
|
|
32
|
-
parseSource(SRC);
|
|
33
|
-
expect(getParseSourceCacheSize()).toBe(1);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("LRU evicts oldest beyond cap", () => {
|
|
37
|
-
for (let i = 0; i < 20; i++) {
|
|
38
|
-
parseSource(`${SRC}// ${i}\n`);
|
|
39
|
-
}
|
|
40
|
-
expect(getParseSourceCacheSize()).toBeLessThanOrEqual(16);
|
|
41
|
-
});
|
|
42
|
-
});
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
number,
|
|
4
|
-
instantiateConstraint,
|
|
5
|
-
isNudoConstraint,
|
|
6
|
-
} from "../constraint.ts";
|
|
7
|
-
import {
|
|
8
|
-
execNudoModule,
|
|
9
|
-
extractNudoImports,
|
|
10
|
-
extractRefinesFromSource,
|
|
11
|
-
refineToIndexedFull,
|
|
12
|
-
} from "../refine.ts";
|
|
13
|
-
import { checkSource, pTrue } from "../index.ts";
|
|
14
|
-
import { predToString } from "../pred.ts";
|
|
15
|
-
|
|
16
|
-
const intf = `
|
|
17
|
-
export const delay = number().gt(0);
|
|
18
|
-
export const percent = number().ge(0).le(100);
|
|
19
|
-
`;
|
|
20
|
-
|
|
21
|
-
const loadModule = (spec: string): string | undefined => {
|
|
22
|
-
if (spec.includes("nudo")) return intf;
|
|
23
|
-
return undefined;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
describe("@nudo:refine <param> <constraint>", () => {
|
|
27
|
-
it("number().gt(0) instantiates to param > 0", () => {
|
|
28
|
-
const c = number().gt(0);
|
|
29
|
-
expect(isNudoConstraint(c)).toBe(true);
|
|
30
|
-
expect(predToString(instantiateConstraint(c, "ms"))).toBe("ms > 0");
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("chains bounds", () => {
|
|
34
|
-
const c = number().ge(0).le(100);
|
|
35
|
-
expect(predToString(instantiateConstraint(c, "n"))).toContain("0");
|
|
36
|
-
expect(predToString(instantiateConstraint(c, "n"))).toContain("100");
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("executes .nudo.js module", () => {
|
|
40
|
-
const exp = execNudoModule(intf);
|
|
41
|
-
expect(Object.keys(exp).sort()).toEqual(["delay", "percent"]);
|
|
42
|
-
expect(isNudoConstraint(exp.delay)).toBe(true);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it("parses named @nudo:import", () => {
|
|
46
|
-
const src = `/// @nudo:import { delay, percent } from "./x.nudo.js"\n`;
|
|
47
|
-
expect(extractNudoImports(src)).toEqual([
|
|
48
|
-
{ names: ["delay", "percent"], spec: "./x.nudo.js" },
|
|
49
|
-
]);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it("resolves ms delay to Pred", () => {
|
|
53
|
-
const src = `
|
|
54
|
-
/// @nudo:import { delay } from "./x.nudo.js"
|
|
55
|
-
/**
|
|
56
|
-
* @nudo:refine ms delay
|
|
57
|
-
*/
|
|
58
|
-
function setDelay(ms) {
|
|
59
|
-
if (ms > 0) return ms;
|
|
60
|
-
return 0;
|
|
61
|
-
}
|
|
62
|
-
`;
|
|
63
|
-
const reqs = extractRefinesFromSource(src, "setDelay", {
|
|
64
|
-
loadModule,
|
|
65
|
-
fromFile: "/t/demo.js",
|
|
66
|
-
});
|
|
67
|
-
expect(reqs.length).toBe(1);
|
|
68
|
-
expect(reqs[0]!.param).toBe("ms");
|
|
69
|
-
expect(predToString(reqs[0]!.pred)).toBe("ms > 0");
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("check catches violation from template", () => {
|
|
73
|
-
const src = `
|
|
74
|
-
/// @nudo:import { delay } from "./x.nudo.js"
|
|
75
|
-
/**
|
|
76
|
-
* @nudo:refine ms delay
|
|
77
|
-
*/
|
|
78
|
-
function setDelay(ms) {
|
|
79
|
-
if (ms > 0) return ms;
|
|
80
|
-
return 0;
|
|
81
|
-
}
|
|
82
|
-
setDelay(0);
|
|
83
|
-
`;
|
|
84
|
-
const r = checkSource("/t/demo.js", src, pTrue, {
|
|
85
|
-
loadModule,
|
|
86
|
-
fromFile: "/t/demo.js",
|
|
87
|
-
});
|
|
88
|
-
expect(r.ok).toBe(false);
|
|
89
|
-
expect(r.issues.some((i) => i.code === "nudo:constraint-violated")).toBe(true);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it("refineToIndexedFull maps param names", () => {
|
|
93
|
-
const src = `
|
|
94
|
-
/// @nudo:import { delay, percent } from "./x.nudo.js"
|
|
95
|
-
/**
|
|
96
|
-
* @nudo:refine ms delay
|
|
97
|
-
* @nudo:refine n percent
|
|
98
|
-
*/
|
|
99
|
-
function f(ms, n) {
|
|
100
|
-
return ms + n;
|
|
101
|
-
}
|
|
102
|
-
`;
|
|
103
|
-
const idx = refineToIndexedFull(src, "f", ["ms", "n"], {
|
|
104
|
-
loadModule,
|
|
105
|
-
fromFile: "/t/f.js",
|
|
106
|
-
});
|
|
107
|
-
expect(idx.length).toBe(2);
|
|
108
|
-
});
|
|
109
|
-
});
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { stableAnalyzeKeySource } from "../stable-source-key.ts";
|
|
3
|
-
|
|
4
|
-
describe("stableAnalyzeKeySource trailing block comments", () => {
|
|
5
|
-
it("strips trailing non-nudo block comments", () => {
|
|
6
|
-
const a = "function f() { return 1; }\n";
|
|
7
|
-
const b = "function f() { return 1; }\n/* trailing note */\n";
|
|
8
|
-
const c = "function f() { return 1; }\n/*\n * multi\n * line\n */\n";
|
|
9
|
-
expect(stableAnalyzeKeySource(b)).toBe(stableAnalyzeKeySource(a));
|
|
10
|
-
expect(stableAnalyzeKeySource(c)).toBe(stableAnalyzeKeySource(a));
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it("keeps trailing block comments that carry @nudo", () => {
|
|
14
|
-
const a = "function f() { return 1; }\n";
|
|
15
|
-
const b = 'function f() { return 1; }\n/* @nudo:case "x" (1) */\n';
|
|
16
|
-
expect(stableAnalyzeKeySource(b)).not.toBe(stableAnalyzeKeySource(a));
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it("does not strip block-comment markers inside code lines", () => {
|
|
20
|
-
const src = 'const s = "/* not a comment */";\n';
|
|
21
|
-
expect(stableAnalyzeKeySource(src).trimEnd()).toBe(src.trimEnd());
|
|
22
|
-
});
|
|
23
|
-
});
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { stableAnalyzeKeySource } from "../stable-source-key.ts";
|
|
3
|
-
|
|
4
|
-
describe("stableAnalyzeKeySource", () => {
|
|
5
|
-
it("strips trailing non-nudo line comments", () => {
|
|
6
|
-
const a = "function f() { return 1; }\n";
|
|
7
|
-
const b = "function f() { return 1; }\n// hello\n";
|
|
8
|
-
const c = "function f() { return 1; }\n\n// t0\n// t1\n";
|
|
9
|
-
expect(stableAnalyzeKeySource(b)).toBe(stableAnalyzeKeySource(a));
|
|
10
|
-
expect(stableAnalyzeKeySource(c)).toBe(stableAnalyzeKeySource(a));
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it("keeps trailing @nudo comments", () => {
|
|
14
|
-
const a = "function f() { return 1; }\n";
|
|
15
|
-
const b = "function f() { return 1; }\n// @nudo:case \"x\" (1)\n";
|
|
16
|
-
expect(stableAnalyzeKeySource(b)).not.toBe(stableAnalyzeKeySource(a));
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it("keeps JSDoc nudo blocks", () => {
|
|
20
|
-
const a = "/**\n * @nudo:case \"c\" (1)\n */\nfunction f(x) { return x; }\n";
|
|
21
|
-
const b = a + "// trailing\n";
|
|
22
|
-
expect(stableAnalyzeKeySource(b)).toBe(stableAnalyzeKeySource(a));
|
|
23
|
-
expect(stableAnalyzeKeySource(a)).toContain("@nudo:case");
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("normalizes trailing blank lines", () => {
|
|
27
|
-
const a = "let x = 1;\n";
|
|
28
|
-
const b = "let x = 1;\n\n\n";
|
|
29
|
-
expect(stableAnalyzeKeySource(b)).toBe(stableAnalyzeKeySource(a));
|
|
30
|
-
});
|
|
31
|
-
});
|