@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.
- package/dist/index.d.ts +18 -3
- package/dist/index.js +97 -59
- package/package.json +16 -6
- package/CHANGELOG.md +0 -68
- package/src/__tests__/abs-bindings.test.ts +0 -53
- package/src/__tests__/abs-call-records.test.ts +0 -33
- package/src/__tests__/abs-calls.test.ts +0 -59
- package/src/__tests__/abs-module-cache.test.ts +0 -121
- package/src/__tests__/abs-modules-graph.test.ts +0 -100
- package/src/__tests__/abs-typevalue-diff.test.ts +0 -111
- package/src/__tests__/analyzer.test.ts +0 -880
- package/src/__tests__/as-replace.test.ts +0 -165
- package/src/__tests__/bpath-arg-provenance.test.ts +0 -104
- package/src/__tests__/bpath-as.test.ts +0 -54
- package/src/__tests__/bpath-cache-key.test.ts +0 -33
- package/src/__tests__/bpath-calls.test.ts +0 -55
- package/src/__tests__/bpath-diagnostics.test.ts +0 -78
- package/src/__tests__/bpath-env-modules.test.ts +0 -30
- package/src/__tests__/bpath-env.test.ts +0 -80
- package/src/__tests__/bpath-hosted-skip.test.ts +0 -71
- package/src/__tests__/bpath-import-default-ns.test.ts +0 -106
- package/src/__tests__/bpath-method-dedupe.test.ts +0 -65
- package/src/__tests__/bpath-method-diag.test.ts +0 -45
- package/src/__tests__/bpath-module-diags.test.ts +0 -101
- package/src/__tests__/bpath-path-env.test.ts +0 -46
- package/src/__tests__/bpath-reexport.test.ts +0 -131
- package/src/__tests__/bpath-replace.test.ts +0 -52
- package/src/__tests__/bpath-require.test.ts +0 -66
- package/src/__tests__/bpath-run.test.ts +0 -109
- package/src/__tests__/case-emitter.test.ts +0 -636
- package/src/__tests__/cjs-require.test.ts +0 -54
- package/src/__tests__/diagnostics.test.ts +0 -60
- package/src/__tests__/dts-generator.test.ts +0 -382
- package/src/__tests__/fn-cache-evict.test.ts +0 -56
- package/src/__tests__/fn-sig-impl.test.ts +0 -247
- package/src/__tests__/guard-generator.test.ts +0 -29
- package/src/__tests__/harvest-auto.test.ts +0 -43
- package/src/__tests__/harvest-node.test.ts +0 -24
- package/src/__tests__/harvest-package.test.ts +0 -38
- package/src/__tests__/harvest-to-abs.test.ts +0 -46
- package/src/__tests__/hover-intension.test.ts +0 -39
- package/src/__tests__/infer-json.test.ts +0 -35
- package/src/__tests__/integration.test.ts +0 -421
- package/src/__tests__/m3-intension.test.ts +0 -28
- package/src/__tests__/mock-seed-fingerprint.test.ts +0 -64
- package/src/__tests__/schema-generator.test.ts +0 -38
- package/src/__tests__/session-cache-evict.test.ts +0 -90
- package/src/__tests__/static-imports.test.ts +0 -44
- package/src/abs-modules-graph.ts +0 -387
- package/src/analysis-file-cache.ts +0 -51
- package/src/analyzer.ts +0 -3133
- package/src/bpath-diagnostics.ts +0 -283
- package/src/bpath-run.ts +0 -409
- package/src/case-emitter.ts +0 -477
- package/src/dts-generator.ts +0 -291
- package/src/env-to-abs.ts +0 -57
- package/src/fn-analysis-cache.ts +0 -119
- package/src/guard-generator.ts +0 -57
- package/src/harvest-auto.ts +0 -86
- package/src/harvest-node.ts +0 -48
- package/src/harvest-package.ts +0 -204
- package/src/harvest-to-abs.ts +0 -129
- package/src/index.ts +0 -150
- package/src/infer-json.ts +0 -137
- package/src/load-module.ts +0 -34
- package/src/mock-abs.ts +0 -275
- package/src/schema-generator.ts +0 -43
- package/src/semantic-tokens.ts +0 -216
- package/src/session-cache.ts +0 -50
- package/src/static-imports.ts +0 -241
- package/src/target-path.ts +0 -17
- package/tsconfig.json +0 -13
package/dist/index.d.ts
CHANGED
|
@@ -206,6 +206,11 @@ type HoverInfo = {
|
|
|
206
206
|
/**
|
|
207
207
|
* LSP hover:优先无损 Abs(类型即计算本体),TypeValue 仅作外延对照。
|
|
208
208
|
* 节点表也是 Abs(collectAbsNodeTypes),不经 bridge。
|
|
209
|
+
*
|
|
210
|
+
* 函数名/调用 callee 位置(design-hof-relations §7):
|
|
211
|
+
* intension 一律走 generalize/formatPoly(HOF fnRels 在这里);
|
|
212
|
+
* typeText 仍落 B-path / TypeValue(调用点显示结果类型,不是函数签名)。
|
|
213
|
+
* 禁止用 B-path 的 arity-only fn Abs 冒充权威关系源。
|
|
209
214
|
*/
|
|
210
215
|
declare function getHoverAtPosition(filePath: string, source: string, line: number, column: number, activeCases?: Map<string, number>): HoverInfo | null;
|
|
211
216
|
declare function getCompletionsAtPosition(filePath: string, source: string, line: number, column: number): CompletionItem[];
|
|
@@ -491,7 +496,16 @@ declare function tryRunBPath(source: string, filePath: string, opts?: {
|
|
|
491
496
|
/** @nudo:mock → Abs,注入为全局绑定(防止顶层调用真 fetch 等) */
|
|
492
497
|
mocks?: Record<string, Abs>;
|
|
493
498
|
}): BPathRunResult | undefined;
|
|
494
|
-
/**
|
|
499
|
+
/**
|
|
500
|
+
* B 路径求值具名导出(结果 + throws);opts.collectCalls 时附带调用点记录。
|
|
501
|
+
*
|
|
502
|
+
* `calls` 只含**本次具名调用期间**(callTranspiledExportFull 内)发生的
|
|
503
|
+
* 调用点——不含模块级顶层调用(run.calls)。模块级记录由主分析流程
|
|
504
|
+
* 另行收集(analyzer 的 bTopCallRecords / absCallRecords);在此重复返回
|
|
505
|
+
* 会把 B 路径结果(常为 unknown)当第二组记录推给 analyzer,dedupe 因
|
|
506
|
+
* 结果形态不同而保留,污染被调函数的 case 列表(多余 call@L 重复 +
|
|
507
|
+
* call@symbolic 聚合 case,见 analyzer directive-case 分支)。
|
|
508
|
+
*/
|
|
495
509
|
declare function tryBPathCallFull(source: string, filePath: string, fnName: string, args: Abs[], opts?: {
|
|
496
510
|
collectCalls?: boolean;
|
|
497
511
|
collectMemberDiags?: boolean;
|
|
@@ -531,8 +545,9 @@ declare function clearAnalysisSessionCaches(): void;
|
|
|
531
545
|
declare function resetAllAnalysisCaches(): void;
|
|
532
546
|
|
|
533
547
|
/**
|
|
534
|
-
* env TypeValue → Abs
|
|
535
|
-
*
|
|
548
|
+
* env TypeValue → Abs:
|
|
549
|
+
* - fnSig 带 impl(Promise.resolve / URL / path.join…)→ apply 路径,保留精确字面量
|
|
550
|
+
* - fnSig 仅有声明(readFileSync…)→ relationFn,调用产出声明返回类型而非 unknown
|
|
536
551
|
*/
|
|
537
552
|
|
|
538
553
|
declare function envValueToAbs(tv: TypeValue): Abs;
|
package/dist/index.js
CHANGED
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
stableAnalyzeKeySource as stableAnalyzeKeySource2,
|
|
30
30
|
fnFingerprints
|
|
31
31
|
} from "@nudojs/core";
|
|
32
|
-
import { parse as parse5, extractDirectives, extractFileDirectives, parseTypeValueExpr } from "@nudojs/parser";
|
|
32
|
+
import { parse as parse5, extractDirectives, extractFileDirectives, parseTypeValueExpr as parseTypeValueExpr2 } from "@nudojs/parser";
|
|
33
33
|
import {
|
|
34
34
|
evaluate,
|
|
35
35
|
evaluateFunction,
|
|
@@ -62,6 +62,7 @@ import {
|
|
|
62
62
|
} from "@nudojs/cli/evaluator";
|
|
63
63
|
|
|
64
64
|
// src/mock-abs.ts
|
|
65
|
+
import { parseTypeValueExpr } from "@nudojs/parser";
|
|
65
66
|
import {
|
|
66
67
|
absFunction,
|
|
67
68
|
emptyEnv,
|
|
@@ -255,6 +256,12 @@ function mockDirectivesToAbsSeeds(functions) {
|
|
|
255
256
|
seedVars[d.name] = markMockConf(absFromMockHelper(d.nudoMock));
|
|
256
257
|
} else if (d.sinonExpr) {
|
|
257
258
|
seedVars[d.name] = markMockConf(absFromSinon(d.sinonExpr));
|
|
259
|
+
} else if (d.expression) {
|
|
260
|
+
try {
|
|
261
|
+
seedVars[d.name] = typeValueToAbs(parseTypeValueExpr(d.expression));
|
|
262
|
+
} catch {
|
|
263
|
+
seedVars[d.name] = absUnknown;
|
|
264
|
+
}
|
|
258
265
|
}
|
|
259
266
|
}
|
|
260
267
|
}
|
|
@@ -995,32 +1002,40 @@ import {
|
|
|
995
1002
|
absToTypeValue,
|
|
996
1003
|
getFnSig as getFnSig2,
|
|
997
1004
|
absFunction as absFunction4,
|
|
1005
|
+
relationFn,
|
|
998
1006
|
objOf
|
|
999
1007
|
} from "@nudojs/core";
|
|
1000
1008
|
function envValueToAbs(tv) {
|
|
1001
1009
|
if (!tv) return typeValueToAbs3({ kind: "unknown" });
|
|
1002
1010
|
if (tv.kind === "function") {
|
|
1003
1011
|
const sig = getFnSig2(tv);
|
|
1004
|
-
if (sig
|
|
1005
|
-
const
|
|
1006
|
-
const
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1012
|
+
if (sig) {
|
|
1013
|
+
const paramTypes = sig.paramTypes.map(typeValueToAbs3);
|
|
1014
|
+
const returnType = typeValueToAbs3(sig.returnType);
|
|
1015
|
+
if (sig.impl) {
|
|
1016
|
+
const params = tv.params?.length ? tv.params : sig.paramTypes.map((_, i) => `_arg${i}`);
|
|
1017
|
+
const dummyBody = {
|
|
1018
|
+
type: "BlockStatement",
|
|
1019
|
+
body: [],
|
|
1020
|
+
directives: []
|
|
1021
|
+
};
|
|
1022
|
+
const impl = sig.impl;
|
|
1023
|
+
return absFunction4(params, {
|
|
1024
|
+
body: dummyBody,
|
|
1025
|
+
apply: (args) => {
|
|
1026
|
+
const tvArgs = args.map((a) => absToTypeValue(a));
|
|
1027
|
+
try {
|
|
1028
|
+
const r = impl(tvArgs);
|
|
1029
|
+
if (!r) return returnType;
|
|
1030
|
+
return envValueToAbs(r);
|
|
1031
|
+
} catch {
|
|
1032
|
+
return returnType;
|
|
1033
|
+
}
|
|
1022
1034
|
}
|
|
1023
|
-
}
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
return relationFn(paramTypes, returnType, {
|
|
1038
|
+
params: tv.params?.length ? tv.params : void 0
|
|
1024
1039
|
});
|
|
1025
1040
|
}
|
|
1026
1041
|
}
|
|
@@ -1322,7 +1337,7 @@ function tryBPathCallFull(source, filePath, fnName, args, opts = {}) {
|
|
|
1322
1337
|
const run = tryRunBPath(source, filePath, { envNames: opts.envNames, mocks: opts.mocks });
|
|
1323
1338
|
if (!run) return void 0;
|
|
1324
1339
|
if (!(fnName in run.exports)) return void 0;
|
|
1325
|
-
const collected = [
|
|
1340
|
+
const collected = [];
|
|
1326
1341
|
const memberDiags = [];
|
|
1327
1342
|
if (opts.collectCalls) {
|
|
1328
1343
|
setBCallCollector((r) => collected.push(r));
|
|
@@ -1335,7 +1350,7 @@ function tryBPathCallFull(source, filePath, fnName, args, opts = {}) {
|
|
|
1335
1350
|
const all = [...run.memberDiags ?? [], ...memberDiags];
|
|
1336
1351
|
return {
|
|
1337
1352
|
...full,
|
|
1338
|
-
calls: opts.collectCalls
|
|
1353
|
+
calls: opts.collectCalls ? collected : void 0,
|
|
1339
1354
|
memberDiags: all.length ? all : void 0,
|
|
1340
1355
|
moduleIssues: run.moduleIssues,
|
|
1341
1356
|
truncatedFns: run.truncatedFns
|
|
@@ -1740,7 +1755,7 @@ function applyMocks(directives, env, filePath, diagnostics) {
|
|
|
1740
1755
|
]
|
|
1741
1756
|
});
|
|
1742
1757
|
}
|
|
1743
|
-
env.bind(d.name,
|
|
1758
|
+
env.bind(d.name, parseTypeValueExpr2(d.expression));
|
|
1744
1759
|
} else if (d.fromPath) {
|
|
1745
1760
|
const mockPath = resolve4(dirname5(filePath), d.fromPath);
|
|
1746
1761
|
const mockSource = readFileSync4(mockPath, "utf-8");
|
|
@@ -2426,7 +2441,7 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
|
|
|
2426
2441
|
seedVars: seeds.seedVars,
|
|
2427
2442
|
seedFns: seeds.seedFns
|
|
2428
2443
|
});
|
|
2429
|
-
absGraphModules = g.modules;
|
|
2444
|
+
absGraphModules = { ...collectEnvModules(envNames), ...g.modules };
|
|
2430
2445
|
pushBModuleIssues(g.issues);
|
|
2431
2446
|
} catch {
|
|
2432
2447
|
}
|
|
@@ -2477,7 +2492,7 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
|
|
|
2477
2492
|
if (selfContained || canAbsModules) {
|
|
2478
2493
|
setAbsTruncationCollector2((label) => bTruncatedFns.add(label));
|
|
2479
2494
|
try {
|
|
2480
|
-
absCallRecords = collectAbsCallRecords(source, seeds, filePath, absGraphModules);
|
|
2495
|
+
absCallRecords = collectAbsCallRecords(source, seeds, filePath, absGraphModules, envNames);
|
|
2481
2496
|
} finally {
|
|
2482
2497
|
setAbsTruncationCollector2(null);
|
|
2483
2498
|
}
|
|
@@ -3101,11 +3116,19 @@ async function getTypeAtPositionAsync(filePath, source, line, column, activeCase
|
|
|
3101
3116
|
}
|
|
3102
3117
|
return getTypeAtPosition(filePath, source, line, column, activeCases);
|
|
3103
3118
|
}
|
|
3119
|
+
function positionInsideCaseFunction(source, ast, line) {
|
|
3120
|
+
try {
|
|
3121
|
+
const enclosing = findEnclosingFunction(extractDirectives(ast), line);
|
|
3122
|
+
return !!enclosing && enclosing.directives.some((d) => d.kind === "case");
|
|
3123
|
+
} catch {
|
|
3124
|
+
return false;
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3104
3127
|
function getTypeAtPosition(filePath, source, line, column, activeCases) {
|
|
3105
3128
|
const ast = parse5(source);
|
|
3106
3129
|
const fileDirectives = extractFileDirectives(ast);
|
|
3107
3130
|
const envNames = fileDirectives.filter((d) => d.kind === "env").flatMap((d) => d.envs);
|
|
3108
|
-
if (isBPathCapable(source, envNames)) {
|
|
3131
|
+
if (isBPathCapable(source, envNames) && !positionInsideCaseFunction(source, ast, line)) {
|
|
3109
3132
|
try {
|
|
3110
3133
|
const seeds = mockDirectivesToAbsSeeds(extractDirectives(ast));
|
|
3111
3134
|
const { modules } = evalAbsModuleGraph(source, filePath);
|
|
@@ -3172,7 +3195,40 @@ function getHoverAtPosition(filePath, source, line, column, activeCases) {
|
|
|
3172
3195
|
file = void 0;
|
|
3173
3196
|
}
|
|
3174
3197
|
const envNames = collectEnvNames(filePath, source, false);
|
|
3175
|
-
|
|
3198
|
+
const fnName = findFunctionNameAtPosition(source, line, column, file);
|
|
3199
|
+
let gDisplay;
|
|
3200
|
+
let gAbs;
|
|
3201
|
+
let gMulti;
|
|
3202
|
+
if (fnName) {
|
|
3203
|
+
try {
|
|
3204
|
+
const g = generalizeFromAst2(fnName, source, file ? { file } : {});
|
|
3205
|
+
if (g) {
|
|
3206
|
+
gDisplay = g.display;
|
|
3207
|
+
gAbs = formatAbs3(g.symbolic);
|
|
3208
|
+
gMulti = formatAbsMultiline(g.symbolic, fnName);
|
|
3209
|
+
}
|
|
3210
|
+
} catch {
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
const attachIntension = (info2) => {
|
|
3214
|
+
if (!gDisplay) return info2;
|
|
3215
|
+
if (!info2) {
|
|
3216
|
+
return { typeText: gDisplay, intension: gDisplay, abs: gAbs, absMultiline: gMulti };
|
|
3217
|
+
}
|
|
3218
|
+
return {
|
|
3219
|
+
...info2,
|
|
3220
|
+
intension: gDisplay,
|
|
3221
|
+
// 外延侧已有更准 Abs 时保留;否则用 symbolic 兜底
|
|
3222
|
+
abs: info2.abs ?? gAbs,
|
|
3223
|
+
absMultiline: info2.absMultiline ?? gMulti
|
|
3224
|
+
};
|
|
3225
|
+
};
|
|
3226
|
+
const insideCaseFn = positionInsideCaseFunction(
|
|
3227
|
+
source,
|
|
3228
|
+
file ?? parse5(source),
|
|
3229
|
+
line
|
|
3230
|
+
);
|
|
3231
|
+
if (isBPathCapable(source, envNames) && !insideCaseFn) {
|
|
3176
3232
|
try {
|
|
3177
3233
|
const seeds = mockDirectivesToAbsSeeds(extractDirectives(file ?? parse5(source)));
|
|
3178
3234
|
const { modules } = evalAbsModuleGraph(source, filePath);
|
|
@@ -3183,7 +3239,7 @@ function getHoverAtPosition(filePath, source, line, column, activeCases) {
|
|
|
3183
3239
|
});
|
|
3184
3240
|
const absAt = findAbsAtPosition(absNodes, line, column);
|
|
3185
3241
|
const ident2 = findIdentNameAtPosition(source, line, column, file);
|
|
3186
|
-
if (ident2 && !
|
|
3242
|
+
if (ident2 && !fnName) {
|
|
3187
3243
|
const binds = collectAbsBindingsFromGraph(source, filePath, {
|
|
3188
3244
|
seedVars: seeds.seedVars,
|
|
3189
3245
|
seedFns: seeds.seedFns
|
|
@@ -3192,42 +3248,21 @@ function getHoverAtPosition(filePath, source, line, column, activeCases) {
|
|
|
3192
3248
|
if (bound) {
|
|
3193
3249
|
const absLine = formatAbs3(bound);
|
|
3194
3250
|
const absMulti = formatAbsMultiline(bound, ident2);
|
|
3195
|
-
return { typeText: absLine, abs: absLine, absMultiline: absMulti };
|
|
3251
|
+
return attachIntension({ typeText: absLine, abs: absLine, absMultiline: absMulti });
|
|
3196
3252
|
}
|
|
3197
3253
|
}
|
|
3198
3254
|
if (absAt) {
|
|
3199
3255
|
const absLine = formatAbs3(absAt);
|
|
3200
3256
|
const absMulti = formatAbsMultiline(absAt, void 0);
|
|
3201
|
-
return { typeText: absLine, abs: absLine, absMultiline: absMulti };
|
|
3257
|
+
return attachIntension({ typeText: absLine, abs: absLine, absMultiline: absMulti });
|
|
3202
3258
|
}
|
|
3203
3259
|
} catch {
|
|
3204
3260
|
}
|
|
3205
3261
|
}
|
|
3206
3262
|
const tv = getTypeAtPosition(filePath, source, line, column, activeCases);
|
|
3207
3263
|
const info = tv ? { typeText: typeValueToString3(tv) } : null;
|
|
3208
|
-
const fnName = findFunctionNameAtPosition(source, line, column, file);
|
|
3209
|
-
if (fnName) {
|
|
3210
|
-
try {
|
|
3211
|
-
const g = generalizeFromAst2(fnName, source, file ? { file } : {});
|
|
3212
|
-
if (g) {
|
|
3213
|
-
if (info) {
|
|
3214
|
-
info.intension = g.display;
|
|
3215
|
-
info.abs = formatAbs3(g.symbolic);
|
|
3216
|
-
info.absMultiline = formatAbsMultiline(g.symbolic, fnName);
|
|
3217
|
-
} else {
|
|
3218
|
-
return {
|
|
3219
|
-
typeText: g.display,
|
|
3220
|
-
intension: g.display,
|
|
3221
|
-
abs: formatAbs3(g.symbolic),
|
|
3222
|
-
absMultiline: formatAbsMultiline(g.symbolic, fnName)
|
|
3223
|
-
};
|
|
3224
|
-
}
|
|
3225
|
-
}
|
|
3226
|
-
} catch {
|
|
3227
|
-
}
|
|
3228
|
-
}
|
|
3229
3264
|
const ident = findIdentNameAtPosition(source, line, column, file);
|
|
3230
|
-
if (ident && !fnName) {
|
|
3265
|
+
if (ident && !fnName && !insideCaseFn) {
|
|
3231
3266
|
try {
|
|
3232
3267
|
if (isBPathCapable(source, []) || !/\brequire\s*\(/.test(source)) {
|
|
3233
3268
|
const seeds = mockDirectivesToAbsSeeds(extractDirectives(file ?? parse5(source)));
|
|
@@ -3242,16 +3277,16 @@ function getHoverAtPosition(filePath, source, line, column, activeCases) {
|
|
|
3242
3277
|
if (info) {
|
|
3243
3278
|
info.abs = absLine;
|
|
3244
3279
|
info.absMultiline = absMulti;
|
|
3245
|
-
return info;
|
|
3280
|
+
return attachIntension(info);
|
|
3246
3281
|
}
|
|
3247
|
-
return { typeText: absLine, abs: absLine, absMultiline: absMulti };
|
|
3282
|
+
return attachIntension({ typeText: absLine, abs: absLine, absMultiline: absMulti });
|
|
3248
3283
|
}
|
|
3249
3284
|
}
|
|
3250
3285
|
} catch {
|
|
3251
3286
|
}
|
|
3252
3287
|
}
|
|
3253
3288
|
try {
|
|
3254
|
-
if (isBPathCapable(source, []) || !/\brequire\s*\(|\bimport\s*[{'"*]/.test(source)) {
|
|
3289
|
+
if (!insideCaseFn && (isBPathCapable(source, []) || !/\brequire\s*\(|\bimport\s*[{'"*]/.test(source))) {
|
|
3255
3290
|
const seeds = mockDirectivesToAbsSeeds(
|
|
3256
3291
|
extractDirectives(file ?? parse5(source))
|
|
3257
3292
|
);
|
|
@@ -3269,13 +3304,13 @@ function getHoverAtPosition(filePath, source, line, column, activeCases) {
|
|
|
3269
3304
|
info.abs = absLine;
|
|
3270
3305
|
info.absMultiline = absMulti;
|
|
3271
3306
|
} else {
|
|
3272
|
-
return { typeText: absLine, abs: absLine, absMultiline: absMulti };
|
|
3307
|
+
return attachIntension({ typeText: absLine, abs: absLine, absMultiline: absMulti });
|
|
3273
3308
|
}
|
|
3274
3309
|
}
|
|
3275
3310
|
}
|
|
3276
3311
|
} catch {
|
|
3277
3312
|
}
|
|
3278
|
-
return info;
|
|
3313
|
+
return attachIntension(info);
|
|
3279
3314
|
}
|
|
3280
3315
|
function findIdentNameAtPosition(source, line, column, fileAst) {
|
|
3281
3316
|
try {
|
|
@@ -3565,11 +3600,11 @@ function absModulesOk(source, envNames) {
|
|
|
3565
3600
|
void envNames;
|
|
3566
3601
|
return true;
|
|
3567
3602
|
}
|
|
3568
|
-
function collectAbsCallRecords(source, seeds, filePath, precomputedModules) {
|
|
3603
|
+
function collectAbsCallRecords(source, seeds, filePath, precomputedModules, envNames = []) {
|
|
3569
3604
|
const absCalls = [];
|
|
3570
3605
|
let modules = precomputedModules;
|
|
3571
3606
|
let importLocals = /* @__PURE__ */ new Map();
|
|
3572
|
-
if (filePath && absModulesOk(source,
|
|
3607
|
+
if (filePath && absModulesOk(source, envNames)) {
|
|
3573
3608
|
if (!modules) {
|
|
3574
3609
|
try {
|
|
3575
3610
|
const graph = evalAbsModuleGraph(source, filePath, {
|
|
@@ -3581,6 +3616,9 @@ function collectAbsCallRecords(source, seeds, filePath, precomputedModules) {
|
|
|
3581
3616
|
modules = void 0;
|
|
3582
3617
|
}
|
|
3583
3618
|
}
|
|
3619
|
+
if (envNames.length > 0) {
|
|
3620
|
+
modules = { ...collectEnvModules(envNames), ...modules ?? {} };
|
|
3621
|
+
}
|
|
3584
3622
|
try {
|
|
3585
3623
|
importLocals = buildAbsImportLocalMap(source, filePath);
|
|
3586
3624
|
} catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nudojs/service",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Shared inference service for Nudo IDE integrations — file analysis, completions, and .d.ts generation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -12,7 +12,17 @@
|
|
|
12
12
|
"dts-generation",
|
|
13
13
|
"analysis"
|
|
14
14
|
],
|
|
15
|
-
"main": "
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
16
26
|
"repository": {
|
|
17
27
|
"type": "git",
|
|
18
28
|
"url": "git+https://github.com/nudojs/nudo.git",
|
|
@@ -28,10 +38,10 @@
|
|
|
28
38
|
"dependencies": {
|
|
29
39
|
"@babel/traverse": "^7.29.0",
|
|
30
40
|
"@babel/types": "^7.29.0",
|
|
31
|
-
"@nudojs/core": "0.
|
|
32
|
-
"@nudojs/parser": "0.
|
|
33
|
-
"@nudojs/cli": "0.
|
|
34
|
-
"@nudojs/harvester": "0.2.
|
|
41
|
+
"@nudojs/core": "0.4.0",
|
|
42
|
+
"@nudojs/parser": "0.4.0",
|
|
43
|
+
"@nudojs/cli": "0.4.0",
|
|
44
|
+
"@nudojs/harvester": "0.2.2"
|
|
35
45
|
},
|
|
36
46
|
"scripts": {
|
|
37
47
|
"build": "tsup src/index.ts --format esm --dts"
|
package/CHANGELOG.md
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
# @nudojs/service
|
|
2
|
-
|
|
3
|
-
## 0.3.0
|
|
4
|
-
|
|
5
|
-
### Minor Changes
|
|
6
|
-
|
|
7
|
-
- 5786fa5: Promote the B-path (transpile + in-process Abs runtime) to the primary evaluation path for capable sources.
|
|
8
|
-
|
|
9
|
-
- Module graph now supports relative imports, bare-package harvest, default/namespace imports, re-exports, `export *`, require, cycle/depth/missing guards, and env modules (`path` / `node:path` etc.).
|
|
10
|
-
- Diagnostics, `@nudo:env` / mock injection, `call@` / `entry@` provenance, method-missing, unknown-recv, generators, classes, async/await, optional chaining, and destructuring run through B.
|
|
11
|
-
- When B hosts a file, `evaluateProgram` is skipped so TypeValue no longer double-reports.
|
|
12
|
-
- CLI `check` / `types` / `test` accept directories; check uses an Abs-only gate.
|
|
13
|
-
- LSP hover / `getTypeAtPosition` on capable files read the Abs node table; `*.nudo.js` edits evict L0 and recheck open parents.
|
|
14
|
-
- Public core surface now re-exports the algebra API (`Abs`, check, generalize, exec, bridge, `parseSource`, `stripTypes`).
|
|
15
|
-
|
|
16
|
-
- 5786fa5: Speed up repeated analysis with layered memos and tighter cache contracts.
|
|
17
|
-
|
|
18
|
-
- Shared parse/AST LRU, `generalizeFromAst` L0–L3 (instantiate, α-equivalence, dep fingerprint + LRU), Abs module cache, and session-wide memos with an incremental after-edit path.
|
|
19
|
-
- Host cache-eviction contract, AST/function-fingerprint memory caps, conservative call-scan depth cap, and fail-open truncated dependency fingerprints.
|
|
20
|
-
- Fix session-memo staleness and identity holes so after-edit results stay correct.
|
|
21
|
-
|
|
22
|
-
### Patch Changes
|
|
23
|
-
|
|
24
|
-
- Updated dependencies [5786fa5]
|
|
25
|
-
- Updated dependencies [5786fa5]
|
|
26
|
-
- Updated dependencies [5786fa5]
|
|
27
|
-
- Updated dependencies [5786fa5]
|
|
28
|
-
- @nudojs/core@0.3.0
|
|
29
|
-
- @nudojs/cli@0.3.0
|
|
30
|
-
- @nudojs/harvester@0.2.0
|
|
31
|
-
- @nudojs/parser@0.3.0
|
|
32
|
-
|
|
33
|
-
## 0.2.1
|
|
34
|
-
|
|
35
|
-
### Patch Changes
|
|
36
|
-
|
|
37
|
-
- Updated dependencies [0fd0718]
|
|
38
|
-
- @nudojs/cli@0.2.1
|
|
39
|
-
|
|
40
|
-
## 0.2.0
|
|
41
|
-
|
|
42
|
-
### Minor Changes
|
|
43
|
-
|
|
44
|
-
- 6c38283: docs and ci
|
|
45
|
-
- 9f7f819: fix pkg info
|
|
46
|
-
- c175f71: version
|
|
47
|
-
|
|
48
|
-
### Patch Changes
|
|
49
|
-
|
|
50
|
-
- Updated dependencies [6c38283]
|
|
51
|
-
- Updated dependencies [9f7f819]
|
|
52
|
-
- Updated dependencies [c175f71]
|
|
53
|
-
- @nudojs/cli@0.2.0
|
|
54
|
-
- @nudojs/core@0.2.0
|
|
55
|
-
- @nudojs/parser@0.2.0
|
|
56
|
-
|
|
57
|
-
## 0.1.0
|
|
58
|
-
|
|
59
|
-
### Minor Changes
|
|
60
|
-
|
|
61
|
-
- Conceptual design and basic implementation.
|
|
62
|
-
|
|
63
|
-
### Patch Changes
|
|
64
|
-
|
|
65
|
-
- Updated dependencies
|
|
66
|
-
- nudo@0.1.0
|
|
67
|
-
- @nudojs/core@0.1.0
|
|
68
|
-
- @nudojs/parser@0.1.0
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, afterAll } from "vitest";
|
|
2
|
-
import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { collectAbsBindingsFromGraph, analyzeFile, getHoverAtPosition } from "@nudojs/service";
|
|
6
|
-
import { formatAbs, absToString } from "@nudojs/core";
|
|
7
|
-
|
|
8
|
-
const dirs: string[] = [];
|
|
9
|
-
afterAll(() => {
|
|
10
|
-
for (const d of dirs) rmSync(d, { recursive: true, force: true });
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
describe("Abs bindings from module graph", () => {
|
|
14
|
-
it("includes local functions and imported names", () => {
|
|
15
|
-
const dir = mkdtempSync(join(tmpdir(), "nudo-abs-bind-"));
|
|
16
|
-
dirs.push(dir);
|
|
17
|
-
writeFileSync(join(dir, "math.js"), `export function double(x) { return x * 2; }\n`);
|
|
18
|
-
const main = `import { double } from "./math.js";
|
|
19
|
-
export function run(n) { return double(n); }
|
|
20
|
-
const k = 1;
|
|
21
|
-
`;
|
|
22
|
-
const mainPath = join(dir, "main.js");
|
|
23
|
-
writeFileSync(mainPath, main, "utf-8");
|
|
24
|
-
const binds = collectAbsBindingsFromGraph(main, mainPath);
|
|
25
|
-
expect(binds.has("run")).toBe(true);
|
|
26
|
-
expect(binds.has("double")).toBe(true);
|
|
27
|
-
expect(binds.has("k")).toBe(true);
|
|
28
|
-
expect(formatAbs(binds.get("k")!)).toContain("1");
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it("analyzeFile bindings prefer Abs projection for capable files", () => {
|
|
32
|
-
const dir = mkdtempSync(join(tmpdir(), "nudo-abs-bind2-"));
|
|
33
|
-
dirs.push(dir);
|
|
34
|
-
writeFileSync(join(dir, "math.js"), `export function double(x) { return x * 2; }\n`);
|
|
35
|
-
const main = `import { double } from "./math.js";
|
|
36
|
-
export function run(n) { return double(n); }
|
|
37
|
-
`;
|
|
38
|
-
const mainPath = join(dir, "main.js");
|
|
39
|
-
writeFileSync(mainPath, main, "utf-8");
|
|
40
|
-
const result = analyzeFile(mainPath, main);
|
|
41
|
-
expect(result.bindings.has("run")).toBe(true);
|
|
42
|
-
// imported binding should exist
|
|
43
|
-
expect(result.bindings.has("double")).toBe(true);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it("hover on local function returns abs", () => {
|
|
47
|
-
const src = `export function add(a, b) { return a + b; }
|
|
48
|
-
`;
|
|
49
|
-
const h = getHoverAtPosition("/tmp/hover-abs.js", src, 1, 16);
|
|
50
|
-
// 不要求精确命中,但不应抛错
|
|
51
|
-
expect(h === null || typeof h.typeText === "string").toBe(true);
|
|
52
|
-
});
|
|
53
|
-
});
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, afterAll } from "vitest";
|
|
2
|
-
import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join, resolve } from "node:path";
|
|
5
|
-
import { analyzeFile } from "@nudojs/service";
|
|
6
|
-
import { typeValueToString } from "@nudojs/core";
|
|
7
|
-
|
|
8
|
-
const dirs: string[] = [];
|
|
9
|
-
afterAll(() => {
|
|
10
|
-
for (const d of dirs) rmSync(d, { recursive: true, force: true });
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
describe("Abs call records with relative import", () => {
|
|
14
|
-
it("tags imported calls with targetModule and feeds externalFunctions", () => {
|
|
15
|
-
const dir = mkdtempSync(join(tmpdir(), "nudo-abs-call-"));
|
|
16
|
-
dirs.push(dir);
|
|
17
|
-
const bSrc = `export function triple(x) { return x * 3; }\n`;
|
|
18
|
-
const aSrc = `import { triple } from "./b.js";\nfunction caller(n) { return triple(n); }\nconst r = caller(4);\n`;
|
|
19
|
-
writeFileSync(join(dir, "b.js"), bSrc);
|
|
20
|
-
const aPath = resolve(dir, "a.js");
|
|
21
|
-
writeFileSync(aPath, aSrc);
|
|
22
|
-
|
|
23
|
-
const result = analyzeFile(aPath, aSrc);
|
|
24
|
-
|
|
25
|
-
expect(result.externalFunctions).toBeDefined();
|
|
26
|
-
expect(result.externalFunctions).toHaveLength(1);
|
|
27
|
-
const triple = result.externalFunctions![0];
|
|
28
|
-
expect(triple.name).toBe("triple");
|
|
29
|
-
expect(triple.fromModule).toContain("b.js");
|
|
30
|
-
expect(triple.cases).toHaveLength(1);
|
|
31
|
-
expect(typeValueToString(triple.cases[0].result)).toBe("12");
|
|
32
|
-
});
|
|
33
|
-
});
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { analyzeFile } from "../analyzer.ts";
|
|
3
|
-
import { typeValueToString } from "@nudojs/core";
|
|
4
|
-
|
|
5
|
-
describe("self-contained Abs call records", () => {
|
|
6
|
-
it("collects call sites from Abs program eval", () => {
|
|
7
|
-
const source = `
|
|
8
|
-
function add(a, b) { return a + b; }
|
|
9
|
-
const r = add(2, 3);
|
|
10
|
-
`;
|
|
11
|
-
const result = analyzeFile("/t/abs-call.js", source);
|
|
12
|
-
const add = result.functions.find((f) => f.name === "add");
|
|
13
|
-
expect(add).toBeDefined();
|
|
14
|
-
expect(add!.cases.length).toBeGreaterThan(0);
|
|
15
|
-
expect(add!.cases[0]!.source).toBe("callsite");
|
|
16
|
-
expect(typeValueToString(add!.cases[0]!.result)).toBe("5");
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it("self-contained entry still works", () => {
|
|
20
|
-
const source = `function scale(x) { return x + 1; }`;
|
|
21
|
-
const result = analyzeFile("/t/abs-entry.js", source);
|
|
22
|
-
const scale = result.functions.find((f) => f.name === "scale");
|
|
23
|
-
expect(scale!.entryOnly).toBe(true);
|
|
24
|
-
expect(scale!.cases[0]!.intension).toBeDefined();
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("mock seeds feed Abs path (call@ stays exact)", () => {
|
|
28
|
-
const source = `
|
|
29
|
-
// @nudo:mock parse = stub().returns(42)
|
|
30
|
-
function run() {
|
|
31
|
-
return parse();
|
|
32
|
-
}
|
|
33
|
-
const r = run();
|
|
34
|
-
`;
|
|
35
|
-
const result = analyzeFile("/t/abs-mock.js", source);
|
|
36
|
-
const run = result.functions.find((f) => f.name === "run");
|
|
37
|
-
expect(run).toBeDefined();
|
|
38
|
-
const call = run!.cases.find((c) => c.source === "callsite");
|
|
39
|
-
expect(call).toBeDefined();
|
|
40
|
-
expect(typeValueToString(call!.result)).toBe("42");
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("directive expected is carried on CaseResult", () => {
|
|
44
|
-
const source = `
|
|
45
|
-
/**
|
|
46
|
-
* @nudo:case "ok" (1) => T.number
|
|
47
|
-
* @nudo:case "bad" (1) => T.string
|
|
48
|
-
*/
|
|
49
|
-
function id(x) { return x; }
|
|
50
|
-
`;
|
|
51
|
-
const result = analyzeFile("/t/abs-expected.js", source);
|
|
52
|
-
const id = result.functions.find((f) => f.name === "id");
|
|
53
|
-
const ok = id!.cases.find((c) => c.name === "ok");
|
|
54
|
-
const bad = id!.cases.find((c) => c.name === "bad");
|
|
55
|
-
expect(ok!.expected).toBeDefined();
|
|
56
|
-
expect(bad!.expected).toBeDefined();
|
|
57
|
-
expect(result.diagnostics.some((d) => d.code === "nudo:case-expected")).toBe(true);
|
|
58
|
-
});
|
|
59
|
-
});
|