@nudojs/service 0.2.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 +7 -0
- package/README.md +27 -0
- package/dist/index.d.ts +137 -2
- package/dist/index.js +1464 -38
- package/package.json +14 -2
- package/src/__tests__/analyzer.test.ts +677 -2
- package/src/__tests__/as-replace.test.ts +165 -0
- package/src/__tests__/case-emitter.test.ts +636 -0
- package/src/__tests__/diagnostics.test.ts +90 -0
- package/src/__tests__/dts-generator.test.ts +290 -6
- package/src/__tests__/fn-sig-impl.test.ts +247 -0
- package/src/__tests__/guard-generator.test.ts +29 -0
- package/src/__tests__/integration.test.ts +332 -0
- package/src/__tests__/schema-generator.test.ts +38 -0
- package/src/analyzer.ts +1201 -18
- package/src/case-emitter.ts +475 -0
- package/src/dts-generator.ts +220 -24
- package/src/guard-generator.ts +50 -0
- package/src/index.ts +25 -0
- package/src/schema-generator.ts +43 -0
package/CHANGELOG.md
CHANGED
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# @nudojs/service
|
|
2
|
+
|
|
3
|
+
Shared inference service for [Nudo](https://github.com/nudojs/nudo) IDE integrations.
|
|
4
|
+
|
|
5
|
+
## What is Nudo?
|
|
6
|
+
|
|
7
|
+
Nudo is a type inference engine for JavaScript. Instead of a separate type system, it runs your code with symbolic type values via abstract interpretation — no TypeScript, no build step.
|
|
8
|
+
|
|
9
|
+
## This package
|
|
10
|
+
|
|
11
|
+
`@nudojs/service` provides the high-level analysis API used by editor extensions and build tools:
|
|
12
|
+
|
|
13
|
+
- **File analysis** — `analyzeFile` returns diagnostics, function analyses, and case results
|
|
14
|
+
- **IDE features** — `getTypeAtPosition`, `getCompletionsAtPosition`, `getCasesForFile`
|
|
15
|
+
- **DTS generation** — `generateDts` and `typeValueToTSType` for producing `.d.ts` output
|
|
16
|
+
- **Zod schema generation** — `typeValueToZodSchema` converts inferred types to Zod schema strings
|
|
17
|
+
- **Guard generation** — `generateGuardFunction` produces zero-dependency runtime type guards
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @nudojs/service
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## License
|
|
26
|
+
|
|
27
|
+
[MIT](https://github.com/nudojs/nudo/blob/main/LICENSE)
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Node } from '@babel/types';
|
|
2
2
|
import { TypeValue } from '@nudojs/core';
|
|
3
|
+
import { CallRecord } from '@nudojs/cli/evaluator';
|
|
4
|
+
export { CallRecord } from '@nudojs/cli/evaluator';
|
|
3
5
|
|
|
4
6
|
type SourceLocation = {
|
|
5
7
|
start: {
|
|
@@ -18,6 +20,14 @@ type Diagnostic = {
|
|
|
18
20
|
severity: DiagnosticSeverity;
|
|
19
21
|
message: string;
|
|
20
22
|
tags?: DiagnosticTag[];
|
|
23
|
+
code?: string;
|
|
24
|
+
suggestions?: string[];
|
|
25
|
+
data?: unknown;
|
|
26
|
+
/** provenance of the receiver value (callsite argument that flowed into the error) */
|
|
27
|
+
origin?: {
|
|
28
|
+
line: number;
|
|
29
|
+
column: number;
|
|
30
|
+
};
|
|
21
31
|
};
|
|
22
32
|
type CaseResult = {
|
|
23
33
|
name: string;
|
|
@@ -25,13 +35,28 @@ type CaseResult = {
|
|
|
25
35
|
result: TypeValue;
|
|
26
36
|
throws: TypeValue;
|
|
27
37
|
throwLoc?: SourceLocation;
|
|
38
|
+
source?: "directive" | "callsite";
|
|
39
|
+
/** number of additional call sites folded into a symbolic case */
|
|
40
|
+
aggregatedFrom?: number;
|
|
28
41
|
};
|
|
29
42
|
type FunctionAnalysis = {
|
|
30
43
|
name: string;
|
|
31
44
|
loc: SourceLocation;
|
|
45
|
+
paramNames: string[];
|
|
32
46
|
cases: CaseResult[];
|
|
33
47
|
combined?: TypeValue;
|
|
34
48
|
assertionErrors?: string[];
|
|
49
|
+
entryOnly?: boolean;
|
|
50
|
+
skipped?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* True for functions collected from CJS-style bindings/assignments
|
|
53
|
+
* (`exports.X = fn`, `module.exports = fn`, `const f = fn`): their name has
|
|
54
|
+
* no declaration-level stability, so .d.ts generation skips them while
|
|
55
|
+
* infer/JSON output still reports them.
|
|
56
|
+
*/
|
|
57
|
+
noDeclaration?: boolean;
|
|
58
|
+
/** absolute path of the module this function is imported from (externalFunctions only) */
|
|
59
|
+
fromModule?: string;
|
|
35
60
|
};
|
|
36
61
|
type BindingInfo = {
|
|
37
62
|
type: TypeValue;
|
|
@@ -48,13 +73,63 @@ type AnalysisResult = {
|
|
|
48
73
|
bindings: Map<string, BindingInfo>;
|
|
49
74
|
nodeTypeMap: Map<Node, TypeValue>;
|
|
50
75
|
caseHints: CaseHint[];
|
|
76
|
+
/** functions imported from other modules, synthesized from cross-file call sites observed while analyzing this file */
|
|
77
|
+
externalFunctions?: FunctionAnalysis[];
|
|
51
78
|
};
|
|
52
79
|
type CompletionItem = {
|
|
53
80
|
label: string;
|
|
54
81
|
kind: "property" | "method" | "variable";
|
|
55
82
|
detail?: string;
|
|
56
83
|
};
|
|
57
|
-
|
|
84
|
+
type SymbolInfo = {
|
|
85
|
+
name: string;
|
|
86
|
+
kind: "function" | "variable" | "class" | "parameter";
|
|
87
|
+
loc: SourceLocation;
|
|
88
|
+
uri?: string;
|
|
89
|
+
};
|
|
90
|
+
type ReferenceInfo = {
|
|
91
|
+
name: string;
|
|
92
|
+
loc: SourceLocation;
|
|
93
|
+
uri?: string;
|
|
94
|
+
};
|
|
95
|
+
type SymbolTable = {
|
|
96
|
+
definitions: Map<string, SymbolInfo>;
|
|
97
|
+
references: ReferenceInfo[];
|
|
98
|
+
};
|
|
99
|
+
/** mtime 边缓存:key 为文件路径,edges 为已抽取的相对 import 边(与 buildModuleGraph 返回语义一致)。 */
|
|
100
|
+
type ModuleGraphCache = Map<string, {
|
|
101
|
+
mtimeMs: number;
|
|
102
|
+
size: number;
|
|
103
|
+
edges: string[];
|
|
104
|
+
}>;
|
|
105
|
+
/** Statically extract each file's relative import edges (extension resolution identical to CLI resolveModule: ''/'.js'/'.ts'/'.mjs'; bare npm specifiers skipped). */
|
|
106
|
+
declare function buildModuleGraph(files: string[], cache?: ModuleGraphCache): {
|
|
107
|
+
imports: Map<string, Set<string>>;
|
|
108
|
+
dependents: Map<string, Set<string>>;
|
|
109
|
+
};
|
|
110
|
+
/** changed plus its transitive dependents (reverse-edge BFS); cycle-safe via visited. */
|
|
111
|
+
declare function computeDirtySet(dependents: Map<string, Set<string>>, changedFile: string): string[];
|
|
112
|
+
/** Topological order with dependencies before dependents (only imports edges internal to dirty; cycles tolerated — remaining files appended in arbitrary order). */
|
|
113
|
+
declare function topoSortDirty(imports: Map<string, Set<string>>, dirty: string[]): string[];
|
|
114
|
+
/**
|
|
115
|
+
* Async entry to analyzeFile: preloads path-based env files
|
|
116
|
+
* (`/// @nudo:env ./nudo-harvest-node.ts`) via dynamic import — impossible
|
|
117
|
+
* synchronously in ESM — then runs the sync analysis, which picks the
|
|
118
|
+
* preloaded factories up from the env-loader cache. The sync analyzeFile
|
|
119
|
+
* signature is unchanged for existing consumers (LSP, MCP, vite-plugin).
|
|
120
|
+
*/
|
|
121
|
+
declare function analyzeFileAsync(filePath: string, source: string, activeCases?: Map<string, number>, externalCallRecords?: CallRecord[]): Promise<AnalysisResult>;
|
|
122
|
+
/**
|
|
123
|
+
* 调用点发现(阶段一):在"使用现场"文件(测试 / 上层应用)中求值
|
|
124
|
+
* 顶层代码,收集它对(外部模块导出的)函数的调用记录。每条记录带
|
|
125
|
+
* 真实的实参类型与结果类型——后续 analyzeFile 将其注入合成 case,
|
|
126
|
+
* 使被使用方从 entry-only(参数全 unknown)升级为真实调用形态。
|
|
127
|
+
*
|
|
128
|
+
* 只做求值与记录,不产出诊断;求值异常不抛出(使用现场文件可能
|
|
129
|
+
* 依赖未 mock 的全局,收集不到就收集不到,不能拖垮主分析)。
|
|
130
|
+
*/
|
|
131
|
+
declare function collectCallRecords(filePath: string, source: string): CallRecord[];
|
|
132
|
+
declare function analyzeFile(filePath: string, source: string, activeCases?: Map<string, number>, externalCallRecords?: CallRecord[]): AnalysisResult;
|
|
58
133
|
type CaseInfo = {
|
|
59
134
|
functionName: string;
|
|
60
135
|
caseName: string;
|
|
@@ -68,10 +143,70 @@ declare function getCasesForFile(filePath: string, source: string): {
|
|
|
68
143
|
}[];
|
|
69
144
|
loc: SourceLocation;
|
|
70
145
|
}[];
|
|
146
|
+
/** Async entry to getTypeAtPosition with path-env preloading (see analyzeFileAsync). */
|
|
147
|
+
declare function getTypeAtPositionAsync(filePath: string, source: string, line: number, column: number, activeCases?: Map<string, number>): Promise<TypeValue | null>;
|
|
71
148
|
declare function getTypeAtPosition(filePath: string, source: string, line: number, column: number, activeCases?: Map<string, number>): TypeValue | null;
|
|
72
149
|
declare function getCompletionsAtPosition(filePath: string, source: string, line: number, column: number): CompletionItem[];
|
|
73
150
|
|
|
74
151
|
declare function typeValueToTSType(tv: TypeValue): string;
|
|
152
|
+
/**
|
|
153
|
+
* 为单个函数生成 .d.ts 声明行(JSDoc + 单一 widen 主签名)。
|
|
154
|
+
* service 的 generateDts 与 CLI `--dts`(infer/watch)共用本函数,
|
|
155
|
+
* 两条路径输出保持一致。
|
|
156
|
+
*/
|
|
157
|
+
declare function generateFunctionDtsLines(fn: FunctionAnalysis): string[];
|
|
75
158
|
declare function generateDts(result: AnalysisResult): string;
|
|
76
159
|
|
|
77
|
-
|
|
160
|
+
declare function typeValueToZodSchema(tv: TypeValue): string;
|
|
161
|
+
|
|
162
|
+
declare function generateGuardFunction(name: string, tv: TypeValue): string;
|
|
163
|
+
|
|
164
|
+
/** 单个 TypeValue → parseTypeValueExpr 可解析回去的表达式文本;不可表达返回 null */
|
|
165
|
+
declare function serializeCaseArg(tv: TypeValue): string | null;
|
|
166
|
+
/**
|
|
167
|
+
* 组装单行 ` * @nudo:case "name" (a, b)` 指令文本(无尾换行)。
|
|
168
|
+
* 任一实参不可序列化、或名字含双引号/换行(名字正则 `"([^"]+)"` 承载不了)→ 整体 null。
|
|
169
|
+
*/
|
|
170
|
+
declare function buildCaseDirective(name: string, args: TypeValue[]): string | null;
|
|
171
|
+
/**
|
|
172
|
+
* 从源码剥离所有本模块生成的 @nudo:case 指令(名字以 call@ 开头,整行删除)。
|
|
173
|
+
* 若所属 JSDoc 块因此只剩空 ` *` 行(无其他 @nudo:* 指令、无文字内容),
|
|
174
|
+
* 连块首 `/**` 行与块尾行整块删除。绝不碰非 case 指令与普通注释。
|
|
175
|
+
* 注意:手写但以 call@ 命名的 case 同样会被删——call@ 前缀保留为生成物标记。
|
|
176
|
+
* removed 返回被删指令名列表(按出现顺序)。
|
|
177
|
+
*/
|
|
178
|
+
declare function stripGeneratedCaseDirectives(source: string): {
|
|
179
|
+
source: string;
|
|
180
|
+
removed: string[];
|
|
181
|
+
};
|
|
182
|
+
type EmitSkipReason = "hand-written" | "already-generated" | "entry-only" | "no-serializable-cases" | "no-declaration" | "skipped";
|
|
183
|
+
type EmitResult = {
|
|
184
|
+
source: string;
|
|
185
|
+
changed: boolean;
|
|
186
|
+
written: Array<{
|
|
187
|
+
fn: string;
|
|
188
|
+
cases: string[];
|
|
189
|
+
}>;
|
|
190
|
+
skipped: Array<{
|
|
191
|
+
fn: string;
|
|
192
|
+
reason: EmitSkipReason;
|
|
193
|
+
detail?: string;
|
|
194
|
+
}>;
|
|
195
|
+
};
|
|
196
|
+
/**
|
|
197
|
+
* 把 analysis 中的合成 case(source === "callsite")固化为源码指令:
|
|
198
|
+
*
|
|
199
|
+
* 1. 源码已有任何非 call@ 命名的 case → skip "hand-written";
|
|
200
|
+
* 已有 call@ case → skip "already-generated"。
|
|
201
|
+
* 2. fn.skipped / fn.noDeclaration / fn.entryOnly → 对应 skip。
|
|
202
|
+
* 3. 逐 case 序列化;个别不可序列化的丢弃并在 skipped 记
|
|
203
|
+
* no-serializable-cases(函数整体仍写可序列化子集,全不可序列化才整函数跳过)。
|
|
204
|
+
* 4. 写入位置:函数声明行正上方——已有 JSDoc 块则插到 `/**` 行后,
|
|
205
|
+
* 无块则新建三行块。缩进取声明行的 loc.start.column(babel 0 基列)。
|
|
206
|
+
* 多函数编辑按行号从下往上应用,避免行号漂移。
|
|
207
|
+
*/
|
|
208
|
+
declare function insertGeneratedCaseDirectives(source: string, analysis: AnalysisResult): EmitResult;
|
|
209
|
+
/** 行级 unified diff:`--- a/path` 头 + `@@` hunk + 上下文 3 行;相同返回 "" */
|
|
210
|
+
declare function unifiedDiff(a: string, b: string, path: string): string;
|
|
211
|
+
|
|
212
|
+
export { type AnalysisResult, type BindingInfo, type CaseHint, type CaseInfo, type CaseResult, type CompletionItem, type Diagnostic, type DiagnosticSeverity, type DiagnosticTag, type EmitResult, type EmitSkipReason, type FunctionAnalysis, type ModuleGraphCache, type ReferenceInfo, type SourceLocation, type SymbolInfo, type SymbolTable, analyzeFile, analyzeFileAsync, buildCaseDirective, buildModuleGraph, collectCallRecords, computeDirtySet, generateDts, generateFunctionDtsLines, generateGuardFunction, getCasesForFile, getCompletionsAtPosition, getTypeAtPosition, getTypeAtPositionAsync, insertGeneratedCaseDirectives, serializeCaseArg, stripGeneratedCaseDirectives, topoSortDirty, typeValueToTSType, typeValueToZodSchema, unifiedDiff };
|