@nudojs/service 0.3.0 → 0.3.2

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 (72) hide show
  1. package/dist/index.d.ts +18 -3
  2. package/dist/index.js +97 -59
  3. package/package.json +16 -6
  4. package/CHANGELOG.md +0 -68
  5. package/src/__tests__/abs-bindings.test.ts +0 -53
  6. package/src/__tests__/abs-call-records.test.ts +0 -33
  7. package/src/__tests__/abs-calls.test.ts +0 -59
  8. package/src/__tests__/abs-module-cache.test.ts +0 -121
  9. package/src/__tests__/abs-modules-graph.test.ts +0 -100
  10. package/src/__tests__/abs-typevalue-diff.test.ts +0 -111
  11. package/src/__tests__/analyzer.test.ts +0 -880
  12. package/src/__tests__/as-replace.test.ts +0 -165
  13. package/src/__tests__/bpath-arg-provenance.test.ts +0 -104
  14. package/src/__tests__/bpath-as.test.ts +0 -54
  15. package/src/__tests__/bpath-cache-key.test.ts +0 -33
  16. package/src/__tests__/bpath-calls.test.ts +0 -55
  17. package/src/__tests__/bpath-diagnostics.test.ts +0 -78
  18. package/src/__tests__/bpath-env-modules.test.ts +0 -30
  19. package/src/__tests__/bpath-env.test.ts +0 -80
  20. package/src/__tests__/bpath-hosted-skip.test.ts +0 -71
  21. package/src/__tests__/bpath-import-default-ns.test.ts +0 -106
  22. package/src/__tests__/bpath-method-dedupe.test.ts +0 -65
  23. package/src/__tests__/bpath-method-diag.test.ts +0 -45
  24. package/src/__tests__/bpath-module-diags.test.ts +0 -101
  25. package/src/__tests__/bpath-path-env.test.ts +0 -46
  26. package/src/__tests__/bpath-reexport.test.ts +0 -131
  27. package/src/__tests__/bpath-replace.test.ts +0 -52
  28. package/src/__tests__/bpath-require.test.ts +0 -66
  29. package/src/__tests__/bpath-run.test.ts +0 -109
  30. package/src/__tests__/case-emitter.test.ts +0 -636
  31. package/src/__tests__/cjs-require.test.ts +0 -54
  32. package/src/__tests__/diagnostics.test.ts +0 -60
  33. package/src/__tests__/dts-generator.test.ts +0 -382
  34. package/src/__tests__/fn-cache-evict.test.ts +0 -56
  35. package/src/__tests__/fn-sig-impl.test.ts +0 -247
  36. package/src/__tests__/guard-generator.test.ts +0 -29
  37. package/src/__tests__/harvest-auto.test.ts +0 -43
  38. package/src/__tests__/harvest-node.test.ts +0 -24
  39. package/src/__tests__/harvest-package.test.ts +0 -38
  40. package/src/__tests__/harvest-to-abs.test.ts +0 -46
  41. package/src/__tests__/hover-intension.test.ts +0 -39
  42. package/src/__tests__/infer-json.test.ts +0 -35
  43. package/src/__tests__/integration.test.ts +0 -421
  44. package/src/__tests__/m3-intension.test.ts +0 -28
  45. package/src/__tests__/mock-seed-fingerprint.test.ts +0 -64
  46. package/src/__tests__/schema-generator.test.ts +0 -38
  47. package/src/__tests__/session-cache-evict.test.ts +0 -90
  48. package/src/__tests__/static-imports.test.ts +0 -44
  49. package/src/abs-modules-graph.ts +0 -387
  50. package/src/analysis-file-cache.ts +0 -51
  51. package/src/analyzer.ts +0 -3133
  52. package/src/bpath-diagnostics.ts +0 -283
  53. package/src/bpath-run.ts +0 -409
  54. package/src/case-emitter.ts +0 -477
  55. package/src/dts-generator.ts +0 -291
  56. package/src/env-to-abs.ts +0 -57
  57. package/src/fn-analysis-cache.ts +0 -119
  58. package/src/guard-generator.ts +0 -57
  59. package/src/harvest-auto.ts +0 -86
  60. package/src/harvest-node.ts +0 -48
  61. package/src/harvest-package.ts +0 -204
  62. package/src/harvest-to-abs.ts +0 -129
  63. package/src/index.ts +0 -150
  64. package/src/infer-json.ts +0 -137
  65. package/src/load-module.ts +0 -34
  66. package/src/mock-abs.ts +0 -275
  67. package/src/schema-generator.ts +0 -43
  68. package/src/semantic-tokens.ts +0 -216
  69. package/src/session-cache.ts +0 -50
  70. package/src/static-imports.ts +0 -241
  71. package/src/target-path.ts +0 -17
  72. package/tsconfig.json +0 -13
