@nudojs/cli 0.2.1 → 0.3.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/package.json +12 -7
- package/src/abs-route.ts +273 -0
- package/src/builtins/builtin-prototype.ts +279 -0
- package/src/builtins/builtin-static.ts +260 -0
- package/src/eval-binary.ts +113 -0
- package/src/evaluator-api.ts +3 -0
- package/src/evaluator.ts +174 -633
- package/src/index.ts +373 -270
- package/src/narrowing.ts +7 -0
- package/src/phi-from-test.ts +42 -0
- package/src/run-test.ts +92 -0
- package/src/term-registry.ts +96 -0
- package/CHANGELOG.md +0 -41
- package/dist/chunk-3HCPXPEW.js +0 -5286
- package/dist/evaluator-api.d.ts +0 -151
- package/dist/evaluator-api.js +0 -214
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -4035
- package/examples/real-world.js +0 -66
- package/examples/validate-real-world.ts +0 -160
- package/src/__tests__/builtin-advanced.test.ts +0 -292
- package/src/__tests__/builtin-api.test.ts +0 -308
- package/src/__tests__/builtin-functions.test.ts +0 -313
- package/src/__tests__/builtin-map.test.ts +0 -129
- package/src/__tests__/builtin-promise.test.ts +0 -150
- package/src/__tests__/builtin-regexp.test.ts +0 -133
- package/src/__tests__/builtin-set.test.ts +0 -163
- package/src/__tests__/builtin-url.test.ts +0 -256
- package/src/__tests__/builtin-web.test.ts +0 -340
- package/src/__tests__/combination-scenarios.test.ts +0 -384
- package/src/__tests__/edge-cases.test.ts +0 -749
- package/src/__tests__/evaluator-loops.test.ts +0 -283
- package/src/__tests__/evaluator-nullish-coalescing.test.ts +0 -86
- package/src/__tests__/evaluator-optional-chaining.test.ts +0 -37
- package/src/__tests__/evaluator-phase2.test.ts +0 -564
- package/src/__tests__/evaluator-phase3.test.ts +0 -864
- package/src/__tests__/evaluator-refined.test.ts +0 -121
- package/src/__tests__/evaluator-string-methods.test.ts +0 -151
- package/src/__tests__/evaluator.test.ts +0 -943
- package/src/__tests__/mock-arrow-function.test.ts +0 -169
- package/src/__tests__/mock-comparison.test.ts +0 -221
- package/src/__tests__/mock-comprehensive.test.ts +0 -250
- package/src/__tests__/mock-final-integration.test.ts +0 -263
- package/src/__tests__/mock-helpers.test.ts +0 -174
- package/src/__tests__/mock-realworld.test.ts +0 -191
- package/src/__tests__/mock-rest-debug.test.ts +0 -110
- package/src/__tests__/mock-rest-edge.test.ts +0 -176
- package/src/__tests__/mock-sinon-runtime.test.ts +0 -168
- package/src/__tests__/narrowing-discriminated.test.ts +0 -35
- package/src/__tests__/narrowing-in-operator.test.ts +0 -56
- package/src/__tests__/narrowing-switch.test.ts +0 -56
- package/src/__tests__/narrowing-truthiness.test.ts +0 -52
- package/src/__tests__/narrowing.test.ts +0 -82
- package/src/__tests__/nudo-mock-syntax.test.ts +0 -245
- package/src/__tests__/realworld-patterns.test.ts +0 -430
- package/src/__tests__/syntax-sugar.test.ts +0 -345
- package/src/builtins/index.ts +0 -61
- package/tsconfig.json +0 -9
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { T, typeValueEquals, typeValueToString, createEnvironment } from "@nudojs/core";
|
|
3
|
-
import { parse } from "@nudojs/parser";
|
|
4
|
-
import { narrow } from "../narrowing.ts";
|
|
5
|
-
import type { ExpressionStatement } from "@babel/types";
|
|
6
|
-
|
|
7
|
-
function getTestExpr(source: string) {
|
|
8
|
-
const ast = parse(source);
|
|
9
|
-
const stmt = ast.program.body[0] as ExpressionStatement;
|
|
10
|
-
return stmt.expression;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
describe("narrow: typeof", () => {
|
|
14
|
-
it("narrows typeof x === 'number' on union", () => {
|
|
15
|
-
const env = createEnvironment();
|
|
16
|
-
env.bind("x", T.union(T.number, T.string));
|
|
17
|
-
const expr = getTestExpr('typeof x === "number"');
|
|
18
|
-
const [trueEnv, falseEnv] = narrow(expr, env);
|
|
19
|
-
expect(typeValueEquals(trueEnv.lookup("x"), T.number)).toBe(true);
|
|
20
|
-
expect(typeValueEquals(falseEnv.lookup("x"), T.string)).toBe(true);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it("narrows typeof x === 'string' on union", () => {
|
|
24
|
-
const env = createEnvironment();
|
|
25
|
-
env.bind("x", T.union(T.number, T.string, T.boolean));
|
|
26
|
-
const expr = getTestExpr('typeof x === "string"');
|
|
27
|
-
const [trueEnv, falseEnv] = narrow(expr, env);
|
|
28
|
-
expect(typeValueEquals(trueEnv.lookup("x"), T.string)).toBe(true);
|
|
29
|
-
const falseType = falseEnv.lookup("x");
|
|
30
|
-
expect(typeValueToString(falseType)).toBe("number | boolean");
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("narrows typeof x !== 'number' (inverted)", () => {
|
|
34
|
-
const env = createEnvironment();
|
|
35
|
-
env.bind("x", T.union(T.number, T.string));
|
|
36
|
-
const expr = getTestExpr('typeof x !== "number"');
|
|
37
|
-
const [trueEnv, falseEnv] = narrow(expr, env);
|
|
38
|
-
expect(typeValueEquals(trueEnv.lookup("x"), T.string)).toBe(true);
|
|
39
|
-
expect(typeValueEquals(falseEnv.lookup("x"), T.number)).toBe(true);
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
describe("narrow: strict equality", () => {
|
|
44
|
-
it("narrows x === null", () => {
|
|
45
|
-
const env = createEnvironment();
|
|
46
|
-
env.bind("x", T.union(T.null, T.number));
|
|
47
|
-
const expr = getTestExpr("x === null");
|
|
48
|
-
const [trueEnv, falseEnv] = narrow(expr, env);
|
|
49
|
-
expect(typeValueEquals(trueEnv.lookup("x"), T.null)).toBe(true);
|
|
50
|
-
expect(typeValueEquals(falseEnv.lookup("x"), T.number)).toBe(true);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("narrows x === 1", () => {
|
|
54
|
-
const env = createEnvironment();
|
|
55
|
-
env.bind("x", T.union(T.literal(1), T.literal(2), T.literal(3)));
|
|
56
|
-
const expr = getTestExpr("x === 1");
|
|
57
|
-
const [trueEnv, falseEnv] = narrow(expr, env);
|
|
58
|
-
expect(typeValueEquals(trueEnv.lookup("x"), T.literal(1))).toBe(true);
|
|
59
|
-
const falseType = falseEnv.lookup("x");
|
|
60
|
-
expect(typeValueToString(falseType)).toBe("2 | 3");
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("narrows x !== 1 (inverted)", () => {
|
|
64
|
-
const env = createEnvironment();
|
|
65
|
-
env.bind("x", T.union(T.literal(1), T.literal(2)));
|
|
66
|
-
const expr = getTestExpr("x !== 1");
|
|
67
|
-
const [trueEnv, falseEnv] = narrow(expr, env);
|
|
68
|
-
expect(typeValueEquals(trueEnv.lookup("x"), T.literal(2))).toBe(true);
|
|
69
|
-
expect(typeValueEquals(falseEnv.lookup("x"), T.literal(1))).toBe(true);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
describe("narrow: negation", () => {
|
|
74
|
-
it("narrows !(typeof x === 'number')", () => {
|
|
75
|
-
const env = createEnvironment();
|
|
76
|
-
env.bind("x", T.union(T.number, T.string));
|
|
77
|
-
const expr = getTestExpr('!(typeof x === "number")');
|
|
78
|
-
const [trueEnv, falseEnv] = narrow(expr, env);
|
|
79
|
-
expect(typeValueEquals(trueEnv.lookup("x"), T.string)).toBe(true);
|
|
80
|
-
expect(typeValueEquals(falseEnv.lookup("x"), T.number)).toBe(true);
|
|
81
|
-
});
|
|
82
|
-
});
|
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { parse, extractDirectives, type CaseDirective, type MockDirective, parseTypeValueExpr } from "@nudojs/parser";
|
|
3
|
-
import { typeValueToString, createEnvironment, T, mockHelperToTypeValue } from "@nudojs/core";
|
|
4
|
-
import { evaluateFunctionFull } from "../evaluator.js";
|
|
5
|
-
|
|
6
|
-
function runNudoTest(source: string): { name: string; caseName: string; result: string }[] {
|
|
7
|
-
const ast = parse(source);
|
|
8
|
-
const directives = extractDirectives(ast);
|
|
9
|
-
const env = createEnvironment();
|
|
10
|
-
const results: { name: string; caseName: string; result: string }[] = [];
|
|
11
|
-
|
|
12
|
-
for (const fn of directives) {
|
|
13
|
-
for (const d of fn.directives) {
|
|
14
|
-
if (d.kind === "mock") {
|
|
15
|
-
if (d.arrowFn) {
|
|
16
|
-
const fnType = T.fn(d.arrowFn.params, d.arrowFn.body, env);
|
|
17
|
-
(fnType as any)._paramPatterns = d.arrowFn.paramPatterns;
|
|
18
|
-
env.bind(d.name, fnType);
|
|
19
|
-
} else if (d.nudoMock) {
|
|
20
|
-
const typeVal = mockHelperToTypeValue(d.nudoMock, env);
|
|
21
|
-
env.bind(d.name, typeVal);
|
|
22
|
-
} else if (d.sinonExpr) {
|
|
23
|
-
// Handle sinon expressions
|
|
24
|
-
const body = { type: "BlockStatement", body: [] } as any;
|
|
25
|
-
const fn = T.fn(["...args"], body, env);
|
|
26
|
-
if (d.sinonExpr.returnValue) {
|
|
27
|
-
(fn as any)._directReturn = d.sinonExpr.returnValue;
|
|
28
|
-
} else if (d.sinonExpr.resolvedValue) {
|
|
29
|
-
(fn as any)._directReturn = T.promise(d.sinonExpr.resolvedValue);
|
|
30
|
-
} else if (d.sinonExpr.rejectedValue) {
|
|
31
|
-
(fn as any)._directReturn = T.never;
|
|
32
|
-
} else {
|
|
33
|
-
(fn as any)._directReturn = T.unknown;
|
|
34
|
-
}
|
|
35
|
-
env.bind(d.name, fn);
|
|
36
|
-
} else if (d.expression) {
|
|
37
|
-
env.bind(d.name, parseTypeValueExpr(d.expression));
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const caseDirectives = fn.directives.filter((d): d is CaseDirective => d.kind === "case");
|
|
43
|
-
for (const dir of caseDirectives) {
|
|
44
|
-
const result = evaluateFunctionFull(fn.node, dir.args, env);
|
|
45
|
-
results.push({
|
|
46
|
-
name: fn.name,
|
|
47
|
-
caseName: dir.name,
|
|
48
|
-
result: typeValueToString(result.value),
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return results;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
describe("Nudo Mock Helper Syntax", () => {
|
|
56
|
-
it("stub() should bind as a function returning unknown", () => {
|
|
57
|
-
const results = runNudoTest(`
|
|
58
|
-
// @nudo:mock handler = stub()
|
|
59
|
-
// @nudo:case "test" (42)
|
|
60
|
-
function testFn(x) {
|
|
61
|
-
return handler(x);
|
|
62
|
-
}
|
|
63
|
-
`);
|
|
64
|
-
expect(results[0].result).toBe("unknown");
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("stub().returns({ status: 200 }) should return the specified value", () => {
|
|
68
|
-
const results = runNudoTest(`
|
|
69
|
-
// @nudo:mock fetch = stub().returns({ status: 200, data: { ok: true } })
|
|
70
|
-
// @nudo:case "api" ("/users")
|
|
71
|
-
function fetchUsers(url) {
|
|
72
|
-
return fetch(url);
|
|
73
|
-
}
|
|
74
|
-
`);
|
|
75
|
-
expect(results[0].result).toContain("status: 200");
|
|
76
|
-
expect(results[0].result).toContain("ok: true");
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it("stub().resolves('hello') should return Promise<\"hello\">", () => {
|
|
80
|
-
const results = runNudoTest(`
|
|
81
|
-
// @nudo:mock fetchData = stub().resolves("hello")
|
|
82
|
-
// @nudo:case "async" ()
|
|
83
|
-
function getData() {
|
|
84
|
-
return fetchData();
|
|
85
|
-
}
|
|
86
|
-
`);
|
|
87
|
-
expect(results[0].result).toBe('Promise<"hello">');
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it("spy() should bind as a function returning unknown", () => {
|
|
91
|
-
const results = runNudoTest(`
|
|
92
|
-
// @nudo:mock listener = spy()
|
|
93
|
-
// @nudo:case "event" ({ type: "click" })
|
|
94
|
-
function handleEvent(event) {
|
|
95
|
-
return listener(event);
|
|
96
|
-
}
|
|
97
|
-
`);
|
|
98
|
-
expect(results[0].result).toBe("unknown");
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("mock() should bind as a function returning unknown", () => {
|
|
102
|
-
const results = runNudoTest(`
|
|
103
|
-
// @nudo:mock service = mock()
|
|
104
|
-
// @nudo:case "call" ()
|
|
105
|
-
function useService() {
|
|
106
|
-
return service();
|
|
107
|
-
}
|
|
108
|
-
`);
|
|
109
|
-
expect(results[0].result).toBe("unknown");
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it("stub().returns with complex object", () => {
|
|
113
|
-
const results = runNudoTest(`
|
|
114
|
-
// @nudo:mock httpClient = stub().returns({ status: 200, data: { users: [{ id: 1, name: "Alice" }] } })
|
|
115
|
-
// @nudo:case "users" ()
|
|
116
|
-
function getUsers() {
|
|
117
|
-
const response = httpClient();
|
|
118
|
-
return response.data.users;
|
|
119
|
-
}
|
|
120
|
-
`);
|
|
121
|
-
expect(results[0].result).toContain("id: 1");
|
|
122
|
-
expect(results[0].result).toContain("name: \"Alice\"");
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it("mixed nudo mocks and arrow function mocks", () => {
|
|
126
|
-
const results = runNudoTest(`
|
|
127
|
-
// @nudo:mock validate = (x) => x !== null
|
|
128
|
-
// @nudo:mock transform = stub().returns({ processed: true })
|
|
129
|
-
// @nudo:case "mix" (42)
|
|
130
|
-
function process(x) {
|
|
131
|
-
if (!validate(x)) return null;
|
|
132
|
-
return transform(x);
|
|
133
|
-
}
|
|
134
|
-
`);
|
|
135
|
-
expect(results[0].result).toContain("processed: true");
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
describe("Chain call syntax", () => {
|
|
140
|
-
it("stub().returns(v).onFirstCall() should parse and use returns value", () => {
|
|
141
|
-
const results = runNudoTest(`
|
|
142
|
-
// @nudo:mock handler = stub().returns(42).onFirstCall()
|
|
143
|
-
// @nudo:case "test" ()
|
|
144
|
-
function fn() {
|
|
145
|
-
return handler();
|
|
146
|
-
}
|
|
147
|
-
`);
|
|
148
|
-
expect(results[0].result).toBe("42");
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it("stub().callsFake(expr) should parse", () => {
|
|
152
|
-
const results = runNudoTest(`
|
|
153
|
-
// @nudo:mock transform = stub().callsFake((x) => x)
|
|
154
|
-
// @nudo:case "test" (21)
|
|
155
|
-
function fn(x) {
|
|
156
|
-
return transform(x);
|
|
157
|
-
}
|
|
158
|
-
`);
|
|
159
|
-
expect(results[0].result).toBeDefined();
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it("spy().returns(v) should parse", () => {
|
|
163
|
-
const results = runNudoTest(`
|
|
164
|
-
// @nudo:mock listener = spy().returns({ handled: true })
|
|
165
|
-
// @nudo:case "test" ()
|
|
166
|
-
function fn() {
|
|
167
|
-
return listener();
|
|
168
|
-
}
|
|
169
|
-
`);
|
|
170
|
-
expect(results[0].result).toContain("handled: true");
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it("stub().withArgs(a, b).returns(v) should parse", () => {
|
|
174
|
-
const results = runNudoTest(`
|
|
175
|
-
// @nudo:mock fetch = stub().withArgs("GET", "/api").returns({ status: 200 })
|
|
176
|
-
// @nudo:case "test" ()
|
|
177
|
-
function fn() {
|
|
178
|
-
return fetch("GET", "/api");
|
|
179
|
-
}
|
|
180
|
-
`);
|
|
181
|
-
expect(results[0].result).toContain("status: 200");
|
|
182
|
-
});
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
describe("callsFake execution semantics (unified stub/sinon paths)", () => {
|
|
186
|
-
it("stub().callsFake lifts the fake onto arrowFn (same binding as plain arrow mocks)", () => {
|
|
187
|
-
const ast = parse(`
|
|
188
|
-
// @nudo:mock transform = stub().callsFake((x) => ({ v: x }))
|
|
189
|
-
function applyTransform(x) {
|
|
190
|
-
return transform(x);
|
|
191
|
-
}
|
|
192
|
-
`);
|
|
193
|
-
const directives = extractDirectives(ast);
|
|
194
|
-
const mockDir = directives[0].directives.find((d): d is MockDirective => d.kind === "mock")!;
|
|
195
|
-
// 与 @nudo:mock f = (x) => ... 相同的 arrowFn 形态:T.fn + _paramPatterns + 全局 env
|
|
196
|
-
expect(mockDir.arrowFn).toBeDefined();
|
|
197
|
-
expect(mockDir.arrowFn!.params).toEqual(["x"]);
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
it("stub().callsFake((x) => ({ v: x })) direct call with 21 yields { v: 21 }", () => {
|
|
201
|
-
const results = runNudoTest(`
|
|
202
|
-
// @nudo:mock transform = stub().callsFake((x) => ({ v: x }))
|
|
203
|
-
// @nudo:case "direct" (21)
|
|
204
|
-
function applyTransform(x) {
|
|
205
|
-
return transform(x);
|
|
206
|
-
}
|
|
207
|
-
`);
|
|
208
|
-
expect(results[0].result).toBe("{ v: 21 }");
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
it("sinon.stub().callsFake((x) => ({ v: x })) direct call with 21 yields { v: 21 }", () => {
|
|
212
|
-
const results = runNudoTest(`
|
|
213
|
-
// @nudo:mock transform = sinon.stub().callsFake((x) => ({ v: x }))
|
|
214
|
-
// @nudo:case "direct" (21)
|
|
215
|
-
function applyTransform(x) {
|
|
216
|
-
return transform(x);
|
|
217
|
-
}
|
|
218
|
-
`);
|
|
219
|
-
expect(results[0].result).toBe("{ v: 21 }");
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
it("callsFake propagates precisely through arr.map(transform) HOF", () => {
|
|
223
|
-
const results = runNudoTest(`
|
|
224
|
-
// @nudo:mock transform = stub().callsFake((x) => ({ v: x }))
|
|
225
|
-
// @nudo:case "hof" ([1, 2, 3])
|
|
226
|
-
function mapAll(arr) {
|
|
227
|
-
return arr.map(transform);
|
|
228
|
-
}
|
|
229
|
-
`);
|
|
230
|
-
expect(results[0].result).toBe("[{ v: 1 }, { v: 2 }, { v: 3 }]");
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it("withArgs(...).returns(...) mixed chain: matched args win, unmatched use default stub behavior", () => {
|
|
234
|
-
const results = runNudoTest(`
|
|
235
|
-
// @nudo:mock handler = stub().withArgs(21).returns("hit")
|
|
236
|
-
// @nudo:case "match" (21)
|
|
237
|
-
// @nudo:case "miss" (22)
|
|
238
|
-
function call(x) {
|
|
239
|
-
return handler(x);
|
|
240
|
-
}
|
|
241
|
-
`);
|
|
242
|
-
expect(results[0].result).toBe('"hit"');
|
|
243
|
-
expect(results[1].result).toBe("unknown");
|
|
244
|
-
});
|
|
245
|
-
});
|