@nudojs/cli 0.2.1 → 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 (64) hide show
  1. package/dist/{chunk-3HCPXPEW.js → chunk-YDM4HHQ4.js} +1783 -1421
  2. package/dist/evaluator-api.d.ts +12 -1
  3. package/dist/evaluator-api.js +52 -2
  4. package/dist/index.d.ts +1 -2
  5. package/dist/index.js +366 -3572
  6. package/package.json +23 -11
  7. package/CHANGELOG.md +0 -41
  8. package/examples/real-world.js +0 -66
  9. package/examples/validate-real-world.ts +0 -160
  10. package/src/__tests__/builtin-advanced.test.ts +0 -292
  11. package/src/__tests__/builtin-api.test.ts +0 -308
  12. package/src/__tests__/builtin-functions.test.ts +0 -313
  13. package/src/__tests__/builtin-map.test.ts +0 -129
  14. package/src/__tests__/builtin-promise.test.ts +0 -150
  15. package/src/__tests__/builtin-regexp.test.ts +0 -133
  16. package/src/__tests__/builtin-set.test.ts +0 -163
  17. package/src/__tests__/builtin-url.test.ts +0 -256
  18. package/src/__tests__/builtin-web.test.ts +0 -340
  19. package/src/__tests__/combination-scenarios.test.ts +0 -384
  20. package/src/__tests__/edge-cases.test.ts +0 -749
  21. package/src/__tests__/evaluator-loops.test.ts +0 -283
  22. package/src/__tests__/evaluator-nullish-coalescing.test.ts +0 -86
  23. package/src/__tests__/evaluator-optional-chaining.test.ts +0 -37
  24. package/src/__tests__/evaluator-phase2.test.ts +0 -564
  25. package/src/__tests__/evaluator-phase3.test.ts +0 -864
  26. package/src/__tests__/evaluator-refined.test.ts +0 -121
  27. package/src/__tests__/evaluator-string-methods.test.ts +0 -151
  28. package/src/__tests__/evaluator.test.ts +0 -943
  29. package/src/__tests__/mock-arrow-function.test.ts +0 -169
  30. package/src/__tests__/mock-comparison.test.ts +0 -221
  31. package/src/__tests__/mock-comprehensive.test.ts +0 -250
  32. package/src/__tests__/mock-final-integration.test.ts +0 -263
  33. package/src/__tests__/mock-helpers.test.ts +0 -174
  34. package/src/__tests__/mock-realworld.test.ts +0 -191
  35. package/src/__tests__/mock-rest-debug.test.ts +0 -110
  36. package/src/__tests__/mock-rest-edge.test.ts +0 -176
  37. package/src/__tests__/mock-sinon-runtime.test.ts +0 -168
  38. package/src/__tests__/narrowing-discriminated.test.ts +0 -35
  39. package/src/__tests__/narrowing-in-operator.test.ts +0 -56
  40. package/src/__tests__/narrowing-switch.test.ts +0 -56
  41. package/src/__tests__/narrowing-truthiness.test.ts +0 -52
  42. package/src/__tests__/narrowing.test.ts +0 -82
  43. package/src/__tests__/nudo-mock-syntax.test.ts +0 -245
  44. package/src/__tests__/realworld-patterns.test.ts +0 -430
  45. package/src/__tests__/syntax-sugar.test.ts +0 -345
  46. package/src/builtins/builtin-intl.ts +0 -17
  47. package/src/builtins/builtin-map.ts +0 -174
  48. package/src/builtins/builtin-promise.ts +0 -116
  49. package/src/builtins/builtin-reflect.ts +0 -30
  50. package/src/builtins/builtin-regexp.ts +0 -34
  51. package/src/builtins/builtin-set.ts +0 -131
  52. package/src/builtins/builtin-symbol.ts +0 -16
  53. package/src/builtins/builtin-url.ts +0 -58
  54. package/src/builtins/builtin-weak.ts +0 -22
  55. package/src/builtins/builtin-web.ts +0 -111
  56. package/src/builtins/index.ts +0 -61
  57. package/src/config.ts +0 -32
  58. package/src/env-loader.ts +0 -174
  59. package/src/evaluator-api.ts +0 -36
  60. package/src/evaluator.ts +0 -5623
  61. package/src/index.ts +0 -931
  62. package/src/narrowing.ts +0 -537
  63. package/src/resolve-npm.ts +0 -63
  64. 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
- });