@@ -1,291 +0,0 @@
1
- import type { TypeValue } from "@nudojs/core";
2
- import { T, isTemplate, getTemplateParts, simplifyUnion, typeValueEquals, getFnSig } from "@nudojs/core";
3
- import type { AnalysisResult, FunctionAnalysis } from "./analyzer.ts";
4
-
5
- export function typeValueToTSType(tv: TypeValue): string {
6
- switch (tv.kind) {
7
- case "literal": {
8
- const v = tv.value;
9
- if (v === null) return "null";
10
- if (v === undefined) return "undefined";
11
- if (typeof v === "string") return JSON.stringify(v);
12
- if (typeof v === "boolean") return String(v);
13
- return String(v);
14
- }
15
- case "primitive":
16
- return tv.type;
17
- case "refined": {
18
- if (isTemplate(tv)) {
19
- const parts = getTemplateParts(tv)!;
20
- const inner = parts
21
- .map((p) => (p.kind === "literal" && typeof p.value === "string" ? p.value : `\${${typeValueToTSType(p)}}`))
22
- .join("");
23
- return `\`${inner}\``;
24
- }
25
- return typeValueToTSType(tv.base);
26
- }
27
- case "object": {
28
- const entries = Object.entries(tv.properties);
29
- if (entries.length === 0) return "{}";
30
- const inner = entries
31
- .map(([k, v]) => `${k}: ${typeValueToTSType(v)}`)
32
- .join("; ");
33
- return `{ ${inner} }`;
34
- }
35
- case "array":
36
- return `${wrapComplexType(tv.element)}[]`;
37
- case "tuple": {
38
- const inner = tv.elements.map(typeValueToTSType).join(", ");
39
- return `[${inner}]`;
40
- }
41
- case "function": {
42
- const params = tv.params
43
- .map((p) => `${p}: unknown`)
44
- .join(", ");
45
- const sig = getFnSig(tv);
46
- const retStr = sig?.returnType ? typeValueToTSType(sig.returnType) : "unknown";
47
- return `(${params}) => ${retStr}`;
48
- }
49
- case "promise":
50
- return `Promise<${typeValueToTSType(tv.value)}>`;
51
- case "instance":
52
- return tv.className;
53
- case "union":
54
- return tv.members.map(typeValueToTSType).join(" | ");
55
- case "never":
56
- return "never";
57
- case "unknown":
58
- return "unknown";
59
- }
60
- }
61
-
62
- function wrapComplexType(tv: TypeValue): string {
63
- const ts = typeValueToTSType(tv);
64
- if (tv.kind === "union") return `(${ts})`;
65
- return ts;
66
- }
67
-
68
- function getParamName(fn: FunctionAnalysis, index: number): string {
69
- if (fn.paramNames && fn.paramNames[index]) {
70
- return fn.paramNames[index];
71
- }
72
- return `arg${index}`;
73
- }
74
-
75
- // ---------------------------------------------------------------------------
76
- // Widening —— 主签名语义
77
- //
78
- // case 形状携带字面量精度(10、"Alice"、true、[1,2,3]、{ id: 1 }),但
79
- // .d.ts 是调用面而非 case 台账:按 case 生成重载时 `safeSqrt(arg0: 10): 10`
80
- // 会让合法调用 `safeSqrt(5)` 匹配不到任何重载,直接编译错误(tsc 5.9.3
81
- // 实测 TS2769,见 __tests__/dts-generator.test.ts 的 safeSqrt 场景)。
82
- // 主签名因此取 Combined 语义:每个参数位置取各 case 对应参数类型的联合
83
- // 并 widen,返回类型对 combined 同样 widen(顶层标量字面量 → 基类型
84
- // number/string/boolean/bigint;null/undefined 字面量本身是基类型,不变)。
85
- //
86
- // 参数位是逆变位,拦截面最广,widen 递归进结构(tsc --strict 实测):
87
- // - 同构元组 [1,2,3,4,5] → number[]:定长 widened 元组
88
- // [number,number,number,number,number] 仍会拒收变长合法实参(TS2345
89
- // "Source has 2 element(s) but target requires 3"),而字面量长度只是
90
- // 单次调用观察,不该构成约束;
91
- // - 异构元组 [1,"two"] → [number, string]:位置语义真实,且不拦截
92
- // 合法的按位调用(实测 f([1,"x"]) 通过);
93
- // - 对象属性值 { id: 1 } → { id: number }、数组元素、Promise 载荷、
94
- // 实例属性同样递归 widen(全部处于逆变位,字面量精度同样拦截赋值);
95
- // - refined(模板串/数值收窄)剥到 base:参数位收窄拦截一切不匹配
96
- // 实参,精确形状由 JSDoc Case: 行保留。
97
- //
98
- // 返回值位是协变位,收窄只会让调用方拿到更精确的类型、不会拦截调用,
99
- // 维持现状:仅顶层(含 union 成员)标量字面量 widen,嵌套精度保留
100
- // (Promise<42>、返回元组字面量等)。
101
- //
102
- // 就地实现而不复用 core 的 widenLiteral:①本路径不依赖 core 侧并行改动的
103
- // 落地时序;②core 版本把 bigint 字面量映射到 unknown,不符合此处语义。
104
- // ---------------------------------------------------------------------------
105
- function widenLiteralToBase(tv: TypeValue): TypeValue {
106
- if (tv.kind !== "literal") return tv;
107
- const v = tv.value;
108
- if (typeof v === "number") return T.number;
109
- if (typeof v === "string") return T.string;
110
- if (typeof v === "boolean") return T.boolean;
111
- if (typeof v === "bigint") return T.bigint;
112
- return tv;
113
- }
114
-
115
- /** 返回值位(协变):仅顶层标量字面量 widen,嵌套精度保留 */
116
- function widenTopLevel(tv: TypeValue): TypeValue {
117
- if (tv.kind !== "union") return widenLiteralToBase(tv);
118
- return simplifyUnion(tv.members.map(widenLiteralToBase));
119
- }
120
-
121
- function widenProperties(properties: Record<string, TypeValue>): Record<string, TypeValue> {
122
- const out: Record<string, TypeValue> = {};
123
- for (const [k, v] of Object.entries(properties)) out[k] = widenParamType(v);
124
- return out;
125
- }
126
-
127
- /** 参数位(逆变)递归 widen:结构内的字面量精度一律放宽到基类型 */
128
- function widenParamType(tv: TypeValue): TypeValue {
129
- switch (tv.kind) {
130
- case "literal":
131
- return widenLiteralToBase(tv);
132
- case "union":
133
- return simplifyUnion(tv.members.map(widenParamType));
134
- case "tuple": {
135
- const widened = tv.elements.map(widenParamType);
136
- const first = widened[0];
137
- if (first && widened.every((el) => typeValueEquals(el, first))) {
138
- return T.array(first);
139
- }
140
- return T.tuple(widened);
141
- }
142
- case "array":
143
- return T.array(widenParamType(tv.element));
144
- case "object":
145
- return T.object(widenProperties(tv.properties));
146
- case "promise":
147
- return T.promise(widenParamType(tv.value));
148
- case "instance":
149
- return T.instanceOf(tv.className, widenProperties(tv.properties));
150
- case "refined":
151
- return widenParamType(tv.base);
152
- default:
153
- return tv;
154
- }
155
- }
156
-
157
- /**
158
- * 各 case 在同一参数位的类型 → 拍平、递归 widen、去重后的单一 TypeValue。
159
- * 去重以 TS 渲染串为键:TypeValue 的对象/元组相等性是引用比较,两个同形
160
- * 的 widened 对象(如各 case 的 { id: number })不去重会渲染成重复成员。
161
- */
162
- function widenedUnion(members: TypeValue[]): TypeValue {
163
- const flat: TypeValue[] = [];
164
- const collect = (tv: TypeValue): void => {
165
- if (tv.kind === "union") {
166
- tv.members.forEach(collect);
167
- return;
168
- }
169
- flat.push(tv);
170
- };
171
- members.forEach(collect);
172
- const widened = flat.map(widenParamType);
173
- const seen = new Set<string>();
174
- const deduped = widened.filter((tv) => {
175
- const key = typeValueToTSType(tv);
176
- if (seen.has(key)) return false;
177
- seen.add(key);
178
- return true;
179
- });
180
- return simplifyUnion(deduped);
181
- }
182
-
183
- type MainSignature = {
184
- /** 渲染后的参数声明(含 `?` / `...rest`),如 `["x: number", "y?: string"]` */
185
- params: string[];
186
- /** 去重后的参数名(JSDoc @param 与渲染共用,保证一致) */
187
- paramNames: string[];
188
- /** 各参数位 widen 后的 TS 类型串(JSDoc 与精确度比对用) */
189
- paramTypes: string[];
190
- /** widen 后的返回类型串 */
191
- returnType: string;
192
- };
193
-
194
- function computeMainSignature(fn: FunctionAnalysis): MainSignature {
195
- const arity = Math.max(...fn.cases.map((c) => c.args.length));
196
- const minArity = Math.min(...fn.cases.map((c) => c.args.length));
197
- const params: string[] = [];
198
- const paramNames: string[] = [];
199
- const paramTypes: string[] = [];
200
- const usedNames = new Set<string>();
201
- for (let i = 0; i < arity; i++) {
202
- const members = fn.cases
203
- .filter((c) => i < c.args.length)
204
- .map((c) => c.args[i]);
205
- const typeStr = typeValueToTSType(widenedUnion(members));
206
- let name = getParamName(fn, i);
207
- if (usedNames.has(name)) {
208
- // 解构/模式参数在 AST 提取时都叫 "_";单一签名里重名会让 .d.ts
209
- // 非法(tsc TS2300 Duplicate identifier),序号去重
210
- let n = 2;
211
- while (usedNames.has(`${name}${n}`)) n++;
212
- name = `${name}${n}`;
213
- }
214
- usedNames.add(name);
215
- // 各 case 元数不一致时,短 case 不传的尾部参数标可选,长调用短调用都放行
216
- const optional = i >= minArity && !name.startsWith("...");
217
- params.push(optional ? `${name}?: ${typeStr}` : `${name}: ${typeStr}`);
218
- paramNames.push(name);
219
- paramTypes.push(typeStr);
220
- }
221
- const combined = fn.combined ?? simplifyUnion(fn.cases.map((c) => c.result));
222
- const returnType = typeValueToTSType(widenTopLevel(combined));
223
- return { params, paramNames, paramTypes, returnType };
224
- }
225
-
226
- function generateJSDoc(fn: FunctionAnalysis, sig: MainSignature): string {
227
- if (fn.cases.length === 0) return "";
228
- const lines: string[] = ["/**"];
229
- // 精确 case 形状记录在主签名 JSDoc 而非生成精确重载。决策依据按实测修正
230
- // (tsc 5.9.3,最小 .d.ts + 调用文件 + tsc --noEmit):字面量参数重载其实
231
- // 可达——「宽主签名在前则后置精确重载永不命中」不成立,新鲜与非新鲜
232
- // (as const 传入)字面量实参都会优先命中字面量参数重载,与声明顺序无关。
233
- // 即便如此仍只生成单一主签名:① --callsites 场景单个函数可合成几十个
234
- // case,逐 case 重载会让声明面爆炸;② throwing case 的 `: never` 重载对
235
- // 调用方是陷阱(对 never 取属性/运算直接报错);③ 字面量精度由下面的
236
- // Case: 行完整保留。与主签名同形的 case(无信息损失)不罗列。
237
- for (const c of fn.cases) {
238
- const preciseDiffers =
239
- c.args.length !== sig.paramTypes.length ||
240
- c.args.some((a, i) => typeValueToTSType(a) !== sig.paramTypes[i]) ||
241
- typeValueToTSType(c.result) !== sig.returnType;
242
- if (!preciseDiffers) continue;
243
- const argsStr = c.args.map(typeValueToTSType).join(", ");
244
- lines.push(` * Case: ${c.name} (${argsStr}) => ${typeValueToTSType(c.result)}`);
245
- }
246
- for (let i = 0; i < sig.paramTypes.length; i++) {
247
- lines.push(` * @param ${sig.paramNames[i]} - ${sig.paramTypes[i]}`);
248
- }
249
- lines.push(` * @returns ${sig.returnType}`);
250
- lines.push(" */");
251
- return lines.join("\n");
252
- }
253
-
254
- /**
255
- * 为单个函数生成 .d.ts 声明行(JSDoc + 单一 widen 主签名)。
256
- * service 的 generateDts 与 CLI `--dts`(infer/watch)共用本函数,
257
- * 两条路径输出保持一致。
258
- */
259
- export function generateFunctionDtsLines(fn: FunctionAnalysis): string[] {
260
- // CJS-style binding/assignment functions have no declaration-stable
261
- // export name; they stay in infer/JSON output only.
262
- if (fn.noDeclaration) return [];
263
-
264
- if (fn.cases.length === 0) {
265
- // skipped / entryOnly / 无 case 函数:只有声明或 combined 返回类型已知,
266
- // 保持历史行为——rest-args 形式,combined(含 Promise<T>)原样输出。
267
- if (fn.combined) {
268
- return [
269
- `export declare function ${fn.name}(...args: unknown[]): ${typeValueToTSType(fn.combined)};`,
270
- ];
271
- }
272
- return [];
273
- }
274
-
275
- const sig = computeMainSignature(fn);
276
- const jsdoc = generateJSDoc(fn, sig);
277
- const lines: string[] = [];
278
- if (jsdoc) lines.push(jsdoc);
279
- lines.push(`export declare function ${fn.name}(${sig.params.join(", ")}): ${sig.returnType};`);
280
- return lines;
281
- }
282
-
283
- export function generateDts(result: AnalysisResult): string {
284
- const lines: string[] = [];
285
-
286
- for (const fn of result.functions) {
287
- lines.push(...generateFunctionDtsLines(fn));
288
- }
289
-
290
- return lines.join("\n") + "\n";
291
- }
package/src/env-to-abs.ts DELETED
@@ -1,57 +0,0 @@
1
- /**
2
- * env TypeValue → Abs:保留 fnSig impl,使 Promise.resolve / URL 等
3
- * 在 B 路径也能产出精确字面量。
4
- */
5
-
6
- import {
7
- type TypeValue,
8
- type Abs,
9
- typeValueToAbs,
10
- absToTypeValue,
11
- getFnSig,
12
- absFunction,
13
- objOf,
14
- } from "@nudojs/core";
15
-
16
- export function envValueToAbs(tv: TypeValue): Abs {
17
- if (!tv) return typeValueToAbs({ kind: "unknown" });
18
-
19
- if (tv.kind === "function") {
20
- const sig = getFnSig(tv);
21
- if (sig?.impl) {
22
- const params =
23
- tv.kind === "function" && tv.params?.length
24
- ? tv.params
25
- : sig.paramTypes.map((_, i) => `_arg${i}`);
26
- const dummyBody = {
27
- type: "BlockStatement",
28
- body: [],
29
- directives: [],
30
- } as never;
31
- const impl = sig.impl;
32
- return absFunction(params, {
33
- body: dummyBody,
34
- apply: (args: Abs[]): Abs => {
35
- const tvArgs = args.map((a) => absToTypeValue(a));
36
- try {
37
- const r = impl(tvArgs);
38
- if (!r) return typeValueToAbs(sig.returnType);
39
- return envValueToAbs(r);
40
- } catch {
41
- return typeValueToAbs(sig.returnType);
42
- }
43
- },
44
- });
45
- }
46
- }
47
-
48
- if (tv.kind === "object") {
49
- const slots: Record<string, { value: Abs }> = {};
50
- for (const [k, v] of Object.entries(tv.properties)) {
51
- slots[k] = { value: envValueToAbs(v) };
52
- }
53
- return objOf(slots);
54
- }
55
-
56
- return typeValueToAbs(tv);
57
- }
@@ -1,119 +0,0 @@
1
- /**
2
- * Per-function FunctionAnalysis cache (body-edit: recompute only dirty fns).
3
- * Cleared together with B-path / whole-file analysis caches.
4
- */
5
- import type { TypeValue } from "@nudojs/core";
6
-
7
- // Structural types — avoid importing analyzer (cycle).
8
- export type CachedSourceLocation = {
9
- start: { line: number; column: number };
10
- end: { line: number; column: number };
11
- };
12
-
13
- export type CachedDiagnostic = {
14
- range: CachedSourceLocation;
15
- severity: "error" | "warning" | "info";
16
- message: string;
17
- tags?: string[];
18
- code?: string;
19
- suggestions?: string[];
20
- data?: unknown;
21
- origin?: { line: number; column: number };
22
- };
23
-
24
- export type CachedCaseHint = {
25
- line: number;
26
- label: string;
27
- ok: boolean;
28
- };
29
-
30
- export type CachedCallRecord = {
31
- // opaque; cloned shallowly. Position fields that need lineDelta shift
32
- // (currently only callLoc) are listed on CallRecord in evaluator.ts —
33
- // keep shiftCallRecordLines in sync when adding any.
34
- [k: string]: unknown;
35
- };
36
-
37
- export type CachedFnAnalysis = {
38
- analysis: {
39
- name: string;
40
- loc: CachedSourceLocation;
41
- paramNames: string[];
42
- cases: Array<{
43
- name: string;
44
- args: TypeValue[];
45
- result: TypeValue;
46
- throws: TypeValue;
47
- throwLoc?: CachedSourceLocation;
48
- source?: string;
49
- expected?: TypeValue;
50
- aggregatedFrom?: number;
51
- intension?: Record<string, unknown>;
52
- }>;
53
- combined?: TypeValue;
54
- entryOnly?: boolean;
55
- skipped?: boolean;
56
- noDeclaration?: boolean;
57
- fromModule?: string;
58
- };
59
- diagnostics: CachedDiagnostic[];
60
- caseHints: CachedCaseHint[];
61
- callRecords: CachedCallRecord[];
62
- };
63
-
64
- const fnAnalysisCache = new Map<string, CachedFnAnalysis>();
65
- const MAX_FN_ANALYSIS_CACHE = 1024;
66
-
67
- export function clearFnAnalysisCache(): void {
68
- fnAnalysisCache.clear();
69
- }
70
-
71
- export function fnAnalysisCacheGet(key: string): CachedFnAnalysis | undefined {
72
- const hit = fnAnalysisCache.get(key);
73
- if (hit !== undefined) {
74
- fnAnalysisCache.delete(key);
75
- fnAnalysisCache.set(key, hit);
76
- }
77
- return hit;
78
- }
79
-
80
- export function fnAnalysisCacheSet(key: string, value: CachedFnAnalysis): void {
81
- // Only evict when inserting a new key; overwrite of an existing key keeps LRU size
82
- if (fnAnalysisCache.size >= MAX_FN_ANALYSIS_CACHE && !fnAnalysisCache.has(key)) {
83
- const oldest = fnAnalysisCache.keys().next().value;
84
- if (oldest !== undefined) fnAnalysisCache.delete(oldest);
85
- }
86
- fnAnalysisCache.set(key, value);
87
- }
88
-
89
- /**
90
- * Dependency content changed: drop every per-fn entry for these entry files.
91
- * Keys are `filePath\0...`, so a prefix scan is sound and cheap at LRU size.
92
- */
93
- export function evictFnAnalysisCacheForFiles(files: string[]): number {
94
- if (files.length === 0 || fnAnalysisCache.size === 0) return 0;
95
- const prefixes = files.map((f) => `${f}\0`);
96
- let n = 0;
97
- for (const key of [...fnAnalysisCache.keys()]) {
98
- if (prefixes.some((p) => key.startsWith(p))) {
99
- fnAnalysisCache.delete(key);
100
- n++;
101
- }
102
- }
103
- return n;
104
- }
105
-
106
- export function caseDirectiveKey(
107
- directives: Array<{ kind: string; name?: string; args?: TypeValue[] }>,
108
- typeValueToString: (t: TypeValue) => string,
109
- ): string {
110
- return directives
111
- .map((d) => {
112
- if (d.kind === "case") {
113
- const args = (d.args ?? []).map((a) => typeValueToString(a)).join(",");
114
- return `c:${d.name ?? ""}:${args}`;
115
- }
116
- return d.kind;
117
- })
118
- .join("|");
119
- }
@@ -1,57 +0,0 @@
1
- import type { TypeValue, Abs } from "@nudojs/core";
2
- import { denoteGuard } from "@nudojs/core";
3
-
4
- export function generateGuardFunction(name: string, tv: TypeValue): string {
5
- const body = generateGuardBody(tv, "data");
6
- return `export function ${name}(data) {\n return ${body};\n}`;
7
- }
8
-
9
- /** Abs 指称守卫(设计 §2.7):比 TypeValue 投影多保留 pred */
10
- export function generateGuardFunctionFromAbs(name: string, abs: Abs): string {
11
- const body = denoteGuard(abs, "data");
12
- return `export function ${name}(data) {\n return ${body};\n}`;
13
- }
14
-
15
- function generateGuardBody(tv: TypeValue, varName: string): string {
16
- switch (tv.kind) {
17
- case "literal": {
18
- if (tv.value === null) return `${varName} === null`;
19
- if (tv.value === undefined) return `${varName} === undefined`;
20
- return `${varName} === ${JSON.stringify(tv.value)}`;
21
- }
22
- case "primitive":
23
- return `typeof ${varName} === '${tv.type}'`;
24
- case "refined":
25
- return generateGuardBody(tv.base, varName);
26
- case "object": {
27
- const checks = [`typeof ${varName} === 'object'`, `${varName} !== null`];
28
- for (const [key, val] of Object.entries(tv.properties)) {
29
- checks.push(generateGuardBody(val, `${varName}.${key}`));
30
- }
31
- return checks.join(" && ");
32
- }
33
- case "array":
34
- return `Array.isArray(${varName}) && ${varName}.every(item => ${generateGuardBody(tv.element, "item")})`;
35
- case "tuple": {
36
- const checks = [`Array.isArray(${varName})`, `${varName}.length === ${tv.elements.length}`];
37
- tv.elements.forEach((el, i) => {
38
- checks.push(generateGuardBody(el, `${varName}[${i}]`));
39
- });
40
- return checks.join(" && ");
41
- }
42
- case "union": {
43
- const members = tv.members.map((m) => generateGuardBody(m, varName));
44
- return `(${members.join(" || ")})`;
45
- }
46
- case "never":
47
- return "false";
48
- case "unknown":
49
- return "true";
50
- case "function":
51
- return `typeof ${varName} === 'function'`;
52
- case "promise":
53
- return `${varName} instanceof Promise`;
54
- case "instance":
55
- return `${varName} instanceof ${tv.className}`;
56
- }
57
- }
@@ -1,86 +0,0 @@
1
- /**
2
- * harvest 自动化:源码裸 import → @types/package → env modules。
3
- * 手动 `nudo harvest` 仍保留;这里是分析路径上的按需注入。
4
- */
5
-
6
- import { parse } from "@nudojs/parser";
7
- import type { TypeValue } from "@nudojs/core";
8
- import { collectDependencySpecs } from "./static-imports.ts";
9
- import { harvestPackage, type PackageHarvest } from "./harvest-package.ts";
10
-
11
- /** 裸说明符 → 包名(含 scope);相对/绝对/node: 内建返回 undefined */
12
- export function barePackageName(spec: string): string | undefined {
13
- if (!spec) return undefined;
14
- if (spec.startsWith(".") || spec.startsWith("/") || spec.startsWith("node:")) return undefined;
15
- const parts = spec.split("/");
16
- if (spec.startsWith("@")) {
17
- return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : undefined;
18
- }
19
- return parts[0];
20
- }
21
-
22
- export function collectBarePackages(source: string): string[] {
23
- try {
24
- const ast = parse(source);
25
- const names = new Set<string>();
26
- for (const spec of collectDependencySpecs(ast)) {
27
- const pkg = barePackageName(spec);
28
- if (pkg) names.add(pkg);
29
- }
30
- return [...names];
31
- } catch {
32
- return [];
33
- }
34
- }
35
-
36
- /** 进程内 harvest 缓存:同包只 walk 一次 dts */
37
- const harvestCache = new Map<string, PackageHarvest | null>();
38
-
39
- export function harvestPackageCached(pkg: string, fromDir: string): PackageHarvest | null {
40
- const key = `${fromDir}::${pkg}`;
41
- if (harvestCache.has(key)) return harvestCache.get(key)!;
42
- let result: PackageHarvest | null = null;
43
- try {
44
- const h = harvestPackage(pkg, fromDir);
45
- if (!("error" in h)) result = h;
46
- } catch {
47
- result = null;
48
- }
49
- harvestCache.set(key, result);
50
- return result;
51
- }
52
-
53
- export function clearHarvestCache(): void {
54
- harvestCache.clear();
55
- }
56
-
57
- /**
58
- * 自动 harvest 源码中全部裸包(有 @types 或包内 dts 时)。
59
- * 返回可注入 setEnvModules 的 modules 表;无包可 harvest 时为空对象。
60
- */
61
- export function autoHarvestModules(
62
- source: string,
63
- fromDir: string,
64
- ): Record<string, Record<string, TypeValue>> {
65
- const packages = collectBarePackages(source);
66
- if (packages.length === 0) return {};
67
- const modules: Record<string, Record<string, TypeValue>> = {};
68
- for (const pkg of packages) {
69
- const h = harvestPackageCached(pkg, fromDir);
70
- if (!h) continue;
71
- for (const [mod, exports] of Object.entries(h.env.modules)) {
72
- // 模块键可能是相对路径形态;同时登记裸包名与原始键
73
- modules[mod] = exports;
74
- if (!modules[pkg]) modules[pkg] = exports;
75
- // `path` / `node:path` 等别名:basename 匹配
76
- const base = mod.replace(/^@types\//, "").replace(/\\/g, "/");
77
- if (base && !modules[base]) modules[base] = exports;
78
- }
79
- for (const [k, v] of Object.entries(h.env.globals)) {
80
- // 全局 API 也挂一份到包名模块,供 default/namespace 消费
81
- if (!modules[pkg]) modules[pkg] = {};
82
- if (modules[pkg]![k] === undefined) modules[pkg]![k] = v;
83
- }
84
- }
85
- return modules;
86
- }
@@ -1,48 +0,0 @@
1
- /**
2
- * 预置 Node API env:从 @types/node 的 .d.ts harvest。
3
- * 与 packages/env 的手写 env 并行;此路径自动、可刷新。
4
- */
5
-
6
- import { existsSync } from "node:fs";
7
- import { join } from "node:path";
8
- import { harvestDts, type HarvestedEnv } from "@nudojs/harvester";
9
- import { collectDtsFiles, resolvePackageRoot } from "./harvest-package.ts";
10
-
11
- export type NodeEnvResult =
12
- | { ok: true; env: HarvestedEnv; root: string; files: number }
13
- | { ok: false; error: string };
14
-
15
- /** harvest @types/node(限制文件数 + 时间预算,避免拖垮启动) */
16
- export function harvestNodeTypes(
17
- fromDir?: string,
18
- maxFiles = 12,
19
- maxMs = 2500,
20
- ): NodeEnvResult {
21
- // @types/node 在 node_modules/@types/node
22
- const root =
23
- resolvePackageRoot("@types/node", fromDir) ??
24
- resolvePackageRoot("node", fromDir);
25
- if (!root || !existsSync(root)) {
26
- return { ok: false, error: "@types/node not found" };
27
- }
28
- // 优先 index.d.ts 等入口
29
- const dts = collectDtsFiles(root, maxFiles);
30
- if (dts.length === 0) {
31
- return { ok: false, error: `no .d.ts under ${root}` };
32
- }
33
- const env = harvestDts(dts, { maxMs });
34
- return { ok: true, env, root, files: env.stats.files };
35
- }
36
-
37
- /** 把 harvest 结果压成「模块名 → 导出名列表」摘要,便于日志/测试 */
38
- export function summarizeNodeEnv(env: HarvestedEnv): {
39
- modules: string[];
40
- globals: string[];
41
- symbolCount: number;
42
- } {
43
- return {
44
- modules: Object.keys(env.modules).sort(),
45
- globals: Object.keys(env.globals).sort().slice(0, 50),
46
- symbolCount: env.stats.symbols,
47
- };
48
- }