@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.
Files changed (58) hide show
  1. package/package.json +12 -7
  2. package/src/abs-route.ts +273 -0
  3. package/src/builtins/builtin-prototype.ts +279 -0
  4. package/src/builtins/builtin-static.ts +260 -0
  5. package/src/eval-binary.ts +113 -0
  6. package/src/evaluator-api.ts +3 -0
  7. package/src/evaluator.ts +174 -633
  8. package/src/index.ts +373 -270
  9. package/src/narrowing.ts +7 -0
  10. package/src/phi-from-test.ts +42 -0
  11. package/src/run-test.ts +92 -0
  12. package/src/term-registry.ts +96 -0
  13. package/CHANGELOG.md +0 -41
  14. package/dist/chunk-3HCPXPEW.js +0 -5286
  15. package/dist/evaluator-api.d.ts +0 -151
  16. package/dist/evaluator-api.js +0 -214
  17. package/dist/index.d.ts +0 -2
  18. package/dist/index.js +0 -4035
  19. package/examples/real-world.js +0 -66
  20. package/examples/validate-real-world.ts +0 -160
  21. package/src/__tests__/builtin-advanced.test.ts +0 -292
  22. package/src/__tests__/builtin-api.test.ts +0 -308
  23. package/src/__tests__/builtin-functions.test.ts +0 -313
  24. package/src/__tests__/builtin-map.test.ts +0 -129
  25. package/src/__tests__/builtin-promise.test.ts +0 -150
  26. package/src/__tests__/builtin-regexp.test.ts +0 -133
  27. package/src/__tests__/builtin-set.test.ts +0 -163
  28. package/src/__tests__/builtin-url.test.ts +0 -256
  29. package/src/__tests__/builtin-web.test.ts +0 -340
  30. package/src/__tests__/combination-scenarios.test.ts +0 -384
  31. package/src/__tests__/edge-cases.test.ts +0 -749
  32. package/src/__tests__/evaluator-loops.test.ts +0 -283
  33. package/src/__tests__/evaluator-nullish-coalescing.test.ts +0 -86
  34. package/src/__tests__/evaluator-optional-chaining.test.ts +0 -37
  35. package/src/__tests__/evaluator-phase2.test.ts +0 -564
  36. package/src/__tests__/evaluator-phase3.test.ts +0 -864
  37. package/src/__tests__/evaluator-refined.test.ts +0 -121
  38. package/src/__tests__/evaluator-string-methods.test.ts +0 -151
  39. package/src/__tests__/evaluator.test.ts +0 -943
  40. package/src/__tests__/mock-arrow-function.test.ts +0 -169
  41. package/src/__tests__/mock-comparison.test.ts +0 -221
  42. package/src/__tests__/mock-comprehensive.test.ts +0 -250
  43. package/src/__tests__/mock-final-integration.test.ts +0 -263
  44. package/src/__tests__/mock-helpers.test.ts +0 -174
  45. package/src/__tests__/mock-realworld.test.ts +0 -191
  46. package/src/__tests__/mock-rest-debug.test.ts +0 -110
  47. package/src/__tests__/mock-rest-edge.test.ts +0 -176
  48. package/src/__tests__/mock-sinon-runtime.test.ts +0 -168
  49. package/src/__tests__/narrowing-discriminated.test.ts +0 -35
  50. package/src/__tests__/narrowing-in-operator.test.ts +0 -56
  51. package/src/__tests__/narrowing-switch.test.ts +0 -56
  52. package/src/__tests__/narrowing-truthiness.test.ts +0 -52
  53. package/src/__tests__/narrowing.test.ts +0 -82
  54. package/src/__tests__/nudo-mock-syntax.test.ts +0 -245
  55. package/src/__tests__/realworld-patterns.test.ts +0 -430
  56. package/src/__tests__/syntax-sugar.test.ts +0 -345
  57. package/src/builtins/index.ts +0 -61
  58. package/tsconfig.json +0 -9
