@nudojs/cli 0.1.0 → 0.2.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.
- package/CHANGELOG.md +28 -0
- package/LICENSE +21 -0
- package/README.md +34 -0
- package/dist/chunk-3HCPXPEW.js +5286 -0
- package/dist/evaluator-api.d.ts +96 -2
- package/dist/evaluator-api.js +191 -3
- package/dist/index.js +595 -120
- package/examples/real-world.js +66 -0
- package/examples/validate-real-world.ts +160 -0
- package/package.json +28 -3
- package/src/__tests__/builtin-advanced.test.ts +292 -0
- package/src/__tests__/builtin-api.test.ts +308 -0
- package/src/__tests__/builtin-functions.test.ts +313 -0
- package/src/__tests__/builtin-map.test.ts +129 -0
- package/src/__tests__/builtin-promise.test.ts +150 -0
- package/src/__tests__/builtin-regexp.test.ts +133 -0
- package/src/__tests__/builtin-set.test.ts +163 -0
- package/src/__tests__/builtin-url.test.ts +256 -0
- package/src/__tests__/builtin-web.test.ts +340 -0
- package/src/__tests__/combination-scenarios.test.ts +384 -0
- package/src/__tests__/edge-cases.test.ts +749 -0
- package/src/__tests__/evaluator-loops.test.ts +283 -0
- package/src/__tests__/evaluator-nullish-coalescing.test.ts +86 -0
- package/src/__tests__/evaluator-optional-chaining.test.ts +37 -0
- package/src/__tests__/evaluator-phase3.test.ts +353 -2
- package/src/__tests__/evaluator-refined.test.ts +121 -0
- package/src/__tests__/evaluator-string-methods.test.ts +151 -0
- package/src/__tests__/evaluator.test.ts +702 -2
- package/src/__tests__/mock-arrow-function.test.ts +169 -0
- package/src/__tests__/mock-comparison.test.ts +221 -0
- package/src/__tests__/mock-comprehensive.test.ts +250 -0
- package/src/__tests__/mock-final-integration.test.ts +263 -0
- package/src/__tests__/mock-helpers.test.ts +174 -0
- package/src/__tests__/mock-realworld.test.ts +191 -0
- package/src/__tests__/mock-rest-debug.test.ts +110 -0
- package/src/__tests__/mock-rest-edge.test.ts +176 -0
- package/src/__tests__/mock-sinon-runtime.test.ts +168 -0
- package/src/__tests__/narrowing-discriminated.test.ts +35 -0
- package/src/__tests__/narrowing-in-operator.test.ts +56 -0
- package/src/__tests__/narrowing-switch.test.ts +56 -0
- package/src/__tests__/narrowing-truthiness.test.ts +52 -0
- package/src/__tests__/nudo-mock-syntax.test.ts +245 -0
- package/src/__tests__/realworld-patterns.test.ts +430 -0
- package/src/__tests__/syntax-sugar.test.ts +345 -0
- package/src/builtins/builtin-intl.ts +17 -0
- package/src/builtins/builtin-map.ts +174 -0
- package/src/builtins/builtin-promise.ts +116 -0
- package/src/builtins/builtin-reflect.ts +30 -0
- package/src/builtins/builtin-regexp.ts +34 -0
- package/src/builtins/builtin-set.ts +131 -0
- package/src/builtins/builtin-symbol.ts +16 -0
- package/src/builtins/builtin-url.ts +58 -0
- package/src/builtins/builtin-weak.ts +22 -0
- package/src/builtins/builtin-web.ts +111 -0
- package/src/builtins/index.ts +61 -0
- package/src/config.ts +32 -0
- package/src/env-loader.ts +174 -0
- package/src/evaluator-api.ts +22 -0
- package/src/evaluator.ts +4218 -171
- package/src/index.ts +776 -125
- package/src/narrowing.ts +286 -15
- package/src/resolve-npm.ts +63 -0
- package/dist/chunk-JW3RMCNY.js +0 -1467
- package/dist/chunk-RBHWWZII.js +0 -1467
- package/dist/chunk-VN5HMBGB.js +0 -1467
package/src/narrowing.ts
CHANGED
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
subtractType,
|
|
8
8
|
getPrimitiveTypeOf,
|
|
9
9
|
typeValueEquals,
|
|
10
|
+
isSubtypeOf,
|
|
11
|
+
createRange,
|
|
10
12
|
} from "@nudojs/core";
|
|
11
13
|
|
|
12
14
|
/**
|
|
@@ -17,6 +19,20 @@ export function narrow(
|
|
|
17
19
|
test: Node,
|
|
18
20
|
env: Environment,
|
|
19
21
|
): [trueEnv: Environment, falseEnv: Environment] {
|
|
22
|
+
// a || b:fallthrough(both-false)是链式交集 —— narrow(a) 的 false
|
|
23
|
+
// env 再 narrow(b) 的 false。这是 `if (typeof x !== 'object' || x === null)
|
|
24
|
+
// return x;` 守卫模式的关键:后续代码里 x 只剩 object 成员。true 侧
|
|
25
|
+
// 环境无法并集合并,保守退回未 narrow 的 env。
|
|
26
|
+
// a && b 对称:true 侧链式(both-true),false 侧保守。
|
|
27
|
+
if (test.type === "LogicalExpression" && (test.operator === "||" || test.operator === "&&")) {
|
|
28
|
+
const [trueOfLeft, falseOfLeft] = narrow(test.left, env);
|
|
29
|
+
const [, falseOfBoth] = narrow(test.right, falseOfLeft);
|
|
30
|
+
const [trueOfBoth] = narrow(test.right, trueOfLeft);
|
|
31
|
+
return test.operator === "||"
|
|
32
|
+
? [env, falseOfBoth]
|
|
33
|
+
: [trueOfBoth, env];
|
|
34
|
+
}
|
|
35
|
+
|
|
20
36
|
// typeof x === "string"
|
|
21
37
|
if (
|
|
22
38
|
test.type === "BinaryExpression" &&
|
|
@@ -24,9 +40,9 @@ export function narrow(
|
|
|
24
40
|
isTypeofExpr(test.left) &&
|
|
25
41
|
test.right.type === "StringLiteral"
|
|
26
42
|
) {
|
|
27
|
-
const
|
|
28
|
-
if (
|
|
29
|
-
return narrowByTypeof(
|
|
43
|
+
const target = getTypeofTarget(test.left);
|
|
44
|
+
if (target) {
|
|
45
|
+
return target ? narrowByTypeof(target, test.right.value, env) : [env, env];
|
|
30
46
|
}
|
|
31
47
|
}
|
|
32
48
|
|
|
@@ -37,9 +53,9 @@ export function narrow(
|
|
|
37
53
|
test.left.type === "StringLiteral" &&
|
|
38
54
|
isTypeofExpr(test.right)
|
|
39
55
|
) {
|
|
40
|
-
const
|
|
41
|
-
if (
|
|
42
|
-
return narrowByTypeof(
|
|
56
|
+
const target = getTypeofTarget(test.right);
|
|
57
|
+
if (target) {
|
|
58
|
+
return target ? narrowByTypeof(target, test.left.value, env) : [env, env];
|
|
43
59
|
}
|
|
44
60
|
}
|
|
45
61
|
|
|
@@ -50,9 +66,9 @@ export function narrow(
|
|
|
50
66
|
isTypeofExpr(test.left) &&
|
|
51
67
|
test.right.type === "StringLiteral"
|
|
52
68
|
) {
|
|
53
|
-
const
|
|
54
|
-
if (
|
|
55
|
-
const [trueEnv, falseEnv] = narrowByTypeof(
|
|
69
|
+
const target = getTypeofTarget(test.left);
|
|
70
|
+
if (target) {
|
|
71
|
+
const [trueEnv, falseEnv] = target ? narrowByTypeof(target, test.right.value, env) : [env, env];
|
|
56
72
|
return [falseEnv, trueEnv];
|
|
57
73
|
}
|
|
58
74
|
}
|
|
@@ -64,13 +80,50 @@ export function narrow(
|
|
|
64
80
|
test.left.type === "StringLiteral" &&
|
|
65
81
|
isTypeofExpr(test.right)
|
|
66
82
|
) {
|
|
67
|
-
const
|
|
68
|
-
if (
|
|
69
|
-
const [trueEnv, falseEnv] = narrowByTypeof(
|
|
83
|
+
const target = getTypeofTarget(test.right);
|
|
84
|
+
if (target) {
|
|
85
|
+
const [trueEnv, falseEnv] = narrowByTypeof(target, test.left.value, env);
|
|
70
86
|
return [falseEnv, trueEnv];
|
|
71
87
|
}
|
|
72
88
|
}
|
|
73
89
|
|
|
90
|
+
// obj.prop === literal (discriminated union)
|
|
91
|
+
if (
|
|
92
|
+
test.type === "BinaryExpression" &&
|
|
93
|
+
test.operator === "===" &&
|
|
94
|
+
test.left.type === "MemberExpression" &&
|
|
95
|
+
test.left.object.type === "Identifier" &&
|
|
96
|
+
test.left.property.type === "Identifier" &&
|
|
97
|
+
isLiteralNode(test.right)
|
|
98
|
+
) {
|
|
99
|
+
return narrowByDiscriminant(test.left.object.name, test.left.property.name, getLiteralValue(test.right), env);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// literal === obj.prop
|
|
103
|
+
if (
|
|
104
|
+
test.type === "BinaryExpression" &&
|
|
105
|
+
test.operator === "===" &&
|
|
106
|
+
isLiteralNode(test.left) &&
|
|
107
|
+
test.right.type === "MemberExpression" &&
|
|
108
|
+
test.right.object.type === "Identifier" &&
|
|
109
|
+
test.right.property.type === "Identifier"
|
|
110
|
+
) {
|
|
111
|
+
return narrowByDiscriminant(test.right.object.name, test.right.property.name, getLiteralValue(test.left), env);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// obj.prop !== literal
|
|
115
|
+
if (
|
|
116
|
+
test.type === "BinaryExpression" &&
|
|
117
|
+
test.operator === "!==" &&
|
|
118
|
+
test.left.type === "MemberExpression" &&
|
|
119
|
+
test.left.object.type === "Identifier" &&
|
|
120
|
+
test.left.property.type === "Identifier" &&
|
|
121
|
+
isLiteralNode(test.right)
|
|
122
|
+
) {
|
|
123
|
+
const [trueEnv, falseEnv] = narrowByDiscriminant(test.left.object.name, test.left.property.name, getLiteralValue(test.right), env);
|
|
124
|
+
return [falseEnv, trueEnv];
|
|
125
|
+
}
|
|
126
|
+
|
|
74
127
|
// x === literal
|
|
75
128
|
if (
|
|
76
129
|
test.type === "BinaryExpression" &&
|
|
@@ -123,12 +176,68 @@ export function narrow(
|
|
|
123
176
|
return narrowByInstanceof(test.left.name, test.right.name, env);
|
|
124
177
|
}
|
|
125
178
|
|
|
179
|
+
// x >= literal / x > literal / x <= literal / x < literal
|
|
180
|
+
if (
|
|
181
|
+
test.type === "BinaryExpression" &&
|
|
182
|
+
(test.operator === ">=" || test.operator === ">" || test.operator === "<=" || test.operator === "<") &&
|
|
183
|
+
test.left.type === "Identifier" &&
|
|
184
|
+
isLiteralNode(test.right)
|
|
185
|
+
) {
|
|
186
|
+
const litVal = getLiteralValue(test.right);
|
|
187
|
+
if (litVal.kind === "literal" && typeof litVal.value === "number") {
|
|
188
|
+
return narrowByComparison(test.left.name, test.operator, litVal.value, env);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// literal >= x / literal > x / literal <= x / literal < x
|
|
193
|
+
if (
|
|
194
|
+
test.type === "BinaryExpression" &&
|
|
195
|
+
(test.operator === ">=" || test.operator === ">" || test.operator === "<=" || test.operator === "<") &&
|
|
196
|
+
isLiteralNode(test.left) &&
|
|
197
|
+
test.right.type === "Identifier"
|
|
198
|
+
) {
|
|
199
|
+
const litVal = getLiteralValue(test.left);
|
|
200
|
+
if (litVal.kind === "literal" && typeof litVal.value === "number") {
|
|
201
|
+
const flipped = { ">=": "<=", ">": "<", "<=": ">=", "<": ">" } as const;
|
|
202
|
+
return narrowByComparison(test.right.name, flipped[test.operator as keyof typeof flipped], litVal.value, env);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// "key" in obj
|
|
207
|
+
if (
|
|
208
|
+
test.type === "BinaryExpression" &&
|
|
209
|
+
test.operator === "in" &&
|
|
210
|
+
test.left.type === "StringLiteral" &&
|
|
211
|
+
test.right.type === "Identifier"
|
|
212
|
+
) {
|
|
213
|
+
return narrowByPropertyIn(test.left.value, test.right.name, env);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Array.isArray(x)
|
|
217
|
+
if (
|
|
218
|
+
test.type === "CallExpression" &&
|
|
219
|
+
test.callee.type === "MemberExpression" &&
|
|
220
|
+
test.callee.object.type === "Identifier" &&
|
|
221
|
+
test.callee.object.name === "Array" &&
|
|
222
|
+
test.callee.property.type === "Identifier" &&
|
|
223
|
+
test.callee.property.name === "isArray" &&
|
|
224
|
+
test.arguments.length === 1 &&
|
|
225
|
+
test.arguments[0].type === "Identifier"
|
|
226
|
+
) {
|
|
227
|
+
return narrowByIsArray(test.arguments[0].name, env);
|
|
228
|
+
}
|
|
229
|
+
|
|
126
230
|
// !expr (negate)
|
|
127
231
|
if (test.type === "UnaryExpression" && test.operator === "!") {
|
|
128
232
|
const [trueEnv, falseEnv] = narrow(test.argument, env);
|
|
129
233
|
return [falseEnv, trueEnv];
|
|
130
234
|
}
|
|
131
235
|
|
|
236
|
+
// Truthiness narrowing: if (x)
|
|
237
|
+
if (test.type === "Identifier") {
|
|
238
|
+
return narrowByTruthy(test.name, env);
|
|
239
|
+
}
|
|
240
|
+
|
|
132
241
|
return [env, env];
|
|
133
242
|
}
|
|
134
243
|
|
|
@@ -136,13 +245,24 @@ function isTypeofExpr(node: Node): boolean {
|
|
|
136
245
|
return node.type === "UnaryExpression" && node.operator === "typeof";
|
|
137
246
|
}
|
|
138
247
|
|
|
139
|
-
|
|
248
|
+
/** typeof 目标:标识符(x)或成员访问链(ref.has / ns.fn) */
|
|
249
|
+
function getTypeofTarget(node: Node): { kind: "ident"; name: string } | { kind: "member"; objName: string; propName: string } | null {
|
|
140
250
|
if (
|
|
141
251
|
node.type === "UnaryExpression" &&
|
|
142
252
|
node.operator === "typeof" &&
|
|
143
253
|
node.argument.type === "Identifier"
|
|
144
254
|
) {
|
|
145
|
-
return node.argument.name;
|
|
255
|
+
return { kind: "ident", name: node.argument.name };
|
|
256
|
+
}
|
|
257
|
+
if (
|
|
258
|
+
node.type === "UnaryExpression" &&
|
|
259
|
+
node.operator === "typeof" &&
|
|
260
|
+
node.argument.type === "MemberExpression" &&
|
|
261
|
+
node.argument.object.type === "Identifier" &&
|
|
262
|
+
!node.argument.computed &&
|
|
263
|
+
node.argument.property.type === "Identifier"
|
|
264
|
+
) {
|
|
265
|
+
return { kind: "member", objName: node.argument.object.name, propName: node.argument.property.name };
|
|
146
266
|
}
|
|
147
267
|
return null;
|
|
148
268
|
}
|
|
@@ -174,11 +294,56 @@ const typeofToPrimitive: Record<string, TypeValue> = {
|
|
|
174
294
|
symbol: T.symbol,
|
|
175
295
|
};
|
|
176
296
|
|
|
297
|
+
/** 内置类实例的原型方法归属(与 evaluator classMethods 表对齐的子集) */
|
|
298
|
+
const INSTANCE_METHOD_OWNERS: Record<string, Set<string>> = {
|
|
299
|
+
has: new Set(["Set", "Map", "WeakSet", "WeakMap"]),
|
|
300
|
+
get: new Set(["Map", "WeakMap"]),
|
|
301
|
+
set: new Set(["Map", "WeakMap", "Set"]),
|
|
302
|
+
add: new Set(["Set", "WeakSet"]),
|
|
303
|
+
delete: new Set(["Set", "Map", "WeakSet", "WeakMap"]),
|
|
304
|
+
clear: new Set(["Set", "Map"]),
|
|
305
|
+
forEach: new Set(["Set", "Map", "Array"]),
|
|
306
|
+
size: new Set(["Set", "Map"]),
|
|
307
|
+
keys: new Set(["Set", "Map"]),
|
|
308
|
+
values: new Set(["Set", "Map"]),
|
|
309
|
+
entries: new Set(["Set", "Map"]),
|
|
310
|
+
test: new Set(["RegExp"]),
|
|
311
|
+
exec: new Set(["RegExp"]),
|
|
312
|
+
};
|
|
313
|
+
|
|
177
314
|
function narrowByTypeof(
|
|
178
|
-
|
|
315
|
+
target: { kind: "ident"; name: string } | { kind: "member"; objName: string; propName: string },
|
|
179
316
|
typeStr: string,
|
|
180
317
|
env: Environment,
|
|
181
318
|
): [Environment, Environment] {
|
|
319
|
+
// 成员目标(typeof ref.has === 'function'):按对象成员中 prop 的类型
|
|
320
|
+
// 过滤 union——留下 has 为函数的成员(如 Set),剔除 tuple/{}
|
|
321
|
+
if (target.kind === "member") {
|
|
322
|
+
const current = env.lookup(target.objName);
|
|
323
|
+
if (!current || current.kind !== "union") return [env, env];
|
|
324
|
+
const propMatches = (m: TypeValue): boolean => {
|
|
325
|
+
let prop: TypeValue | undefined;
|
|
326
|
+
if (m.kind === "object") prop = m.properties[target.propName];
|
|
327
|
+
else if (m.kind === "instance") {
|
|
328
|
+
// instance 的 properties 不含原型方法:按内置类方法表判断
|
|
329
|
+
// (Set/Map 的 has 等——与 evaluator 的 classMethods 表保持一致)
|
|
330
|
+
if (INSTANCE_METHOD_OWNERS[target.propName]?.has(m.className)) return true;
|
|
331
|
+
prop = m.properties[target.propName];
|
|
332
|
+
}
|
|
333
|
+
if (typeStr === "function") return prop?.kind === "function" || prop?.kind === "unknown";
|
|
334
|
+
return true;
|
|
335
|
+
};
|
|
336
|
+
const narrowed = narrowType(current, propMatches);
|
|
337
|
+
const excluded = subtractType(current, propMatches);
|
|
338
|
+
if (narrowed.kind === "never") return [env, env];
|
|
339
|
+
const trueEnv = env.extend({});
|
|
340
|
+
trueEnv.bind(target.objName, narrowed);
|
|
341
|
+
const falseEnv = env.extend({});
|
|
342
|
+
falseEnv.bind(target.objName, excluded.kind === "never" ? current : excluded);
|
|
343
|
+
return [trueEnv, falseEnv];
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const varName = target.name;
|
|
182
347
|
const current = env.lookup(varName);
|
|
183
348
|
const targetPrimitive = typeofToPrimitive[typeStr];
|
|
184
349
|
|
|
@@ -264,3 +429,109 @@ function narrowByStrictEqual(
|
|
|
264
429
|
falseEnv.bind(varName, excluded.kind === "never" ? current : excluded);
|
|
265
430
|
return [trueEnv, falseEnv];
|
|
266
431
|
}
|
|
432
|
+
|
|
433
|
+
function narrowByComparison(
|
|
434
|
+
varName: string,
|
|
435
|
+
op: ">=" | ">" | "<=" | "<",
|
|
436
|
+
value: number,
|
|
437
|
+
env: Environment,
|
|
438
|
+
): [Environment, Environment] {
|
|
439
|
+
const current = env.lookup(varName);
|
|
440
|
+
if (!isSubtypeOf(current, T.number) && current.kind !== "union") return [env, env];
|
|
441
|
+
|
|
442
|
+
const trueEnv = env.extend({});
|
|
443
|
+
const falseEnv = env.extend({});
|
|
444
|
+
|
|
445
|
+
const trueRange = op === ">=" ? createRange({ min: value })
|
|
446
|
+
: op === ">" ? createRange({ min: value + 1 })
|
|
447
|
+
: op === "<=" ? createRange({ max: value })
|
|
448
|
+
: createRange({ max: value - 1 });
|
|
449
|
+
|
|
450
|
+
const falseRange = op === ">=" ? createRange({ max: value - 1 })
|
|
451
|
+
: op === ">" ? createRange({ max: value })
|
|
452
|
+
: op === "<=" ? createRange({ min: value + 1 })
|
|
453
|
+
: createRange({ min: value });
|
|
454
|
+
|
|
455
|
+
trueEnv.bind(varName, trueRange);
|
|
456
|
+
falseEnv.bind(varName, falseRange);
|
|
457
|
+
return [trueEnv, falseEnv];
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function narrowByPropertyIn(propName: string, varName: string, env: Environment): [Environment, Environment] {
|
|
461
|
+
const current = env.lookup(varName);
|
|
462
|
+
|
|
463
|
+
const narrowed = narrowType(current, (m) =>
|
|
464
|
+
m.kind === "object" && propName in m.properties
|
|
465
|
+
);
|
|
466
|
+
const excluded = subtractType(current, (m) =>
|
|
467
|
+
m.kind === "object" && propName in m.properties
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
const trueEnv = env.extend({});
|
|
471
|
+
trueEnv.bind(varName, narrowed.kind === "never" ? current : narrowed);
|
|
472
|
+
const falseEnv = env.extend({});
|
|
473
|
+
falseEnv.bind(varName, excluded.kind === "never" ? current : excluded);
|
|
474
|
+
return [trueEnv, falseEnv];
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function narrowByIsArray(varName: string, env: Environment): [Environment, Environment] {
|
|
478
|
+
const current = env.lookup(varName);
|
|
479
|
+
|
|
480
|
+
const narrowed = narrowType(current, (m) => m.kind === "array" || m.kind === "tuple");
|
|
481
|
+
const excluded = subtractType(current, (m) => m.kind === "array" || m.kind === "tuple");
|
|
482
|
+
|
|
483
|
+
const trueEnv = env.extend({});
|
|
484
|
+
trueEnv.bind(varName, narrowed.kind === "never" ? T.array(T.unknown) : narrowed);
|
|
485
|
+
const falseEnv = env.extend({});
|
|
486
|
+
falseEnv.bind(varName, excluded.kind === "never" ? current : excluded);
|
|
487
|
+
return [trueEnv, falseEnv];
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function narrowByDiscriminant(
|
|
491
|
+
objName: string,
|
|
492
|
+
propName: string,
|
|
493
|
+
literalValue: TypeValue,
|
|
494
|
+
env: Environment,
|
|
495
|
+
): [Environment, Environment] {
|
|
496
|
+
const current = env.lookup(objName);
|
|
497
|
+
|
|
498
|
+
const narrowed = narrowType(current, (m) => {
|
|
499
|
+
if (m.kind !== "object") return false;
|
|
500
|
+
const propType = m.properties[propName];
|
|
501
|
+
if (!propType) return false;
|
|
502
|
+
return typeValueEquals(propType, literalValue);
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
const excluded = subtractType(current, (m) => {
|
|
506
|
+
if (m.kind !== "object") return false;
|
|
507
|
+
const propType = m.properties[propName];
|
|
508
|
+
if (!propType) return false;
|
|
509
|
+
return typeValueEquals(propType, literalValue);
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
const trueEnv = env.extend({});
|
|
513
|
+
trueEnv.bind(objName, narrowed.kind === "never" ? current : narrowed);
|
|
514
|
+
const falseEnv = env.extend({});
|
|
515
|
+
falseEnv.bind(objName, excluded.kind === "never" ? current : excluded);
|
|
516
|
+
return [trueEnv, falseEnv];
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function isFalsyType(tv: TypeValue): boolean {
|
|
520
|
+
if (tv.kind === "literal") {
|
|
521
|
+
return tv.value === null || tv.value === undefined || tv.value === false || tv.value === 0 || tv.value === "";
|
|
522
|
+
}
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function narrowByTruthy(varName: string, env: Environment): [Environment, Environment] {
|
|
527
|
+
const current = env.lookup(varName);
|
|
528
|
+
|
|
529
|
+
const narrowed = narrowType(current, (m) => !isFalsyType(m));
|
|
530
|
+
const excluded = subtractType(current, (m) => !isFalsyType(m));
|
|
531
|
+
|
|
532
|
+
const trueEnv = env.extend({});
|
|
533
|
+
trueEnv.bind(varName, narrowed.kind === "never" ? current : narrowed);
|
|
534
|
+
const falseEnv = env.extend({});
|
|
535
|
+
falseEnv.bind(varName, excluded.kind === "never" ? current : excluded);
|
|
536
|
+
return [trueEnv, falseEnv];
|
|
537
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { resolve, join, dirname } from "node:path";
|
|
3
|
+
|
|
4
|
+
function findNodeModules(startDir: string): string | null {
|
|
5
|
+
let dir = resolve(startDir);
|
|
6
|
+
const root = resolve("/");
|
|
7
|
+
|
|
8
|
+
while (dir !== root) {
|
|
9
|
+
const nmPath = join(dir, "node_modules");
|
|
10
|
+
if (existsSync(nmPath)) return nmPath;
|
|
11
|
+
const parent = dirname(dir);
|
|
12
|
+
if (parent === dir) break;
|
|
13
|
+
dir = parent;
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function resolveExportsNudo(exports: unknown, subpath: string): string | null {
|
|
19
|
+
if (!exports || typeof exports !== "object") return null;
|
|
20
|
+
|
|
21
|
+
const entry = (exports as Record<string, unknown>)[subpath];
|
|
22
|
+
if (!entry) return null;
|
|
23
|
+
|
|
24
|
+
if (typeof entry === "object" && entry !== null && "nudo" in entry) {
|
|
25
|
+
const nudoEntry = (entry as Record<string, unknown>)["nudo"];
|
|
26
|
+
if (typeof nudoEntry === "string") return nudoEntry;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function resolveNpmNudo(
|
|
33
|
+
source: string,
|
|
34
|
+
fromDir: string,
|
|
35
|
+
): string | null {
|
|
36
|
+
const isRelative = source.startsWith(".") || source.startsWith("/");
|
|
37
|
+
if (isRelative) return null;
|
|
38
|
+
|
|
39
|
+
const parts = source.startsWith("@")
|
|
40
|
+
? source.split("/").slice(0, 2)
|
|
41
|
+
: source.split("/").slice(0, 1);
|
|
42
|
+
const pkgName = parts.join("/");
|
|
43
|
+
const subpath = source.slice(pkgName.length) || ".";
|
|
44
|
+
|
|
45
|
+
const nodeModules = findNodeModules(fromDir);
|
|
46
|
+
if (!nodeModules) return null;
|
|
47
|
+
|
|
48
|
+
const pkgJsonPath = join(nodeModules, pkgName, "package.json");
|
|
49
|
+
if (!existsSync(pkgJsonPath)) return null;
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
53
|
+
const nudoEntry = resolveExportsNudo(pkg.exports, subpath);
|
|
54
|
+
if (nudoEntry) {
|
|
55
|
+
const resolved = resolve(dirname(pkgJsonPath), nudoEntry);
|
|
56
|
+
if (existsSync(resolved)) return resolved;
|
|
57
|
+
}
|
|
58
|
+
} catch {
|
|
59
|
+
// ignore parse errors
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return null;
|
|
63
|
+
}
|