@nudojs/service 1.0.0 → 2.0.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/dist/chunk-ADFCZP72.js +556 -0
- package/dist/config-Cqj8zeZH.d.ts +117 -0
- package/dist/evaluator/evaluator-api.d.ts +10 -21
- package/dist/evaluator/evaluator-api.js +11 -53
- package/dist/index.d.ts +539 -52
- package/dist/index.js +3780 -1490
- package/package.json +5 -5
- package/dist/chunk-FSIHAE7I.js +0 -5871
- package/dist/config-UlReSTfh.d.ts +0 -152
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
// src/evaluator/builtins/builtin-prototype.ts
|
|
2
|
+
import {
|
|
3
|
+
abs,
|
|
4
|
+
objOf,
|
|
5
|
+
formatAbs,
|
|
6
|
+
relationFn
|
|
7
|
+
} from "@nudojs/core";
|
|
8
|
+
function hasOwnProp(props, name) {
|
|
9
|
+
return Object.prototype.hasOwnProperty.call(props, name);
|
|
10
|
+
}
|
|
11
|
+
var BUILTIN_ERROR_CLASSES = /* @__PURE__ */ new Set([
|
|
12
|
+
"Error",
|
|
13
|
+
"TypeError",
|
|
14
|
+
"SyntaxError",
|
|
15
|
+
"RangeError",
|
|
16
|
+
"ReferenceError",
|
|
17
|
+
"URIError",
|
|
18
|
+
"EvalError"
|
|
19
|
+
]);
|
|
20
|
+
var BUILTIN_PROTOTYPE_CLASSES = /* @__PURE__ */ new Set([
|
|
21
|
+
...BUILTIN_ERROR_CLASSES,
|
|
22
|
+
"Date",
|
|
23
|
+
"Object",
|
|
24
|
+
"Map",
|
|
25
|
+
"Set",
|
|
26
|
+
"Promise",
|
|
27
|
+
"RegExp",
|
|
28
|
+
"Array",
|
|
29
|
+
"Function",
|
|
30
|
+
"String",
|
|
31
|
+
"Number",
|
|
32
|
+
"Boolean",
|
|
33
|
+
"Symbol",
|
|
34
|
+
"WeakMap",
|
|
35
|
+
"WeakSet",
|
|
36
|
+
"Buffer"
|
|
37
|
+
]);
|
|
38
|
+
var numA = { shape: { k: "prim", type: "number" }, conf: "exact" };
|
|
39
|
+
var strA = { shape: { k: "prim", type: "string" }, conf: "exact" };
|
|
40
|
+
var boolA = { shape: { k: "prim", type: "boolean" }, conf: "exact" };
|
|
41
|
+
var unkA = { shape: { k: "unknown" }, conf: "partial" };
|
|
42
|
+
var undefA = abs({ k: "unknown" }, { op: "lit", value: void 0 }, void 0, "exact");
|
|
43
|
+
var nullA = abs({ k: "unknown" }, { op: "lit", value: null }, void 0, "exact");
|
|
44
|
+
function arrOf(el) {
|
|
45
|
+
return { shape: { k: "arr", element: el }, conf: "exact" };
|
|
46
|
+
}
|
|
47
|
+
function tupleOf(els) {
|
|
48
|
+
return { shape: { k: "tuple", elements: els }, conf: "exact" };
|
|
49
|
+
}
|
|
50
|
+
function promiseOf(inner) {
|
|
51
|
+
return { shape: { k: "eff", eff: "promise", inner }, conf: "exact" };
|
|
52
|
+
}
|
|
53
|
+
function sumOf(...members) {
|
|
54
|
+
if (members.length === 1) return members[0];
|
|
55
|
+
return { shape: { k: "sum", members }, conf: "exact" };
|
|
56
|
+
}
|
|
57
|
+
function brand(name) {
|
|
58
|
+
return { shape: { k: "brand", name, shape: unkA }, conf: "path" };
|
|
59
|
+
}
|
|
60
|
+
function emptyObj() {
|
|
61
|
+
return objOf({});
|
|
62
|
+
}
|
|
63
|
+
function sig(params, ret) {
|
|
64
|
+
return relationFn(params, ret, {
|
|
65
|
+
conf: "exact",
|
|
66
|
+
params: params.map((_, i) => `_arg${i}`)
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
var OBJECT_PROTOTYPE_METHODS = {
|
|
70
|
+
hasOwnProperty: sig([unkA], boolA),
|
|
71
|
+
isPrototypeOf: sig([unkA], boolA),
|
|
72
|
+
propertyIsEnumerable: sig([unkA], boolA),
|
|
73
|
+
toString: sig([], strA),
|
|
74
|
+
toLocaleString: sig([], strA),
|
|
75
|
+
valueOf: sig([], unkA)
|
|
76
|
+
};
|
|
77
|
+
var BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS = {
|
|
78
|
+
Object: { ...OBJECT_PROTOTYPE_METHODS },
|
|
79
|
+
Array: {
|
|
80
|
+
push: sig([unkA], numA),
|
|
81
|
+
pop: sig([], unkA),
|
|
82
|
+
shift: sig([], unkA),
|
|
83
|
+
unshift: sig([unkA], numA),
|
|
84
|
+
slice: sig([numA, numA], arrOf(unkA)),
|
|
85
|
+
splice: sig([numA, numA], arrOf(unkA)),
|
|
86
|
+
concat: sig([unkA], arrOf(unkA)),
|
|
87
|
+
join: sig([strA], strA),
|
|
88
|
+
indexOf: sig([unkA], numA),
|
|
89
|
+
lastIndexOf: sig([unkA], numA),
|
|
90
|
+
includes: sig([unkA], boolA),
|
|
91
|
+
map: sig([unkA], arrOf(unkA)),
|
|
92
|
+
flatMap: sig([unkA], arrOf(unkA)),
|
|
93
|
+
filter: sig([unkA], arrOf(unkA)),
|
|
94
|
+
forEach: sig([unkA], undefA),
|
|
95
|
+
find: sig([unkA], unkA),
|
|
96
|
+
findIndex: sig([unkA], numA),
|
|
97
|
+
some: sig([unkA], boolA),
|
|
98
|
+
every: sig([unkA], boolA),
|
|
99
|
+
reduce: sig([unkA, unkA], unkA),
|
|
100
|
+
sort: sig([unkA], arrOf(unkA)),
|
|
101
|
+
reverse: sig([], arrOf(unkA)),
|
|
102
|
+
toString: sig([], strA)
|
|
103
|
+
},
|
|
104
|
+
Function: {
|
|
105
|
+
call: sig([unkA], unkA),
|
|
106
|
+
apply: sig([unkA, unkA], unkA),
|
|
107
|
+
bind: sig([unkA], unkA),
|
|
108
|
+
toString: sig([], strA)
|
|
109
|
+
},
|
|
110
|
+
Map: {
|
|
111
|
+
get: sig([unkA], unkA),
|
|
112
|
+
set: sig([unkA, unkA], unkA),
|
|
113
|
+
has: sig([unkA], boolA),
|
|
114
|
+
delete: sig([unkA], boolA),
|
|
115
|
+
clear: sig([], undefA),
|
|
116
|
+
forEach: sig([unkA], undefA),
|
|
117
|
+
keys: sig([], arrOf(unkA)),
|
|
118
|
+
values: sig([], arrOf(unkA)),
|
|
119
|
+
entries: sig([], arrOf(tupleOf([unkA, unkA]))),
|
|
120
|
+
toString: sig([], strA)
|
|
121
|
+
},
|
|
122
|
+
Set: {
|
|
123
|
+
add: sig([unkA], unkA),
|
|
124
|
+
has: sig([unkA], boolA),
|
|
125
|
+
delete: sig([unkA], boolA),
|
|
126
|
+
clear: sig([], undefA),
|
|
127
|
+
forEach: sig([unkA], undefA),
|
|
128
|
+
keys: sig([], arrOf(unkA)),
|
|
129
|
+
values: sig([], arrOf(unkA)),
|
|
130
|
+
entries: sig([], arrOf(tupleOf([unkA, unkA]))),
|
|
131
|
+
toString: sig([], strA)
|
|
132
|
+
},
|
|
133
|
+
WeakMap: {
|
|
134
|
+
get: sig([unkA], unkA),
|
|
135
|
+
set: sig([unkA, unkA], unkA),
|
|
136
|
+
has: sig([unkA], boolA),
|
|
137
|
+
delete: sig([unkA], boolA),
|
|
138
|
+
toString: sig([], strA)
|
|
139
|
+
},
|
|
140
|
+
WeakSet: {
|
|
141
|
+
add: sig([unkA], unkA),
|
|
142
|
+
has: sig([unkA], boolA),
|
|
143
|
+
delete: sig([unkA], boolA),
|
|
144
|
+
toString: sig([], strA)
|
|
145
|
+
},
|
|
146
|
+
Promise: {
|
|
147
|
+
then: sig([unkA], promiseOf(unkA)),
|
|
148
|
+
catch: sig([unkA], promiseOf(unkA)),
|
|
149
|
+
finally: sig([unkA], promiseOf(unkA)),
|
|
150
|
+
toString: sig([], strA)
|
|
151
|
+
},
|
|
152
|
+
Date: {
|
|
153
|
+
getTime: sig([], numA),
|
|
154
|
+
valueOf: sig([], numA),
|
|
155
|
+
toISOString: sig([], strA),
|
|
156
|
+
toJSON: sig([], strA),
|
|
157
|
+
toLocaleString: sig([], strA),
|
|
158
|
+
toString: sig([], strA)
|
|
159
|
+
},
|
|
160
|
+
RegExp: {
|
|
161
|
+
test: sig([strA], boolA),
|
|
162
|
+
exec: sig([strA], sumOf(emptyObj(), nullA)),
|
|
163
|
+
toString: sig([], strA)
|
|
164
|
+
},
|
|
165
|
+
String: {
|
|
166
|
+
charAt: sig([numA], strA),
|
|
167
|
+
charCodeAt: sig([numA], numA),
|
|
168
|
+
indexOf: sig([strA], numA),
|
|
169
|
+
lastIndexOf: sig([strA], numA),
|
|
170
|
+
includes: sig([strA], boolA),
|
|
171
|
+
startsWith: sig([strA], boolA),
|
|
172
|
+
endsWith: sig([strA], boolA),
|
|
173
|
+
slice: sig([numA, numA], strA),
|
|
174
|
+
substring: sig([numA, numA], strA),
|
|
175
|
+
toUpperCase: sig([], strA),
|
|
176
|
+
toLowerCase: sig([], strA),
|
|
177
|
+
trim: sig([], strA),
|
|
178
|
+
replace: sig([unkA, strA], strA),
|
|
179
|
+
split: sig([strA], arrOf(strA)),
|
|
180
|
+
toString: sig([], strA),
|
|
181
|
+
valueOf: sig([], strA)
|
|
182
|
+
},
|
|
183
|
+
Number: {
|
|
184
|
+
toFixed: sig([numA], strA),
|
|
185
|
+
toPrecision: sig([numA], strA),
|
|
186
|
+
valueOf: sig([], numA),
|
|
187
|
+
toString: sig([numA], strA)
|
|
188
|
+
},
|
|
189
|
+
Boolean: {
|
|
190
|
+
valueOf: sig([], boolA),
|
|
191
|
+
toString: sig([], strA)
|
|
192
|
+
},
|
|
193
|
+
Symbol: {
|
|
194
|
+
toString: sig([], strA),
|
|
195
|
+
valueOf: sig([], brand("Symbol"))
|
|
196
|
+
},
|
|
197
|
+
Buffer: {
|
|
198
|
+
equals: sig([unkA], boolA),
|
|
199
|
+
compare: sig([unkA], numA),
|
|
200
|
+
toString: sig([unkA], strA),
|
|
201
|
+
toJSON: sig([], unkA)
|
|
202
|
+
},
|
|
203
|
+
Error: {
|
|
204
|
+
toString: sig([], strA)
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
function fmtNoConf(a) {
|
|
208
|
+
return formatAbs(a).replace(/\s+#(exact|path|widened|mock|partial|opaque)$/, "");
|
|
209
|
+
}
|
|
210
|
+
function describeAbsMember(a) {
|
|
211
|
+
if (a.shape.k !== "fn") return fmtNoConf(a);
|
|
212
|
+
const s = a.shape;
|
|
213
|
+
const pts = s.paramTypes ?? [];
|
|
214
|
+
const params = pts.map((p, i) => `${s.params[i] ?? `arg${i}`}: ${fmtNoConf(p)}`).join(", ");
|
|
215
|
+
const ret = s.returnType ? fmtNoConf(s.returnType) : "unknown";
|
|
216
|
+
return `(${params}) => ${ret}`;
|
|
217
|
+
}
|
|
218
|
+
function builtinProtoMemberNames(className) {
|
|
219
|
+
const table = hasOwnProp(BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS, className) ? BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS[className] : void 0;
|
|
220
|
+
return table ? Object.keys(table) : [];
|
|
221
|
+
}
|
|
222
|
+
function builtinProtoMember(className, member) {
|
|
223
|
+
const table = hasOwnProp(BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS, className) ? BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS[className] : BUILTIN_ERROR_CLASSES.has(className) ? BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS.Error : void 0;
|
|
224
|
+
if (!table) return null;
|
|
225
|
+
return table[member] ?? null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/evaluator/env-loader.ts
|
|
229
|
+
import { readFileSync, existsSync, statSync, mkdirSync, writeFileSync } from "fs";
|
|
230
|
+
import { resolve as resolvePath, join as joinPath } from "path";
|
|
231
|
+
import { pathToFileURL } from "url";
|
|
232
|
+
import { createRequire } from "module";
|
|
233
|
+
import { createHash } from "crypto";
|
|
234
|
+
import { tmpdir } from "os";
|
|
235
|
+
import { defineEnv as defineEsEnv } from "@nudojs/env/es";
|
|
236
|
+
import { defineEnv as defineWebEnv } from "@nudojs/env/web";
|
|
237
|
+
import { defineEnv as defineNodeEnv } from "@nudojs/env/node";
|
|
238
|
+
var envFactories = {
|
|
239
|
+
es: defineEsEnv,
|
|
240
|
+
web: defineWebEnv,
|
|
241
|
+
node: defineNodeEnv
|
|
242
|
+
};
|
|
243
|
+
var impliedDeps = {
|
|
244
|
+
web: ["es"],
|
|
245
|
+
node: ["es"]
|
|
246
|
+
};
|
|
247
|
+
function resolveEnvNames(names) {
|
|
248
|
+
const resolved = /* @__PURE__ */ new Set();
|
|
249
|
+
const visit = (name) => {
|
|
250
|
+
if (resolved.has(name)) return;
|
|
251
|
+
const deps = impliedDeps[name];
|
|
252
|
+
if (deps) deps.forEach(visit);
|
|
253
|
+
resolved.add(name);
|
|
254
|
+
};
|
|
255
|
+
names.forEach(visit);
|
|
256
|
+
return [...resolved];
|
|
257
|
+
}
|
|
258
|
+
var pathEnvCache = /* @__PURE__ */ new Map();
|
|
259
|
+
var pathEnvByPath = /* @__PURE__ */ new Map();
|
|
260
|
+
var pathEnvBaseDirs = /* @__PURE__ */ new Set();
|
|
261
|
+
function isPathEnvName(name, baseDir) {
|
|
262
|
+
if (name in envFactories) return false;
|
|
263
|
+
if (name.includes("/") || name.startsWith("./") || name.startsWith("../")) return true;
|
|
264
|
+
const resolved = resolvePath(baseDir, name);
|
|
265
|
+
return resolved.endsWith(".ts") && existsSync(resolved);
|
|
266
|
+
}
|
|
267
|
+
function rewriteBareImports(text) {
|
|
268
|
+
const require2 = createRequire(import.meta.url);
|
|
269
|
+
let rewrote = false;
|
|
270
|
+
const out = text.replace(/(["'])(@nudojs\/[a-z0-9-]+)\1/g, (_m, quote, spec) => {
|
|
271
|
+
try {
|
|
272
|
+
const entry = require2.resolve(spec);
|
|
273
|
+
const url = pathToFileURL(entry).href;
|
|
274
|
+
rewrote = true;
|
|
275
|
+
return `${quote}${url}${quote}`;
|
|
276
|
+
} catch {
|
|
277
|
+
return `${quote}${spec}${quote}`;
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
return rewrote ? out : null;
|
|
281
|
+
}
|
|
282
|
+
async function importPathEnv(resolvedPath, mtimeMs) {
|
|
283
|
+
const cacheKey = `${resolvedPath}:${mtimeMs}`;
|
|
284
|
+
if (pathEnvCache.has(cacheKey)) return;
|
|
285
|
+
let mod = null;
|
|
286
|
+
try {
|
|
287
|
+
const url = pathToFileURL(resolvedPath).href + `?mtime=${mtimeMs}`;
|
|
288
|
+
mod = await import(url);
|
|
289
|
+
} catch {
|
|
290
|
+
mod = null;
|
|
291
|
+
}
|
|
292
|
+
if (!mod) {
|
|
293
|
+
try {
|
|
294
|
+
const text = readFileSync(resolvedPath, "utf-8");
|
|
295
|
+
const rewritten = rewriteBareImports(text);
|
|
296
|
+
if (rewritten === null) return;
|
|
297
|
+
const cacheDir = joinPath(tmpdir(), "nudo-env");
|
|
298
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
299
|
+
const hash = createHash("md5").update(`${resolvedPath}:${mtimeMs}`).digest("hex").slice(0, 16);
|
|
300
|
+
const copyPath = joinPath(cacheDir, `${hash}.ts`);
|
|
301
|
+
writeFileSync(copyPath, rewritten, "utf-8");
|
|
302
|
+
mod = await import(pathToFileURL(copyPath).href);
|
|
303
|
+
} catch {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (mod && typeof mod.defineEnv === "function") {
|
|
308
|
+
pathEnvCache.set(cacheKey, mod.defineEnv);
|
|
309
|
+
pathEnvByPath.set(resolvedPath, {
|
|
310
|
+
factory: mod.defineEnv,
|
|
311
|
+
mtimeMs
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
function lookupPathEnv(name) {
|
|
316
|
+
if (pathEnvByPath.size === 0) return void 0;
|
|
317
|
+
const candidates = name.startsWith("/") ? [name] : [name, ...[...pathEnvBaseDirs].map((d) => resolvePath(d, name))];
|
|
318
|
+
for (const candidate of candidates) {
|
|
319
|
+
const hit = pathEnvByPath.get(candidate);
|
|
320
|
+
if (!hit) continue;
|
|
321
|
+
try {
|
|
322
|
+
const { mtimeMs } = statSync(candidate);
|
|
323
|
+
if (mtimeMs !== hit.mtimeMs) {
|
|
324
|
+
pathEnvByPath.delete(candidate);
|
|
325
|
+
return void 0;
|
|
326
|
+
}
|
|
327
|
+
} catch {
|
|
328
|
+
pathEnvByPath.delete(candidate);
|
|
329
|
+
return void 0;
|
|
330
|
+
}
|
|
331
|
+
return hit.factory;
|
|
332
|
+
}
|
|
333
|
+
return void 0;
|
|
334
|
+
}
|
|
335
|
+
function clearPathEnvCaches() {
|
|
336
|
+
pathEnvCache.clear();
|
|
337
|
+
pathEnvByPath.clear();
|
|
338
|
+
pathEnvBaseDirs.clear();
|
|
339
|
+
}
|
|
340
|
+
async function preloadPathEnvs(envNames, baseDir) {
|
|
341
|
+
pathEnvBaseDirs.add(baseDir);
|
|
342
|
+
for (const name of envNames) {
|
|
343
|
+
if (!isPathEnvName(name, baseDir)) continue;
|
|
344
|
+
const resolved = resolvePath(baseDir, name);
|
|
345
|
+
if (!existsSync(resolved)) continue;
|
|
346
|
+
try {
|
|
347
|
+
const { mtimeMs } = statSync(resolved);
|
|
348
|
+
await importPathEnv(resolved, mtimeMs);
|
|
349
|
+
} catch {
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
async function loadEnvsAsync(envNames, globalEnv, baseDir = process.cwd()) {
|
|
354
|
+
await preloadPathEnvs(envNames, baseDir);
|
|
355
|
+
return loadEnvs(envNames, globalEnv);
|
|
356
|
+
}
|
|
357
|
+
function loadEnvs(envNames, globalEnv) {
|
|
358
|
+
const allModules = {};
|
|
359
|
+
const allGlobals = {};
|
|
360
|
+
const resolved = resolveEnvNames(envNames);
|
|
361
|
+
for (const name of resolved) {
|
|
362
|
+
const factory = envFactories[name] ?? lookupPathEnv(name);
|
|
363
|
+
if (!factory) continue;
|
|
364
|
+
const def = factory();
|
|
365
|
+
for (const [key, value] of Object.entries(def.globals)) {
|
|
366
|
+
allGlobals[key] = value;
|
|
367
|
+
globalEnv.bind(key, value);
|
|
368
|
+
}
|
|
369
|
+
if (def.modules) {
|
|
370
|
+
for (const [modName, exports] of Object.entries(def.modules)) {
|
|
371
|
+
allModules[modName] = { ...allModules[modName], ...exports };
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return { modules: allModules, globals: allGlobals };
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// src/evaluator/config.ts
|
|
379
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
380
|
+
import { resolve, dirname, relative, sep } from "path";
|
|
381
|
+
var DEFAULT_ANALYSIS_EXCLUDE = [
|
|
382
|
+
"**/node_modules/**",
|
|
383
|
+
"**/dist/**",
|
|
384
|
+
"**/coverage/**"
|
|
385
|
+
];
|
|
386
|
+
var DEFAULT_ANALYSIS_MODE = "exports";
|
|
387
|
+
function toStringArray(raw, fallback) {
|
|
388
|
+
if (raw === void 0) return fallback;
|
|
389
|
+
const arr = Array.isArray(raw) ? raw : [raw];
|
|
390
|
+
const out = arr.filter((s) => typeof s === "string" && s.length > 0);
|
|
391
|
+
return out.length > 0 ? out : fallback;
|
|
392
|
+
}
|
|
393
|
+
function analysisConfig(config) {
|
|
394
|
+
const raw = config?.analysis;
|
|
395
|
+
const modeRaw = raw?.mode;
|
|
396
|
+
const mode = modeRaw === "exports" || modeRaw === "all" || modeRaw === "directives" ? modeRaw : DEFAULT_ANALYSIS_MODE;
|
|
397
|
+
const diagRaw = raw?.diagnostics;
|
|
398
|
+
const diagnostics = diagRaw === "off" || diagRaw === "errors" || diagRaw === "default" || diagRaw === "verbose" ? diagRaw : mode === "directives" ? "errors" : "default";
|
|
399
|
+
const budgetRaw = raw?.callSiteBudget;
|
|
400
|
+
const callSiteBudget = typeof budgetRaw === "number" && Number.isFinite(budgetRaw) && budgetRaw >= 1 ? Math.min(Math.floor(budgetRaw), 64) : 3;
|
|
401
|
+
return {
|
|
402
|
+
// include 空数组 = 不过滤(与「省略」同义)
|
|
403
|
+
include: toStringArray(raw?.include, []),
|
|
404
|
+
// exclude 空数组回落默认安全列表,避免误关 node_modules 保护
|
|
405
|
+
exclude: toStringArray(raw?.exclude, DEFAULT_ANALYSIS_EXCLUDE),
|
|
406
|
+
mode,
|
|
407
|
+
diagnostics,
|
|
408
|
+
callSiteBudget,
|
|
409
|
+
evalMissingSlot: raw?.evalMissingSlot === "warning" ? "warning" : "off"
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
function diskCacheRoot(config, projectDir) {
|
|
413
|
+
const raw = config?.cache;
|
|
414
|
+
if (raw === false) return void 0;
|
|
415
|
+
if (typeof raw === "string" && raw.length > 0) {
|
|
416
|
+
return projectDir ? resolve(projectDir, raw) : raw;
|
|
417
|
+
}
|
|
418
|
+
if (raw === true) {
|
|
419
|
+
return projectDir ? resolve(projectDir, ".nudo/cache") : void 0;
|
|
420
|
+
}
|
|
421
|
+
const env = process.env.NUDO_CACHE_DIR;
|
|
422
|
+
if (env === "off" || env === "0") return void 0;
|
|
423
|
+
if (env && env.length > 0) return env;
|
|
424
|
+
return void 0;
|
|
425
|
+
}
|
|
426
|
+
function interfaceConfig(config) {
|
|
427
|
+
const raw = config?.interface?.emit;
|
|
428
|
+
const emit = raw === void 0 ? [] : Array.isArray(raw) ? raw.filter((s) => typeof s === "string" && s.length > 0) : typeof raw === "string" && raw.length > 0 ? [raw] : [];
|
|
429
|
+
return {
|
|
430
|
+
autoBind: config?.interface?.autoBind ?? true,
|
|
431
|
+
emit
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
function matchesEmitAllowlist(absPath, projectDir, patterns) {
|
|
435
|
+
if (patterns.length === 0) return true;
|
|
436
|
+
if (!projectDir) return false;
|
|
437
|
+
const rel = relative(projectDir, absPath).split(sep).join("/");
|
|
438
|
+
if (rel.startsWith("..")) return false;
|
|
439
|
+
return patterns.some(
|
|
440
|
+
(p) => globMatch(
|
|
441
|
+
p.split(sep).join("/").replace(/^\.\//, ""),
|
|
442
|
+
rel
|
|
443
|
+
)
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
function globMatch(pattern, path) {
|
|
447
|
+
let rx = "";
|
|
448
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
449
|
+
const ch = pattern[i];
|
|
450
|
+
if (ch === "*" && pattern[i + 1] === "*") {
|
|
451
|
+
if (pattern[i + 2] === "/") {
|
|
452
|
+
rx += "(?:.*/)?";
|
|
453
|
+
i += 2;
|
|
454
|
+
} else {
|
|
455
|
+
rx += ".*";
|
|
456
|
+
i += 1;
|
|
457
|
+
}
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
if (ch === "*") {
|
|
461
|
+
rx += "[^/]*";
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
if (ch === "?") {
|
|
465
|
+
rx += "[^/]";
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
rx += /[.+^${}()|[\]\\]/.test(ch) ? `\\${ch}` : ch;
|
|
469
|
+
}
|
|
470
|
+
return new RegExp(`^${rx}$`).test(path);
|
|
471
|
+
}
|
|
472
|
+
function findProjectConfig(startDir) {
|
|
473
|
+
let dir = resolve(startDir);
|
|
474
|
+
const root = resolve("/");
|
|
475
|
+
while (dir !== root) {
|
|
476
|
+
const pkgPath = resolve(dir, "package.json");
|
|
477
|
+
if (existsSync2(pkgPath)) {
|
|
478
|
+
try {
|
|
479
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
480
|
+
if (pkg.nudo) {
|
|
481
|
+
return { config: pkg.nudo, projectDir: dir };
|
|
482
|
+
}
|
|
483
|
+
} catch {
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
const parent = dirname(dir);
|
|
487
|
+
if (parent === dir) break;
|
|
488
|
+
dir = parent;
|
|
489
|
+
}
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// src/evaluator/resolve-npm.ts
|
|
494
|
+
import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
|
|
495
|
+
import { resolve as resolve2, join, dirname as dirname2 } from "path";
|
|
496
|
+
function findNodeModules(startDir) {
|
|
497
|
+
let dir = resolve2(startDir);
|
|
498
|
+
const root = resolve2("/");
|
|
499
|
+
while (dir !== root) {
|
|
500
|
+
const nmPath = join(dir, "node_modules");
|
|
501
|
+
if (existsSync3(nmPath)) return nmPath;
|
|
502
|
+
const parent = dirname2(dir);
|
|
503
|
+
if (parent === dir) break;
|
|
504
|
+
dir = parent;
|
|
505
|
+
}
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
function resolveExportsNudo(exports, subpath) {
|
|
509
|
+
if (!exports || typeof exports !== "object") return null;
|
|
510
|
+
const entry = exports[subpath];
|
|
511
|
+
if (!entry) return null;
|
|
512
|
+
if (typeof entry === "object" && entry !== null && "nudo" in entry) {
|
|
513
|
+
const nudoEntry = entry["nudo"];
|
|
514
|
+
if (typeof nudoEntry === "string") return nudoEntry;
|
|
515
|
+
}
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
function resolveNpmNudo(source, fromDir) {
|
|
519
|
+
const isRelative = source.startsWith(".") || source.startsWith("/");
|
|
520
|
+
if (isRelative) return null;
|
|
521
|
+
const parts = source.startsWith("@") ? source.split("/").slice(0, 2) : source.split("/").slice(0, 1);
|
|
522
|
+
const pkgName = parts.join("/");
|
|
523
|
+
const subpath = source.slice(pkgName.length) || ".";
|
|
524
|
+
const nodeModules = findNodeModules(fromDir);
|
|
525
|
+
if (!nodeModules) return null;
|
|
526
|
+
const pkgJsonPath = join(nodeModules, pkgName, "package.json");
|
|
527
|
+
if (!existsSync3(pkgJsonPath)) return null;
|
|
528
|
+
try {
|
|
529
|
+
const pkg = JSON.parse(readFileSync3(pkgJsonPath, "utf-8"));
|
|
530
|
+
const nudoEntry = resolveExportsNudo(pkg.exports, subpath);
|
|
531
|
+
if (nudoEntry) {
|
|
532
|
+
const resolved = resolve2(dirname2(pkgJsonPath), nudoEntry);
|
|
533
|
+
if (existsSync3(resolved)) return resolved;
|
|
534
|
+
}
|
|
535
|
+
} catch {
|
|
536
|
+
}
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
export {
|
|
541
|
+
clearPathEnvCaches,
|
|
542
|
+
preloadPathEnvs,
|
|
543
|
+
loadEnvsAsync,
|
|
544
|
+
loadEnvs,
|
|
545
|
+
DEFAULT_ANALYSIS_MODE,
|
|
546
|
+
analysisConfig,
|
|
547
|
+
diskCacheRoot,
|
|
548
|
+
interfaceConfig,
|
|
549
|
+
matchesEmitAllowlist,
|
|
550
|
+
findProjectConfig,
|
|
551
|
+
resolveNpmNudo,
|
|
552
|
+
BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS,
|
|
553
|
+
describeAbsMember,
|
|
554
|
+
builtinProtoMemberNames,
|
|
555
|
+
builtinProtoMember
|
|
556
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { Abs, Environment } from '@nudojs/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 调用点记录(CallRecord)。Abs 唯一真理源。
|
|
5
|
+
* argAbs/resultAbs/throwsAbs 必填;展示/外延在 CaseResult 边界再桥。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
type CallRecord = {
|
|
9
|
+
fnName: string;
|
|
10
|
+
/** 无损参数 Abs */
|
|
11
|
+
argAbs: Abs[];
|
|
12
|
+
/** 无损结果 Abs(threw 时为 never) */
|
|
13
|
+
resultAbs: Abs;
|
|
14
|
+
/** 无损抛出值 Abs(未抛为 never) */
|
|
15
|
+
throwsAbs: Abs;
|
|
16
|
+
/** Line-relative. Per-fn cache replay shifts this by lineDelta — if you
|
|
17
|
+
* add another position field (callee loc, arg loc), extend
|
|
18
|
+
* shiftCallRecordLines in analyzer.ts in the same change. */
|
|
19
|
+
callLoc?: {
|
|
20
|
+
line: number;
|
|
21
|
+
column: number;
|
|
22
|
+
};
|
|
23
|
+
targetModule?: string;
|
|
24
|
+
targetExport?: string;
|
|
25
|
+
/** export names the same function value was re-exported under after its
|
|
26
|
+
* defining module (barrel `index.js`, CJS forwarding shims); usage-site
|
|
27
|
+
* records stay name-matchable against them */
|
|
28
|
+
targetAliases?: string[];
|
|
29
|
+
/** module whose evaluation created the function value (definition site). */
|
|
30
|
+
fnModule?: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type LoadedEnv = {
|
|
34
|
+
/** Abs 原生模块导出(B 路径 / Abs 模块图) */
|
|
35
|
+
modules: Record<string, Record<string, Abs>>;
|
|
36
|
+
globals: Record<string, Abs>;
|
|
37
|
+
};
|
|
38
|
+
/** Host cache-clear hooks (CLI watch / vite / tests) must drop path-env modules too */
|
|
39
|
+
declare function clearPathEnvCaches(): void;
|
|
40
|
+
declare function preloadPathEnvs(envNames: string[], baseDir: string): Promise<void>;
|
|
41
|
+
declare function loadEnvsAsync(envNames: string[], globalEnv: Environment, baseDir?: string): Promise<LoadedEnv>;
|
|
42
|
+
declare function loadEnvs(envNames: string[], globalEnv: Environment): LoadedEnv;
|
|
43
|
+
|
|
44
|
+
type NudoConfig = {
|
|
45
|
+
env?: string[];
|
|
46
|
+
mocks?: Record<string, string>;
|
|
47
|
+
interface?: {
|
|
48
|
+
/** 侧车 ambient 绑定总开关(check/LSP 执法与 interface 打印共用) */
|
|
49
|
+
autoBind?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* emit 白名单(Phase 3,§7.3):glob 数组,相对 projectDir。
|
|
52
|
+
* 省略/空 = 不按路径过滤(仍受 --fn/--all 与「默认只刷已有生成段」约束)。
|
|
53
|
+
*/
|
|
54
|
+
emit?: string[] | string;
|
|
55
|
+
};
|
|
56
|
+
/** 分析范围与噪声档(design-analysis-scope.md / A2) */
|
|
57
|
+
analysis?: {
|
|
58
|
+
include?: string[] | string;
|
|
59
|
+
exclude?: string[] | string;
|
|
60
|
+
/** directives | exports(默认)| all */
|
|
61
|
+
mode?: string;
|
|
62
|
+
/** off | errors | default | verbose */
|
|
63
|
+
diagnostics?: string;
|
|
64
|
+
/** polyvariant:保留的精确调用点 case 上限(默认 3);超出进 symbolic #widened */
|
|
65
|
+
callSiteBudget?: number;
|
|
66
|
+
/** C0.5:求值命中闭对象缺字段 → nudo:missing-slot;默认 off */
|
|
67
|
+
evalMissingSlot?: "off" | "warning";
|
|
68
|
+
};
|
|
69
|
+
/** 磁盘缓存(B3):true → `.nudo/cache`;字符串 → 自定义根;false/省略 → 关 */
|
|
70
|
+
cache?: boolean | string;
|
|
71
|
+
};
|
|
72
|
+
type InterfaceConfig = {
|
|
73
|
+
autoBind: boolean;
|
|
74
|
+
/** emit 路径白名单(已归一化;空数组 = 不限制) */
|
|
75
|
+
emit: string[];
|
|
76
|
+
};
|
|
77
|
+
type AnalysisMode = "directives" | "exports" | "all";
|
|
78
|
+
type DiagnosticsLevel = "off" | "errors" | "default" | "verbose";
|
|
79
|
+
type AnalysisConfig = {
|
|
80
|
+
include: string[];
|
|
81
|
+
exclude: string[];
|
|
82
|
+
mode: AnalysisMode;
|
|
83
|
+
diagnostics: DiagnosticsLevel;
|
|
84
|
+
/** polyvariant 精确调用点上限(B4) */
|
|
85
|
+
callSiteBudget: number;
|
|
86
|
+
/** C0.5 evaluation-driven missing-slot;默认 off */
|
|
87
|
+
evalMissingSlot: "off" | "warning";
|
|
88
|
+
};
|
|
89
|
+
/** A1 产品默认:exports — 普通带导出的 .js 进 IDE;directives/all 需显式 */
|
|
90
|
+
declare const DEFAULT_ANALYSIS_MODE: AnalysisMode;
|
|
91
|
+
/**
|
|
92
|
+
* 归一化 `nudo.analysis`。默认 mode=exports(A1:无指令但有 export/侧车的文件
|
|
93
|
+
* 进 IDE 分析;`all` / `directives` 需显式配置)。
|
|
94
|
+
* diagnostics:directives→errors,exports/all→default。
|
|
95
|
+
* include 空 = 不按路径过滤(isNudoTargetPath 已管扩展名)。
|
|
96
|
+
*/
|
|
97
|
+
declare function analysisConfig(config: NudoConfig | null | undefined): AnalysisConfig;
|
|
98
|
+
/** 磁盘缓存根(B3):config.cache / NUDO_CACHE_DIR / 默认关 */
|
|
99
|
+
declare function diskCacheRoot(config: NudoConfig | null | undefined, projectDir: string | undefined): string | undefined;
|
|
100
|
+
/**
|
|
101
|
+
* 归一化 `nudo.interface` 配置段。
|
|
102
|
+
* - autoBind 默认 true
|
|
103
|
+
* - emit:string | string[] → string[](空 = 不限制路径)
|
|
104
|
+
*/
|
|
105
|
+
declare function interfaceConfig(config: NudoConfig | null | undefined): InterfaceConfig;
|
|
106
|
+
/**
|
|
107
|
+
* 极简 glob(`**` / `*` / `?`):相对 projectDir 匹配**源文件**绝对路径
|
|
108
|
+
* (不是侧车路径;侧车随源文件同目录写出)。无白名单 → true。
|
|
109
|
+
* 路径分隔符归一为 `/`。
|
|
110
|
+
*/
|
|
111
|
+
declare function matchesEmitAllowlist(absPath: string, projectDir: string | undefined, patterns: string[]): boolean;
|
|
112
|
+
declare function findProjectConfig(startDir: string): {
|
|
113
|
+
config: NudoConfig;
|
|
114
|
+
projectDir: string;
|
|
115
|
+
} | null;
|
|
116
|
+
|
|
117
|
+
export { type AnalysisConfig as A, type CallRecord as C, type DiagnosticsLevel as D, type InterfaceConfig as I, type LoadedEnv as L, type NudoConfig as N, type AnalysisMode as a, DEFAULT_ANALYSIS_MODE as b, analysisConfig as c, clearPathEnvCaches as d, diskCacheRoot as e, findProjectConfig as f, loadEnvsAsync as g, interfaceConfig as i, loadEnvs as l, matchesEmitAllowlist as m, preloadPathEnvs as p };
|