@@ -1,308 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { parse, extractDirectives, type CaseDirective, parseTypeValueExpr } from "@nudojs/parser";
3
- import { typeValueToString, createEnvironment, T } from "@nudojs/core";
4
- import { evaluateFunctionFull } from "../evaluator.js";
5
-
6
- function runTest(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
- const caseDirectives = fn.directives.filter((d): d is CaseDirective => d.kind === "case");
14
- for (const dir of caseDirectives) {
15
- const result = evaluateFunctionFull(fn.node, dir.args, env);
16
- results.push({
17
- name: fn.name,
18
- caseName: dir.name,
19
- result: typeValueToString(result.value),
20
- });
21
- }
22
- }
23
- return results;
24
- }
25
-
26
- describe("Built-in API Tests", () => {
27
- it("Date.now() should return number", () => {
28
- const results = runTest(`
29
- // @nudo:case "now" ()
30
- function getTimestamp() {
31
- return Date.now();
32
- }
33
- `);
34
- console.log("Date.now() result:", results[0].result);
35
- expect(results[0].result).toBe("number");
36
- });
37
-
38
- it("Math.floor() should return number", () => {
39
- const results = runTest(`
40
- // @nudo:case "floor" (3.7)
41
- function floorIt(x) {
42
- return Math.floor(x);
43
- }
44
- `);
45
- console.log("Math.floor() result:", results[0].result);
46
- expect(results[0].result).toBe("number");
47
- });
48
-
49
- it("Math.random() should return number", () => {
50
- const results = runTest(`
51
- // @nudo:case "random" ()
52
- function getRandom() {
53
- return Math.random();
54
- }
55
- `);
56
- console.log("Math.random() result:", results[0].result);
57
- expect(results[0].result).toBe("number");
58
- });
59
-
60
- it("JSON.stringify() should return string", () => {
61
- const results = runTest(`
62
- // @nudo:case "stringify" ({a: 1})
63
- function toJson(obj) {
64
- return JSON.stringify(obj);
65
- }
66
- `);
67
- console.log("JSON.stringify() result:", results[0].result);
68
- expect(results[0].result).toBe("string");
69
- });
70
-
71
- it("JSON.parse(literal string) decodes the literal's exact structure", () => {
72
- const results = runTest(`
73
- // @nudo:case "fixture" ()
74
- function parseFixture() {
75
- return JSON.parse('{"a":1,"b":[2,"x"]}');
76
- }
77
- // @nudo:case "scalars" ()
78
- function parseScalars() {
79
- return JSON.parse('{"n":null,"t":true,"s":"x"}');
80
- }
81
- `);
82
- console.log("JSON.parse literal results:", results.map((r) => r.result));
83
- expect(results[0].result).toBe('{ a: 1, b: [2, "x"] }');
84
- expect(results[1].result).toBe('{ n: null, t: true, s: "x" }');
85
- });
86
-
87
- it("JSON.parse degrades to unknown for non-literal, invalid, or reviver calls", () => {
88
- const results = runTest(`
89
- // @nudo:case "invalid" ('not json')
90
- // @nudo:case "symbolic" (T.string)
91
- // @nudo:case "reviver" ('{"a":1}', T.unknown)
92
- function parseArg(text, rev) {
93
- return JSON.parse(text, rev);
94
- }
95
- `);
96
- console.log("JSON.parse degradation results:", results.map((r) => r.result));
97
- expect(results.map((r) => r.result)).toEqual(["unknown", "unknown", "unknown"]);
98
- });
99
-
100
- it("Object.keys() should return string[]", () => {
101
- const results = runTest(`
102
- // @nudo:case "keys" ({a: 1, b: 2})
103
- function getKeys(obj) {
104
- return Object.keys(obj);
105
- }
106
- `);
107
- console.log("Object.keys() result:", results[0].result);
108
- // Object.keys returns actual keys from the object
109
- expect(results[0].result).toBe('["a", "b"]');
110
- });
111
-
112
- it("Object.prototype.hasOwnProperty.call(obj, key) should return boolean", () => {
113
- const results = runTest(`
114
- // @nudo:case "own" ({a: 1})
115
- function checkOwn(obj) {
116
- return Object.prototype.hasOwnProperty.call(obj, 'a');
117
- }
118
- `);
119
- console.log("Object.prototype.hasOwnProperty.call result:", results[0].result);
120
- expect(results[0].result).toBe("boolean");
121
- });
122
-
123
- it("Object.prototype predicate family (isPrototypeOf, propertyIsEnumerable) should return boolean", () => {
124
- const results = runTest(`
125
- // @nudo:case "proto" ({a: 1})
126
- function checkProto(obj) {
127
- const a = Object.prototype.isPrototypeOf.call({}, obj);
128
- const b = Object.prototype.propertyIsEnumerable.call(obj, 'a');
129
- return a && b;
130
- }
131
- `);
132
- console.log("Object.prototype predicates result:", results[0].result);
133
- expect(results[0].result).toBe("boolean");
134
- });
135
-
136
- it("Object.prototype members resolve through plain objects (member access and destructuring)", () => {
137
- const results = runTest(`
138
- // @nudo:case "member" ({x: 1})
139
- function viaMember(obj) {
140
- return obj.hasOwnProperty('x');
141
- }
142
- `);
143
- console.log("obj.hasOwnProperty result:", results[0].result);
144
- expect(results[0].result).toBe("boolean");
145
-
146
- // Destructuring reads through the prototype chain in real JS, so
147
- // `const { hasOwnProperty } = obj` yields a function, not undefined —
148
- // and never the native JS function (plain records must be own-guarded).
149
- const destructured = runTest(`
150
- // @nudo:case "destructure" ({y: 1})
151
- function viaDestructure(obj) {
152
- const { hasOwnProperty } = obj;
153
- return hasOwnProperty.call(obj, 'y');
154
- }
155
- `);
156
- console.log("destructured hasOwnProperty result:", destructured[0].result);
157
- expect(destructured[0].result).toBe("boolean");
158
- });
159
-
160
- it("Object.prototype.toString.call(x) yields the receiver's brand literal", () => {
161
- const results = runTest(`
162
- // @nudo:case "plain" ({a: 1})
163
- // @nudo:case "array" ([1, 2])
164
- // @nudo:case "fn" ((a) => a)
165
- function brand(x) {
166
- return Object.prototype.toString.call(x);
167
- }
168
- `);
169
- console.log("brand results:", results.map((r) => r.result));
170
- expect(results[0].result).toBe('"[object Object]"');
171
- expect(results[1].result).toBe('"[object Array]"');
172
- expect(results[2].result).toBe('"[object Function]"');
173
- });
174
-
175
- it("Object.prototype.toString.call brands null/undefined and primitives", () => {
176
- const results = runTest(`
177
- // @nudo:case "null" (null)
178
- // @nudo:case "undefined" (undefined)
179
- // @nudo:case "number" (T.number)
180
- function brand(x) {
181
- return Object.prototype.toString.call(x);
182
- }
183
- `);
184
- console.log("brand results:", results.map((r) => r.result));
185
- expect(results[0].result).toBe('"[object Null]"');
186
- expect(results[1].result).toBe('"[object Undefined]"');
187
- expect(results[2].result).toBe('"[object Number]"');
188
- });
189
-
190
- it("Object.prototype.toString.call(new Map()) brands with the class name", () => {
191
- const results = runTest(`
192
- // @nudo:case "map" ()
193
- function brandMap() {
194
- return Object.prototype.toString.call(new Map());
195
- }
196
- `);
197
- console.log("brand Map result:", results[0].result);
198
- expect(results[0].result).toBe('"[object Map]"');
199
- });
200
-
201
- it("Object.getPrototypeOf maps receivers onto cached prototype singletons", () => {
202
- const results = runTest(`
203
- // @nudo:case "plain" ({a: 1})
204
- // @nudo:case "array" ([1])
205
- function proto(x) {
206
- return Object.getPrototypeOf(x);
207
- }
208
- `);
209
- console.log("getPrototypeOf results:", results.map((r) => r.result.slice(0, 24)));
210
- expect(results[0].result).toContain("Object {");
211
- expect(results[1].result).toContain("Array {");
212
- });
213
-
214
- it("Object.getPrototypeOf identity compares equal to X.prototype", () => {
215
- const results = runTest(`
216
- // @nudo:case "plain" ({a: 1})
217
- function protoEq(x) {
218
- return Object.getPrototypeOf(x) === Object.prototype;
219
- }
220
- `);
221
- expect(results[0].result).toBe("true");
222
- });
223
-
224
- it("Object.getOwnPropertyDescriptor returns a value-carrying descriptor", () => {
225
- const results = runTest(`
226
- // @nudo:case "hit" ({a: 1})
227
- function desc(x) {
228
- const d = Object.getOwnPropertyDescriptor(x, 'a');
229
- return d ? d.value : 'missing';
230
- }
231
- `);
232
- console.log("descriptor value:", results[0].result);
233
- expect(results[0].result).toBe("1");
234
- });
235
-
236
- it("Object.getOwnPropertyDescriptor misses return undefined", () => {
237
- const results = runTest(`
238
- // @nudo:case "miss" ({a: 1})
239
- function descMiss(x) {
240
- return Object.getOwnPropertyDescriptor(x, 'b');
241
- }
242
- `);
243
- console.log("descriptor miss:", results[0].result);
244
- expect(results[0].result).toBe("undefined");
245
- });
246
-
247
- it("Buffer namespace resolves: from/prototype feed the clone-style proto chain", () => {
248
- const results = runTest(`
249
- // @nudo:case "from" ({a: 1})
250
- function buf(x) {
251
- const proto = Object.getPrototypeOf(x);
252
- if (proto === Buffer.prototype) {
253
- return Buffer.from(x);
254
- }
255
- return Object.create(proto);
256
- }
257
- `);
258
- console.log("buffer chain result:", results[0].result);
259
- expect(results[0].result).toBe("{}");
260
- });
261
-
262
- it("String.fromCharCode/fromCodePoint with all-literal codes return exact string literals", () => {
263
- const results = runTest(`
264
- // @nudo:case "sep" ()
265
- function lineSep() {
266
- return String.fromCharCode(0x2028);
267
- }
268
- // @nudo:case "multi" ()
269
- function pair() {
270
- return String.fromCharCode(72, 105);
271
- }
272
- // @nudo:case "point" ()
273
- function fromPoint() {
274
- return String.fromCodePoint(65);
275
- }
276
- // @nudo:case "empty" ()
277
- function none() {
278
- return String.fromCharCode();
279
- }
280
- `);
281
- console.log("String static literal results:", results.map((r) => r.result));
282
- expect(results.map((r) => r.result)).toEqual([
283
- JSON.stringify(String.fromCharCode(0x2028)),
284
- '"Hi"',
285
- '"A"',
286
- '""',
287
- ]);
288
- });
289
-
290
- it("String.fromCharCode/fromCodePoint degrade to string for non-literal args", () => {
291
- const results = runTest(`
292
- // @nudo:case "symbolic" (T.number)
293
- function sym(code) {
294
- return String.fromCharCode(code);
295
- }
296
- // @nudo:case "mixed" (65, T.number)
297
- function mixed(a, b) {
298
- return String.fromCharCode(a, b);
299
- }
300
- // @nudo:case "pointSym" (T.number)
301
- function pointSym(code) {
302
- return String.fromCodePoint(code);
303
- }
304
- `);
305
- console.log("String static degradation results:", results.map((r) => r.result));
306
- expect(results.map((r) => r.result)).toEqual(["string", "string", "string"]);
307
- });
308
- });
@@ -1,313 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { parse, extractDirectives, type CaseDirective } from "@nudojs/parser";
3
- import { typeValueToString, createEnvironment } from "@nudojs/core";
4
- import { evaluateFunctionFull } from "../evaluator.js";
5
-
6
- function infer(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
- const caseDirectives = fn.directives.filter((d): d is CaseDirective => d.kind === "case");
14
- for (const dir of caseDirectives) {
15
- const result = evaluateFunctionFull(fn.node, dir.args, env);
16
- results.push({
17
- name: fn.name,
18
- caseName: dir.name,
19
- result: typeValueToString(result.value),
20
- });
21
- }
22
- }
23
- return results;
24
- }
25
-
26
- describe("Built-in Functions", () => {
27
- describe("String()", () => {
28
- it("converts number to string", () => {
29
- const results = infer(`
30
- // @nudo:case "num" (42)
31
- function foo(x) { return String(x); }
32
- `);
33
- expect(results[0].result).toBe('"42"');
34
- });
35
-
36
- it("converts string to string (identity)", () => {
37
- const results = infer(`
38
- // @nudo:case "str" ("hello")
39
- function foo(x) { return String(x); }
40
- `);
41
- expect(results[0].result).toBe('"hello"');
42
- });
43
-
44
- it("converts boolean to string", () => {
45
- const results = infer(`
46
- // @nudo:case "true" (true)
47
- // @nudo:case "false" (false)
48
- function foo(x) { return String(x); }
49
- `);
50
- expect(results[0].result).toBe('"true"');
51
- expect(results[1].result).toBe('"false"');
52
- });
53
-
54
- it("converts null to string", () => {
55
- const results = infer(`
56
- // @nudo:case "null" (null)
57
- function foo(x) { return String(x); }
58
- `);
59
- expect(results[0].result).toBe('"null"');
60
- });
61
-
62
- it("converts undefined to string", () => {
63
- const results = infer(`
64
- // @nudo:case "undef" (undefined)
65
- function foo(x) { return String(x); }
66
- `);
67
- expect(results[0].result).toBe('"undefined"');
68
- });
69
-
70
- it("returns string for symbolic input", () => {
71
- const results = infer(`
72
- // @nudo:case "sym" (T.number)
73
- function foo(x) { return String(x); }
74
- `);
75
- expect(results[0].result).toBe("string");
76
- });
77
-
78
- it("returns empty string for no args", () => {
79
- const results = infer(`
80
- // @nudo:case "empty" ()
81
- function foo() { return String(); }
82
- `);
83
- expect(results[0].result).toBe('""');
84
- });
85
- });
86
-
87
- describe("Number()", () => {
88
- it("converts string to number", () => {
89
- const results = infer(`
90
- // @nudo:case "str" ("42")
91
- function foo(x) { return Number(x); }
92
- `);
93
- expect(results[0].result).toBe("42");
94
- });
95
-
96
- it("converts number to number (identity)", () => {
97
- const results = infer(`
98
- // @nudo:case "num" (42)
99
- function foo(x) { return Number(x); }
100
- `);
101
- expect(results[0].result).toBe("42");
102
- });
103
-
104
- it("converts boolean to number", () => {
105
- const results = infer(`
106
- // @nudo:case "true" (true)
107
- // @nudo:case "false" (false)
108
- function foo(x) { return Number(x); }
109
- `);
110
- expect(results[0].result).toBe("1");
111
- expect(results[1].result).toBe("0");
112
- });
113
-
114
- it("returns number for symbolic input", () => {
115
- const results = infer(`
116
- // @nudo:case "sym" (T.string)
117
- function foo(x) { return Number(x); }
118
- `);
119
- expect(results[0].result).toBe("number");
120
- });
121
- });
122
-
123
- describe("Boolean()", () => {
124
- it("converts truthy values", () => {
125
- const results = infer(`
126
- // @nudo:case "str" ("hello")
127
- // @nudo:case "num" (42)
128
- function foo(x) { return Boolean(x); }
129
- `);
130
- expect(results[0].result).toBe("true");
131
- expect(results[1].result).toBe("true");
132
- });
133
-
134
- it("converts falsy values", () => {
135
- const results = infer(`
136
- // @nudo:case "zero" (0)
137
- // @nudo:case "empty" ("")
138
- // @nudo:case "null" (null)
139
- function foo(x) { return Boolean(x); }
140
- `);
141
- expect(results[0].result).toBe("false");
142
- expect(results[1].result).toBe("false");
143
- expect(results[2].result).toBe("false");
144
- });
145
- });
146
-
147
- describe("parseInt()", () => {
148
- it("returns number", () => {
149
- const results = infer(`
150
- // @nudo:case "str" ("42")
151
- function foo(x) { return parseInt(x); }
152
- `);
153
- expect(results[0].result).toBe("number");
154
- });
155
- });
156
-
157
- describe("parseFloat()", () => {
158
- it("returns number", () => {
159
- const results = infer(`
160
- // @nudo:case "str" ("3.14")
161
- function foo(x) { return parseFloat(x); }
162
- `);
163
- expect(results[0].result).toBe("number");
164
- });
165
- });
166
-
167
- describe("isNaN()", () => {
168
- it("returns boolean", () => {
169
- const results = infer(`
170
- // @nudo:case "num" (42)
171
- // @nudo:case "nan" (NaN)
172
- function foo(x) { return isNaN(x); }
173
- `);
174
- expect(results[0].result).toBe("boolean");
175
- expect(results[1].result).toBe("boolean");
176
- });
177
- });
178
-
179
- describe("isFinite()", () => {
180
- it("returns boolean", () => {
181
- const results = infer(`
182
- // @nudo:case "num" (42)
183
- // @nudo:case "inf" (Infinity)
184
- function foo(x) { return isFinite(x); }
185
- `);
186
- expect(results[0].result).toBe("boolean");
187
- expect(results[1].result).toBe("boolean");
188
- });
189
- });
190
-
191
- describe("encodeURIComponent()", () => {
192
- it("returns string", () => {
193
- const results = infer(`
194
- // @nudo:case "str" ("hello world")
195
- function foo(x) { return encodeURIComponent(x); }
196
- `);
197
- expect(results[0].result).toBe("string");
198
- });
199
- });
200
-
201
- describe("console methods", () => {
202
- it("console.log returns undefined", () => {
203
- const results = infer(`
204
- // @nudo:case "str" ("hello")
205
- function foo(x) { return console.log(x); }
206
- `);
207
- expect(results[0].result).toBe("undefined");
208
- });
209
- });
210
-
211
- describe("Math methods", () => {
212
- it("Math.abs returns number", () => {
213
- const results = infer(`
214
- // @nudo:case "num" (42)
215
- function foo(x) { return Math.abs(x); }
216
- `);
217
- expect(results[0].result).toBe("number");
218
- });
219
-
220
- it("Math.floor returns number", () => {
221
- const results = infer(`
222
- // @nudo:case "num" (3.14)
223
- function foo(x) { return Math.floor(x); }
224
- `);
225
- expect(results[0].result).toBe("number");
226
- });
227
- });
228
-
229
- describe("Global numeric constants", () => {
230
- it("Infinity / -Infinity / NaN resolve to number", () => {
231
- const results = infer(`
232
- // @nudo:case "constants" ()
233
- function foo() {
234
- return [Infinity, -Infinity, NaN];
235
- }
236
- `);
237
- // 修复前:Infinity/NaN 走未知全局(unknown)且触发 nudo:unknown-global;
238
- // 现在通过 BUILTIN_STATIC_METHODS 接线解析为 number。
239
- // 常量显示 number 即可——case 序列化器对非有限数返回 null,不产
240
- // 字面量值。数组字面量求值为 tuple。
241
- expect(results[0].result).toBe("[number, number, number]");
242
- });
243
-
244
- it("Number static constants resolve to number", () => {
245
- const results = infer(`
246
- // @nudo:case "num-consts" ()
247
- function foo() {
248
- return [Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER, Number.MAX_VALUE, Number.EPSILON];
249
- }
250
- `);
251
- // 修复前:Number.MAX_SAFE_INTEGER 报 unknown-property 且值为 undefined
252
- expect(results[0].result).toBe("[number, number, number, number]");
253
- });
254
-
255
- it("Math.PI / Math.E resolve to number", () => {
256
- const results = infer(`
257
- // @nudo:case "math-consts" ()
258
- function foo() { return Math.PI + Math.E; }
259
- `);
260
- // 修复前:Math.PI 走 unknown-property(undefined)
261
- expect(results[0].result).toBe("number");
262
- });
263
- });
264
-
265
- describe("Loose equality", () => {
266
- it("literal == evaluates via JS coercion", () => {
267
- const results = infer(`
268
- // @nudo:case "num" ()
269
- // @nudo:case "str-num" ()
270
- function foo() {
271
- return [5 == 3, "5" == 5];
272
- }
273
- `);
274
- // 修复前:== 未实现(unknown);现在两字面量操作数按 JS 宽松相
275
- // 等求值:5 == 3 → false,"5" == 5 → true
276
- expect(results[0].result).toBe("[false, true]");
277
- });
278
-
279
- it("non-literal == degrades to boolean", () => {
280
- const results = infer(`
281
- // @nudo:case "sym" (T.string)
282
- function foo(x) {
283
- return x == "5";
284
- }
285
- `);
286
- // 任一操作数非字面量:退化为 boolean
287
- expect(results[0].result).toBe("boolean");
288
- });
289
- });
290
-
291
- describe("Integration with narrowing", () => {
292
- it("String() after typeof check", () => {
293
- const results = infer(`
294
- // @nudo:case "str" ("hello")
295
- // @nudo:case "num" (42)
296
- function foo(x) {
297
- if (typeof x === "string") return x.toUpperCase();
298
- return String(x);
299
- }
300
- `);
301
- expect(results[0].result).toBe('"HELLO"');
302
- expect(results[1].result).toBe('"42"');
303
- });
304
-
305
- it("String() in template literal", () => {
306
- const results = infer(`
307
- // @nudo:case "num" (42)
308
- function foo(x) { return \`value: \${String(x)}\`; }
309
- `);
310
- expect(results[0].result).toBe('"value: 42"');
311
- });
312
- });
313
- });