@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.
Files changed (123) hide show
  1. package/dist/chunk-BU4A6XJ3.js +6856 -0
  2. package/dist/exec.d.ts +2 -0
  3. package/dist/exec.js +168 -0
  4. package/dist/index-CVYjXnwU.d.ts +902 -0
  5. package/dist/index.d.ts +64 -710
  6. package/dist/index.js +3258 -8426
  7. package/package.json +18 -5
  8. package/CHANGELOG.md +0 -34
  9. package/src/__tests__/environment.test.ts +0 -48
  10. package/src/__tests__/fn-sig-impl.test.ts +0 -57
  11. package/src/__tests__/ops-refined.test.ts +0 -196
  12. package/src/__tests__/ops.test.ts +0 -84
  13. package/src/__tests__/type-value-phase2.test.ts +0 -117
  14. package/src/__tests__/type-value-phase3.test.ts +0 -94
  15. package/src/__tests__/type-value-refined.test.ts +0 -210
  16. package/src/__tests__/type-value.test.ts +0 -319
  17. package/src/algebra/__tests__/abs-native.test.ts +0 -176
  18. package/src/algebra/__tests__/arithmetic.test.ts +0 -170
  19. package/src/algebra/__tests__/ast-eval.test.ts +0 -154
  20. package/src/algebra/__tests__/bridge.test.ts +0 -160
  21. package/src/algebra/__tests__/call-budget.test.ts +0 -117
  22. package/src/algebra/__tests__/check-case-consistency.test.ts +0 -69
  23. package/src/algebra/__tests__/check-gold.test.ts +0 -290
  24. package/src/algebra/__tests__/check-json.test.ts +0 -57
  25. package/src/algebra/__tests__/check-mini-repo.test.ts +0 -49
  26. package/src/algebra/__tests__/check-real-commander.test.ts +0 -43
  27. package/src/algebra/__tests__/check-real-packages.test.ts +0 -165
  28. package/src/algebra/__tests__/check-recall-gold.test.ts +0 -1024
  29. package/src/algebra/__tests__/check-require-dep-memo.test.ts +0 -45
  30. package/src/algebra/__tests__/check-return-contract.test.ts +0 -90
  31. package/src/algebra/__tests__/check-shape-gold.test.ts +0 -303
  32. package/src/algebra/__tests__/check-source-memo.test.ts +0 -204
  33. package/src/algebra/__tests__/check.test.ts +0 -69
  34. package/src/algebra/__tests__/denote.test.ts +0 -77
  35. package/src/algebra/__tests__/diagnostics.test.ts +0 -64
  36. package/src/algebra/__tests__/differential.test.ts +0 -124
  37. package/src/algebra/__tests__/div-mod.test.ts +0 -38
  38. package/src/algebra/__tests__/example0.test.ts +0 -103
  39. package/src/algebra/__tests__/exec-analyze-throws.test.ts +0 -60
  40. package/src/algebra/__tests__/exec-async.test.ts +0 -46
  41. package/src/algebra/__tests__/exec-bpath.test.ts +0 -193
  42. package/src/algebra/__tests__/exec-class.test.ts +0 -56
  43. package/src/algebra/__tests__/exec-e2e.test.ts +0 -97
  44. package/src/algebra/__tests__/exec-generator.test.ts +0 -53
  45. package/src/algebra/__tests__/exec-inherit-default.test.ts +0 -111
  46. package/src/algebra/__tests__/exec-optional-nested.test.ts +0 -87
  47. package/src/algebra/__tests__/exec-rest.test.ts +0 -55
  48. package/src/algebra/__tests__/exec-spread-forof.test.ts +0 -85
  49. package/src/algebra/__tests__/exec-static-computed.test.ts +0 -61
  50. package/src/algebra/__tests__/exec-switch.test.ts +0 -74
  51. package/src/algebra/__tests__/exec-trycatch.test.ts +0 -71
  52. package/src/algebra/__tests__/fn-fp-memo.test.ts +0 -166
  53. package/src/algebra/__tests__/fn-fp-multidecl.test.ts +0 -49
  54. package/src/algebra/__tests__/format.test.ts +0 -34
  55. package/src/algebra/__tests__/generalize-memo.test.ts +0 -285
  56. package/src/algebra/__tests__/generalize.test.ts +0 -113
  57. package/src/algebra/__tests__/hash-source.test.ts +0 -19
  58. package/src/algebra/__tests__/hof.test.ts +0 -80
  59. package/src/algebra/__tests__/inlay.test.ts +0 -30
  60. package/src/algebra/__tests__/language.test.ts +0 -77
  61. package/src/algebra/__tests__/leq.test.ts +0 -109
  62. package/src/algebra/__tests__/load-deps-fp-memo.test.ts +0 -132
  63. package/src/algebra/__tests__/methods.test.ts +0 -37
  64. package/src/algebra/__tests__/node-types.test.ts +0 -26
  65. package/src/algebra/__tests__/nudo-constraints.ts +0 -46
  66. package/src/algebra/__tests__/objects.test.ts +0 -117
  67. package/src/algebra/__tests__/parse-source-cache.test.ts +0 -42
  68. package/src/algebra/__tests__/refine-import.test.ts +0 -109
  69. package/src/algebra/__tests__/stable-source-key-block.test.ts +0 -23
  70. package/src/algebra/__tests__/stable-source-key.test.ts +0 -31
  71. package/src/algebra/__tests__/surface.test.ts +0 -41
  72. package/src/algebra/__tests__/this-inherit.test.ts +0 -130
  73. package/src/algebra/abs-fn.ts +0 -49
  74. package/src/algebra/abs-modules.ts +0 -139
  75. package/src/algebra/abs.ts +0 -206
  76. package/src/algebra/arithmetic.ts +0 -691
  77. package/src/algebra/ast-eval.ts +0 -1763
  78. package/src/algebra/bridge.ts +0 -350
  79. package/src/algebra/builtins.ts +0 -373
  80. package/src/algebra/check-report.ts +0 -150
  81. package/src/algebra/check.ts +0 -1867
  82. package/src/algebra/constraint.ts +0 -304
  83. package/src/algebra/denote.ts +0 -166
  84. package/src/algebra/diagnostics.ts +0 -210
  85. package/src/algebra/exec/call.ts +0 -29
  86. package/src/algebra/exec/calls.ts +0 -96
  87. package/src/algebra/exec/class-registry.ts +0 -29
  88. package/src/algebra/exec/class.ts +0 -329
  89. package/src/algebra/exec/index.ts +0 -6
  90. package/src/algebra/exec/member-diag.ts +0 -141
  91. package/src/algebra/exec/run.ts +0 -341
  92. package/src/algebra/exec/runtime.ts +0 -583
  93. package/src/algebra/exec/transpile.ts +0 -993
  94. package/src/algebra/fn-fp.ts +0 -294
  95. package/src/algebra/format.ts +0 -91
  96. package/src/algebra/generalize.ts +0 -776
  97. package/src/algebra/hash-source.ts +0 -27
  98. package/src/algebra/index.ts +0 -34
  99. package/src/algebra/inlay.ts +0 -198
  100. package/src/algebra/language.ts +0 -289
  101. package/src/algebra/leak.ts +0 -68
  102. package/src/algebra/leq.ts +0 -301
  103. package/src/algebra/load-deps-fp.ts +0 -97
  104. package/src/algebra/methods.ts +0 -128
  105. package/src/algebra/objects.ts +0 -251
  106. package/src/algebra/parse-source.ts +0 -64
  107. package/src/algebra/phi.ts +0 -42
  108. package/src/algebra/pred.ts +0 -395
  109. package/src/algebra/refine.ts +0 -227
  110. package/src/algebra/stable-source-key.ts +0 -81
  111. package/src/algebra/surface.ts +0 -206
  112. package/src/algebra/template.ts +0 -143
  113. package/src/algebra/term.ts +0 -119
  114. package/src/environment.ts +0 -74
  115. package/src/index.ts +0 -73
  116. package/src/mock-helpers.ts +0 -166
  117. package/src/ops.ts +0 -191
  118. package/src/refinements/range.ts +0 -86
  119. package/src/refinements/template-predicates.ts +0 -11
  120. package/src/refinements/template.ts +0 -176
  121. package/src/strip-types.ts +0 -236
  122. package/src/type-value.ts +0 -513
  123. 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
- });