@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
@@ -0,0 +1,260 @@
1
+ /**
2
+ * 内置静态命名空间与直接全局值(process/Math/JSON/Object statics/Number
3
+ * 常量…)及其实现辅助(JSON 字面量解码、属性描述符、结构 instanceof 判定、
4
+ * fromCharCode 实参收割)。
5
+ */
6
+
7
+ import { T, simplifyUnion, type TypeValue } from "@nudojs/core";
8
+ import { protoOfValue } from "./builtin-prototype.ts";
9
+ import { PROMISE_STATIC_METHODS } from "./builtin-promise.ts";
10
+ import { SYMBOL_STATIC_METHODS, SYMBOL_STATIC_PROPS } from "./builtin-symbol.ts";
11
+ import { REFLECT_METHODS } from "./builtin-reflect.ts";
12
+ import { INTL_DATETIMEFORMAT_METHODS, INTL_NUMBERFORMAT_METHODS } from "./builtin-intl.ts";
13
+
14
+ // hasOwnProp 守卫:普通 Record 表查找会从原型链泄漏原生函数(与
15
+ // builtin-prototype.ts / evaluator 内同名 helper 同语义)。
16
+ function hasOwnProp(props: Record<string, unknown>, name: string): boolean {
17
+ return Object.prototype.hasOwnProperty.call(props, name);
18
+ }
19
+
20
+ // Built-in JavaScript API type mappings
21
+ // Namespace objects (e.g. Math.floor) and direct global values (e.g. parseInt)
22
+ const BUILTIN_STATIC_METHODS: Record<string, Record<string, TypeValue> | TypeValue> = {
23
+ // Node CJS 库普遍依赖的 process 全局——缺它时 process.nextTick(cb) 走
24
+ // 未知全局 → undefined → no-method 假错(hoek wait 的 usage-site 执行)
25
+ process: {
26
+ nextTick: T.fn(["callback", "...args"], { type: "BlockStatement", body: [], directives: [] } as any, undefined as any),
27
+ env: T.object({}),
28
+ platform: T.string,
29
+ versions: T.object({}),
30
+ argv: T.array(T.string),
31
+ cwd: T.fn([], { type: "BlockStatement", body: [], directives: [] } as any, undefined as any),
32
+ exit: T.fn(["code"], { type: "BlockStatement", body: [], directives: [] } as any, undefined as any),
33
+ hrtime: T.fn(["time"], { type: "BlockStatement", body: [], directives: [] } as any, undefined as any),
34
+ stdout: T.instanceOf("Socket"),
35
+ stderr: T.instanceOf("Socket"),
36
+ },
37
+ Date: {
38
+ now: T.number,
39
+ parse: T.number,
40
+ UTC: T.number,
41
+ },
42
+ Math: {
43
+ random: T.number,
44
+ floor: T.fn(["x"], { type: "BlockStatement", body: [] } as any, undefined as any),
45
+ ceil: T.fn(["x"], { type: "BlockStatement", body: [] } as any, undefined as any),
46
+ round: T.fn(["x"], { type: "BlockStatement", body: [] } as any, undefined as any),
47
+ abs: T.fn(["x"], { type: "BlockStatement", body: [] } as any, undefined as any),
48
+ max: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
49
+ min: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
50
+ sqrt: T.fn(["x"], { type: "BlockStatement", body: [] } as any, undefined as any),
51
+ pow: T.fn(["base", "exp"], { type: "BlockStatement", body: [] } as any, undefined as any),
52
+ // Math 常量与 @nudo:env es 的 Math 定义对齐(env 绑定优先);无 env
53
+ // 指令时也在此解析,避免 Math.PI 走 unknown-property。
54
+ PI: T.number,
55
+ E: T.number,
56
+ LN2: T.number,
57
+ LN10: T.number,
58
+ LOG2E: T.number,
59
+ LOG10E: T.number,
60
+ SQRT2: T.number,
61
+ SQRT1_2: T.number,
62
+ },
63
+ JSON: {
64
+ // JSON.parse(literal-string): evaluate with the real JSON.parse and
65
+ // re-encode through jsonToTypeValue, so fixture-style calls
66
+ // (`JSON.parse('{"a":1}')`) keep literal-level precision instead of
67
+ // poisoning downstream clone/deepEqual args with unknown. Degrades to
68
+ // the unknown fallback when the argument is not a literal string or
69
+ // the text is not valid JSON. A reviver argument also degrades: it can
70
+ // replace every value (and the root) with an arbitrary one, so the
71
+ // parse structure is not a sound answer (hoek/json-ext never pass one).
72
+ parse: T.fnSig([T.string], T.unknown, T.never, (args) => {
73
+ const text = args[0];
74
+ if (text?.kind !== "literal" || typeof text.value !== "string") return undefined;
75
+ const reviver = args[1];
76
+ if (reviver && !(reviver.kind === "literal" && reviver.value === undefined)) return undefined;
77
+ try {
78
+ return jsonToTypeValue(JSON.parse(text.value));
79
+ } catch {
80
+ return undefined;
81
+ }
82
+ }),
83
+ stringify: T.string,
84
+ },
85
+ Object: {
86
+ keys: T.array(T.string),
87
+ values: T.array(T.unknown),
88
+ entries: T.array(T.tuple([T.string, T.unknown])),
89
+ assign: T.fnSig([T.unknown], T.unknown, T.never, (args) => {
90
+ // Object.assign(target, ...sources): later sources overwrite; a
91
+ // symbolic source keeps the symbolic unknown fallback.
92
+ const target = args[0];
93
+ if (target?.kind !== "object") return undefined;
94
+ const props: Record<string, TypeValue> = { ...target.properties };
95
+ for (const src of args.slice(1)) {
96
+ if (src?.kind !== "object") return undefined;
97
+ Object.assign(props, src.properties);
98
+ }
99
+ return T.object(props);
100
+ }),
101
+ // Prototype-reflection statics. getPrototypeOf maps receivers onto the
102
+ // cached prototype singletons so `getPrototypeOf(x) !== getPrototypeOf(y)`
103
+ // resolves literally; create/setPrototypeOf cover the clone-style
104
+ // prototype dance; getOwnPropertyDescriptor feeds descriptor.get/set
105
+ // branches with the property's value type.
106
+ getPrototypeOf: T.fnSig([T.unknown], T.unknown, T.never, (args) => protoOfValue(args[0])),
107
+ create: T.fnSig([T.unknown], T.object({}), T.never, () => T.object({})),
108
+ setPrototypeOf: T.fnSig([T.unknown, T.unknown], T.unknown, T.never, (args) => args[0]),
109
+ defineProperty: T.fnSig([T.unknown, T.unknown, T.unknown], T.unknown, T.never, (args) => args[0]),
110
+ getOwnPropertyDescriptor: T.fnSig([T.unknown, T.unknown], T.union(T.object({}), T.undefined), T.never, (args) => {
111
+ const obj = args[0];
112
+ const key = args[1];
113
+ if (obj?.kind !== "object" && obj?.kind !== "instance") return T.undefined;
114
+ if (!key) return T.undefined;
115
+ if (key.kind === "literal" && typeof key.value === "string") {
116
+ const prop = hasOwnProp(obj.properties, key.value) ? obj.properties[key.value] : undefined;
117
+ if (!prop) return T.undefined;
118
+ return descriptorOf(prop);
119
+ }
120
+ if (key.kind === "primitive" && key.type === "string") {
121
+ const props = Object.keys(obj.properties);
122
+ if (props.length === 0) return T.undefined;
123
+ return descriptorOf(simplifyUnion(props.map((k) => obj.properties[k])));
124
+ }
125
+ return T.undefined;
126
+ }),
127
+ },
128
+ Buffer: {
129
+ from: T.fnSig([T.unknown], T.instanceOf("Buffer")),
130
+ alloc: T.fnSig([T.number], T.instanceOf("Buffer")),
131
+ concat: T.fnSig([T.unknown], T.instanceOf("Buffer")),
132
+ byteLength: T.fnSig([T.unknown], T.number),
133
+ isEncoding: T.fnSig([T.unknown], T.boolean),
134
+ isBuffer: T.fnSig([T.unknown], T.boolean, T.never, (args) => {
135
+ const lit = builtinInstanceTest(args[0], "Buffer");
136
+ return lit === undefined ? undefined : T.literal(lit);
137
+ }),
138
+ },
139
+ Array: {
140
+ isArray: T.boolean,
141
+ from: T.array(T.unknown),
142
+ },
143
+ Number: {
144
+ isNaN: T.boolean,
145
+ isFinite: T.boolean,
146
+ parseInt: T.number,
147
+ parseFloat: T.number,
148
+ // Number 常量与 @nudo:env es 的 Number 定义对齐(env 绑定优先);
149
+ // 均为 number(case 序列化器对非有限数返回 null,不产出字面量值)。
150
+ MAX_SAFE_INTEGER: T.number,
151
+ MIN_SAFE_INTEGER: T.number,
152
+ MAX_VALUE: T.number,
153
+ MIN_VALUE: T.number,
154
+ POSITIVE_INFINITY: T.number,
155
+ NEGATIVE_INFINITY: T.number,
156
+ NaN: T.number,
157
+ EPSILON: T.number,
158
+ },
159
+ String: {
160
+ // fromCharCode/fromCodePoint over all-number-literal args evaluate
161
+ // with the real String static (hoek's escaper builds its 0x2028
162
+ // separator literal this way before handing it to escapeJson); any
163
+ // non-literal arg keeps only the string return type (the result is
164
+ // always a string, just not a knowable one). fromCodePoint also
165
+ // falls back on out-of-range literals (real RangeError → string).
166
+ fromCharCode: T.fnSig([T.number], T.string, T.never, (args) => {
167
+ const codes = literalCodeUnits(args);
168
+ return codes === null ? T.string : T.literal(String.fromCharCode(...codes));
169
+ }),
170
+ fromCodePoint: T.fnSig([T.number], T.string, T.never, (args) => {
171
+ const codes = literalCodeUnits(args);
172
+ if (codes === null) return T.string;
173
+ try {
174
+ return T.literal(String.fromCodePoint(...codes));
175
+ } catch {
176
+ return undefined;
177
+ }
178
+ }),
179
+ },
180
+ Promise: PROMISE_STATIC_METHODS,
181
+ Symbol: { ...SYMBOL_STATIC_METHODS, ...SYMBOL_STATIC_PROPS },
182
+ Reflect: REFLECT_METHODS as unknown as Record<string, TypeValue>,
183
+ Intl: {
184
+ DateTimeFormat: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
185
+ NumberFormat: T.fn(["...args"], { type: "BlockStatement", body: [] } as any, undefined as any),
186
+ },
187
+ parseInt: T.number,
188
+ parseFloat: T.number,
189
+ isNaN: T.boolean,
190
+ isFinite: T.boolean,
191
+ // 全局数值常量:与 @nudo:env es 的 globals 对齐(env 绑定优先)。无 env
192
+ // 指令时在此解析为 number——否则 Infinity/NaN 命中大写开头的未知全局
193
+ // 检测,触发 nudo:unknown-global 与 nudo:builtin-unknown 诊断。
194
+ Infinity: T.number,
195
+ NaN: T.number,
196
+ };
197
+ /** Harvest String.fromCharCode/fromCodePoint arguments: a list of number
198
+ * literals ready for real evaluation, or null when any argument is
199
+ * non-literal (symbolic → the caller keeps its plain string fallback).
200
+ * An empty argument list yields [] (both statics return ""). */
201
+ function literalCodeUnits(args: TypeValue[]): number[] | null {
202
+ const codes: number[] = [];
203
+ for (const a of args) {
204
+ if (a?.kind !== "literal" || typeof a.value !== "number") return null;
205
+ codes.push(a.value);
206
+ }
207
+ return codes;
208
+ }
209
+ function descriptorOf(prop: TypeValue): TypeValue {
210
+ return T.object({
211
+ value: prop,
212
+ writable: T.boolean,
213
+ enumerable: T.boolean,
214
+ configurable: T.boolean,
215
+ });
216
+ }
217
+ // `x instanceof C` / `Buffer.isBuffer(x)` / `Array.isArray(x)` literal
218
+ // answer for structurally-known receivers; undefined keeps the symbolic
219
+ // fallback (boolean).
220
+ function builtinInstanceTest(v: TypeValue | undefined, className: string): boolean | undefined {
221
+ if (!v) return undefined;
222
+ let base: TypeValue = v;
223
+ while (base.kind === "refined") base = base.base;
224
+ switch (base.kind) {
225
+ case "array":
226
+ case "tuple": return className === "Array" || className === "Object";
227
+ case "object": return className === "Object";
228
+ case "function": return className === "Function" || className === "Object";
229
+ case "promise": return className === "Promise" || className === "Object";
230
+ case "literal":
231
+ case "primitive": return false;
232
+ default: return undefined;
233
+ }
234
+ }
235
+ /** JSON 值 → TypeValue(字面量级精确:package.json 版本号等成为 string 字面量) */
236
+ function jsonToTypeValue(v: unknown): TypeValue {
237
+ if (v === null) return T.literal(null);
238
+ if (typeof v === "string") return T.literal(v);
239
+ if (typeof v === "number") return T.literal(v);
240
+ if (typeof v === "boolean") return T.literal(v);
241
+ if (Array.isArray(v)) return T.tuple(v.map(jsonToTypeValue));
242
+ if (typeof v === "object") {
243
+ const props: Record<string, TypeValue> = {};
244
+ for (const [k, val] of Object.entries(v)) {
245
+ // Define-as-own: plain assignment would let a "__proto__" key trip
246
+ // the setter on the props record (dropping it and corrupting the
247
+ // record's prototype) — JSON.parse creates an own property, and
248
+ // hoek's prototype-poisoning fixtures exercise exactly that key.
249
+ Object.defineProperty(props, k, {
250
+ value: jsonToTypeValue(val),
251
+ enumerable: true,
252
+ writable: true,
253
+ configurable: true,
254
+ });
255
+ }
256
+ return T.object(props);
257
+ }
258
+ return T.unknown;
259
+ }
260
+ export { BUILTIN_STATIC_METHODS, builtinInstanceTest, jsonToTypeValue };
@@ -0,0 +1,113 @@
1
+ /**
2
+ * 二元运算统一入口:代数优先,语言表面回落 Ops。
3
+ * TypeValue 是 IR;+ - * / % 比较与数值相等走 Abs,其余外延。
4
+ */
5
+
6
+ import {
7
+ type TypeValue,
8
+ T,
9
+ simplifyUnion,
10
+ dispatchBinaryOp,
11
+ } from "@nudojs/core";
12
+ import { tryAbsBinary } from "./abs-route.ts";
13
+
14
+ const MAX_UNION_PRODUCT = 64;
15
+
16
+ export function looseLiteralValue(
17
+ tv: TypeValue,
18
+ ): { hit: boolean; v: number | string | boolean | null | undefined } {
19
+ if (tv.kind === "literal") {
20
+ const v = tv.value;
21
+ if (
22
+ typeof v === "number" ||
23
+ typeof v === "string" ||
24
+ typeof v === "boolean" ||
25
+ v === null ||
26
+ v === undefined
27
+ ) {
28
+ return { hit: true, v };
29
+ }
30
+ }
31
+ return { hit: false, v: undefined };
32
+ }
33
+
34
+ export function distributeBinaryOverUnion(
35
+ left: TypeValue,
36
+ right: TypeValue,
37
+ fn: (l: TypeValue, r: TypeValue) => TypeValue,
38
+ ): TypeValue {
39
+ if (left.kind === "union" && right.kind === "union") {
40
+ if (left.members.length * right.members.length > MAX_UNION_PRODUCT) {
41
+ return T.unknown;
42
+ }
43
+ return simplifyUnion(
44
+ left.members.flatMap((l) => right.members.map((r) => fn(l, r))),
45
+ );
46
+ }
47
+ if (left.kind === "union") {
48
+ return simplifyUnion(left.members.map((l) => fn(l, right)));
49
+ }
50
+ if (right.kind === "union") {
51
+ return simplifyUnion(right.members.map((r) => fn(left, r)));
52
+ }
53
+ return fn(left, right);
54
+ }
55
+
56
+ const ALGEBRA_OPS = new Set([
57
+ "+", "-", "*", "/", "%",
58
+ "<", "<=", ">", ">=",
59
+ "===", "!==",
60
+ ]);
61
+
62
+ /** 数值运算形状:任一侧 number/unknown 时,`- * / %` 结果至少是 number */
63
+ function numberish(tv: TypeValue): boolean {
64
+ if (!tv) return false;
65
+ if (tv.kind === "unknown") return true;
66
+ if (tv.kind === "literal") return typeof tv.value === "number";
67
+ if (tv.kind === "primitive") return tv.type === "number";
68
+ if (tv.kind === "refined") return numberish(tv.base);
69
+ return false;
70
+ }
71
+
72
+ /**
73
+ * 纯值级二元运算(不需要 AST/env)。
74
+ * 返回 undefined 表示调用方需处理 node 特例(instanceof / in / builtin identity)。
75
+ */
76
+ export function evalBinaryValue(
77
+ operator: string,
78
+ left: TypeValue,
79
+ right: TypeValue,
80
+ ): TypeValue | undefined {
81
+ if (operator === "instanceof" || operator === "in") return undefined;
82
+
83
+ if (ALGEBRA_OPS.has(operator)) {
84
+ const absResult = tryAbsBinary(operator, left, right);
85
+ if (absResult !== undefined) return absResult;
86
+ }
87
+
88
+ if (operator === "==" || operator === "!=") {
89
+ const eq = operator === "==";
90
+ return distributeBinaryOverUnion(left, right, (l, r) => {
91
+ const lv = looseLiteralValue(l);
92
+ const rv = looseLiteralValue(r);
93
+ if (lv.hit && rv.hit) return T.literal(eq ? lv.v == rv.v : lv.v != rv.v);
94
+ return T.boolean;
95
+ });
96
+ }
97
+
98
+ // refined 自定义 ops / Ops.add·比较 / nullish ===
99
+ const viaDispatch = distributeBinaryOverUnion(left, right, (l, r) =>
100
+ dispatchBinaryOp(operator, l, r),
101
+ );
102
+
103
+ // 代数与 refinement 都未接住时的数值形状:`unknown * 2` → number
104
+ if (
105
+ viaDispatch.kind === "unknown" &&
106
+ (operator === "-" || operator === "*" || operator === "/" || operator === "%") &&
107
+ (numberish(left) || numberish(right))
108
+ ) {
109
+ return T.number;
110
+ }
111
+
112
+ return viaDispatch;
113
+ }
@@ -29,6 +29,9 @@ export {
29
29
 
30
30
  export { narrow } from "./narrowing.ts";
31
31
 
32
+ // 补全成员派生的唯一真值(service/analyzer 经 @nudojs/cli/evaluator 取用)
33
+ export { BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS } from "./builtins/builtin-prototype.ts";
34
+
32
35
  export { loadEnvs, loadEnvsAsync, preloadPathEnvs, type LoadedEnv } from "./env-loader.ts";
33
36
 
34
37
  export { findProjectConfig, type NudoConfig } from "./config.ts";