@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
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { typeValueToString } from "@nudojs/core";
|
|
3
|
-
import { analyzeFile } from "../analyzer.ts";
|
|
4
|
-
|
|
5
|
-
describe("fnSig impl: concrete args produce precise values", () => {
|
|
6
|
-
it("Math.floor with literal returns exact result", () => {
|
|
7
|
-
const source = `
|
|
8
|
-
/// @nudo:env es
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @nudo:case "concrete" (3.7)
|
|
12
|
-
* @nudo:case "symbolic" (T.number)
|
|
13
|
-
*/
|
|
14
|
-
function floorIt(x) {
|
|
15
|
-
return Math.floor(x);
|
|
16
|
-
}
|
|
17
|
-
`;
|
|
18
|
-
const result = analyzeFile("/test/math.js", source);
|
|
19
|
-
const fn = result.functions[0];
|
|
20
|
-
expect(fn.cases[0].name).toBe("concrete");
|
|
21
|
-
expect(typeValueToString(fn.cases[0].result)).toBe("3");
|
|
22
|
-
expect(fn.cases[1].name).toBe("symbolic");
|
|
23
|
-
expect(typeValueToString(fn.cases[1].result)).toBe("number");
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("Math.max with literals returns exact result", () => {
|
|
27
|
-
const source = `
|
|
28
|
-
/// @nudo:env es
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* @nudo:case "test" (3, 7)
|
|
32
|
-
*/
|
|
33
|
-
function maxOf(a, b) {
|
|
34
|
-
return Math.max(a, b);
|
|
35
|
-
}
|
|
36
|
-
`;
|
|
37
|
-
const result = analyzeFile("/test/math.js", source);
|
|
38
|
-
expect(typeValueToString(result.functions[0].cases[0].result)).toBe("7");
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("JSON.stringify with literal returns exact string", () => {
|
|
42
|
-
const source = `
|
|
43
|
-
/// @nudo:env es
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* @nudo:case "test" (42)
|
|
47
|
-
*/
|
|
48
|
-
function toJson(x) {
|
|
49
|
-
return JSON.stringify(x);
|
|
50
|
-
}
|
|
51
|
-
`;
|
|
52
|
-
const result = analyzeFile("/test/json.js", source);
|
|
53
|
-
expect(typeValueToString(result.functions[0].cases[0].result)).toBe('"42"');
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it("encodeURIComponent with literal returns exact string", () => {
|
|
57
|
-
const source = `
|
|
58
|
-
/// @nudo:env es
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* @nudo:case "test" ("hello world")
|
|
62
|
-
*/
|
|
63
|
-
function encode(s) {
|
|
64
|
-
return encodeURIComponent(s);
|
|
65
|
-
}
|
|
66
|
-
`;
|
|
67
|
-
const result = analyzeFile("/test/encode.js", source);
|
|
68
|
-
expect(typeValueToString(result.functions[0].cases[0].result)).toBe('"hello%20world"');
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("Number.isInteger with literal returns exact boolean", () => {
|
|
72
|
-
const source = `
|
|
73
|
-
/// @nudo:env es
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* @nudo:case "int" (5)
|
|
77
|
-
* @nudo:case "float" (5.5)
|
|
78
|
-
*/
|
|
79
|
-
function checkInt(x) {
|
|
80
|
-
return Number.isInteger(x);
|
|
81
|
-
}
|
|
82
|
-
`;
|
|
83
|
-
const result = analyzeFile("/test/num.js", source);
|
|
84
|
-
expect(typeValueToString(result.functions[0].cases[0].result)).toBe("true");
|
|
85
|
-
expect(typeValueToString(result.functions[0].cases[1].result)).toBe("false");
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it("parseInt with literal returns exact number", () => {
|
|
89
|
-
const source = `
|
|
90
|
-
/// @nudo:env es
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* @nudo:case "test" ("42")
|
|
94
|
-
*/
|
|
95
|
-
function parseIt(s) {
|
|
96
|
-
return parseInt(s);
|
|
97
|
-
}
|
|
98
|
-
`;
|
|
99
|
-
const result = analyzeFile("/test/parse.js", source);
|
|
100
|
-
expect(typeValueToString(result.functions[0].cases[0].result)).toBe("42");
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it("Promise.resolve preserves argument type", () => {
|
|
104
|
-
const source = `
|
|
105
|
-
/// @nudo:env es
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* @nudo:case "test" (42)
|
|
109
|
-
*/
|
|
110
|
-
async function wrap(x) {
|
|
111
|
-
return await Promise.resolve(x);
|
|
112
|
-
}
|
|
113
|
-
`;
|
|
114
|
-
const result = analyzeFile("/test/promise.js", source);
|
|
115
|
-
expect(typeValueToString(result.functions[0].cases[0].result)).toBe("Promise<42>");
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
describe("fnSig impl: URL constructor with concrete args", () => {
|
|
120
|
-
it("URL with literal string returns precise properties", () => {
|
|
121
|
-
const source = `
|
|
122
|
-
/// @nudo:env web
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* @nudo:case "test" ("https://example.com/path?q=hello#hash")
|
|
126
|
-
*/
|
|
127
|
-
function parseUrl(raw) {
|
|
128
|
-
const url = new URL(raw);
|
|
129
|
-
return {
|
|
130
|
-
host: url.hostname,
|
|
131
|
-
path: url.pathname,
|
|
132
|
-
query: url.search,
|
|
133
|
-
hash: url.hash,
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
`;
|
|
137
|
-
const result = analyzeFile("/test/url.js", source);
|
|
138
|
-
const res = typeValueToString(result.functions[0].cases[0].result);
|
|
139
|
-
expect(res).toContain('"example.com"');
|
|
140
|
-
expect(res).toContain('"/path"');
|
|
141
|
-
expect(res).toContain('"?q=hello"');
|
|
142
|
-
expect(res).toContain('"#hash"');
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it("URL with symbolic string falls back to generic type", () => {
|
|
146
|
-
const source = `
|
|
147
|
-
/// @nudo:env web
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* @nudo:case "test" (T.string)
|
|
151
|
-
*/
|
|
152
|
-
function parseUrl(raw) {
|
|
153
|
-
const url = new URL(raw);
|
|
154
|
-
return url.hostname;
|
|
155
|
-
}
|
|
156
|
-
`;
|
|
157
|
-
const result = analyzeFile("/test/url.js", source);
|
|
158
|
-
expect(typeValueToString(result.functions[0].cases[0].result)).toBe("string");
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
describe("fnSig impl: Node.js path with concrete args", () => {
|
|
163
|
-
it("path.join with literals returns exact path", () => {
|
|
164
|
-
const source = `
|
|
165
|
-
/// @nudo:env node
|
|
166
|
-
|
|
167
|
-
import { join } from "node:path";
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* @nudo:case "test" ("src", "utils", "index.js")
|
|
171
|
-
*/
|
|
172
|
-
function buildPath(a, b, c) {
|
|
173
|
-
return join(a, b, c);
|
|
174
|
-
}
|
|
175
|
-
`;
|
|
176
|
-
const result = analyzeFile("/test/path.js", source);
|
|
177
|
-
expect(typeValueToString(result.functions[0].cases[0].result)).toBe('"src/utils/index.js"');
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it("path.extname with literal returns exact extension", () => {
|
|
181
|
-
const source = `
|
|
182
|
-
/// @nudo:env node
|
|
183
|
-
|
|
184
|
-
import { extname } from "node:path";
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* @nudo:case "test" ("file.ts")
|
|
188
|
-
*/
|
|
189
|
-
function getExt(f) {
|
|
190
|
-
return extname(f);
|
|
191
|
-
}
|
|
192
|
-
`;
|
|
193
|
-
const result = analyzeFile("/test/path.js", source);
|
|
194
|
-
expect(typeValueToString(result.functions[0].cases[0].result)).toBe('".ts"');
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it("path.basename with literal returns exact name", () => {
|
|
198
|
-
const source = `
|
|
199
|
-
/// @nudo:env node
|
|
200
|
-
|
|
201
|
-
import { basename } from "node:path";
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* @nudo:case "test" ("/home/user/file.txt")
|
|
205
|
-
*/
|
|
206
|
-
function getName(p) {
|
|
207
|
-
return basename(p);
|
|
208
|
-
}
|
|
209
|
-
`;
|
|
210
|
-
const result = analyzeFile("/test/path.js", source);
|
|
211
|
-
expect(typeValueToString(result.functions[0].cases[0].result)).toBe('"file.txt"');
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
it("path.dirname with literal returns exact dir", () => {
|
|
215
|
-
const source = `
|
|
216
|
-
/// @nudo:env node
|
|
217
|
-
|
|
218
|
-
import { dirname } from "node:path";
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* @nudo:case "test" ("/home/user/file.txt")
|
|
222
|
-
*/
|
|
223
|
-
function getDir(p) {
|
|
224
|
-
return dirname(p);
|
|
225
|
-
}
|
|
226
|
-
`;
|
|
227
|
-
const result = analyzeFile("/test/path.js", source);
|
|
228
|
-
expect(typeValueToString(result.functions[0].cases[0].result)).toBe('"/home/user"');
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it("path.join with symbolic args falls back to string", () => {
|
|
232
|
-
const source = `
|
|
233
|
-
/// @nudo:env node
|
|
234
|
-
|
|
235
|
-
import { join } from "node:path";
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* @nudo:case "test" (T.string, T.string)
|
|
239
|
-
*/
|
|
240
|
-
function buildPath(a, b) {
|
|
241
|
-
return join(a, b);
|
|
242
|
-
}
|
|
243
|
-
`;
|
|
244
|
-
const result = analyzeFile("/test/path.js", source);
|
|
245
|
-
expect(typeValueToString(result.functions[0].cases[0].result)).toBe("string");
|
|
246
|
-
});
|
|
247
|
-
});
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { T } from "@nudojs/core";
|
|
3
|
-
import { generateGuardFunction } from "../guard-generator.ts";
|
|
4
|
-
|
|
5
|
-
describe("guard-generator", () => {
|
|
6
|
-
it("generates guard for string type", () => {
|
|
7
|
-
const guard = generateGuardFunction("isString", T.string);
|
|
8
|
-
expect(guard).toContain("typeof data === 'string'");
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it("generates guard for object type", () => {
|
|
12
|
-
const obj = T.object({ name: T.string, age: T.number });
|
|
13
|
-
const guard = generateGuardFunction("isUser", obj);
|
|
14
|
-
expect(guard).toContain("typeof data === 'object'");
|
|
15
|
-
expect(guard).toContain("data.name");
|
|
16
|
-
expect(guard).toContain("data.age");
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it("generates guard for union type", () => {
|
|
20
|
-
const union = T.union(T.string, T.number);
|
|
21
|
-
const guard = generateGuardFunction("isStringOrNumber", union);
|
|
22
|
-
expect(guard).toContain("||");
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("generates guard for array type", () => {
|
|
26
|
-
const guard = generateGuardFunction("isStringArray", T.array(T.string));
|
|
27
|
-
expect(guard).toContain("Array.isArray(data)");
|
|
28
|
-
});
|
|
29
|
-
});
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { barePackageName, collectBarePackages } from "../harvest-auto.ts";
|
|
3
|
-
|
|
4
|
-
describe("barePackageName", () => {
|
|
5
|
-
it("keeps simple package names", () => {
|
|
6
|
-
expect(barePackageName("ms")).toBe("ms");
|
|
7
|
-
expect(barePackageName("lodash/fp")).toBe("lodash");
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
it("keeps scoped packages", () => {
|
|
11
|
-
expect(barePackageName("@types/node")).toBe("@types/node");
|
|
12
|
-
expect(barePackageName("@babel/parser")).toBe("@babel/parser");
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it("rejects relative / absolute / node builtins", () => {
|
|
16
|
-
expect(barePackageName("./a.js")).toBeUndefined();
|
|
17
|
-
expect(barePackageName("../b")).toBeUndefined();
|
|
18
|
-
expect(barePackageName("/abs/path")).toBeUndefined();
|
|
19
|
-
expect(barePackageName("node:fs")).toBeUndefined();
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
describe("collectBarePackages", () => {
|
|
24
|
-
it("extracts import and require specs", () => {
|
|
25
|
-
const src = `
|
|
26
|
-
import ms from "ms";
|
|
27
|
-
import { join } from "path";
|
|
28
|
-
const x = require("lodash/fp");
|
|
29
|
-
import fs from "node:fs";
|
|
30
|
-
import local from "./local.js";
|
|
31
|
-
`;
|
|
32
|
-
const pkgs = collectBarePackages(src);
|
|
33
|
-
expect(pkgs).toContain("ms");
|
|
34
|
-
expect(pkgs).toContain("path");
|
|
35
|
-
expect(pkgs).toContain("lodash");
|
|
36
|
-
expect(pkgs).not.toContain("node:fs");
|
|
37
|
-
expect(pkgs).not.toContain("./local.js");
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it("returns empty for unparsable source", () => {
|
|
41
|
-
expect(collectBarePackages("function (")).toEqual([]);
|
|
42
|
-
});
|
|
43
|
-
});
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { harvestNodeTypes, summarizeNodeEnv } from "../harvest-node.ts";
|
|
3
|
-
import { resolve } from "node:path";
|
|
4
|
-
|
|
5
|
-
describe("harvest @types/node", () => {
|
|
6
|
-
it("harvests @types/node when installed", () => {
|
|
7
|
-
const root = resolve(process.cwd(), "../..");
|
|
8
|
-
const r = harvestNodeTypes(root, 6);
|
|
9
|
-
if (!r.ok) {
|
|
10
|
-
// env may not have @types/node in some CI — soft skip
|
|
11
|
-
expect(r.error).toBeTruthy();
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
expect(r.files).toBeGreaterThan(0);
|
|
15
|
-
expect(r.env.stats.symbols).toBeGreaterThan(0);
|
|
16
|
-
const s = summarizeNodeEnv(r.env);
|
|
17
|
-
expect(s.symbolCount).toBeGreaterThan(0);
|
|
18
|
-
// Node env 通常含 process / Buffer 等全局
|
|
19
|
-
const hasUseful =
|
|
20
|
-
s.globals.some((g) => /process|Buffer|console/i.test(g)) ||
|
|
21
|
-
s.modules.length > 0;
|
|
22
|
-
expect(hasUseful).toBe(true);
|
|
23
|
-
});
|
|
24
|
-
});
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { harvestPackage, formatHarvestSummary, resolvePackageRoot } from "../harvest-package.ts";
|
|
3
|
-
import { existsSync } from "node:fs";
|
|
4
|
-
import { resolve } from "node:path";
|
|
5
|
-
|
|
6
|
-
describe("harvest npm package", () => {
|
|
7
|
-
const root = resolve(process.cwd(), "../..");
|
|
8
|
-
// monorepo root may vary; try a few
|
|
9
|
-
const cwdHasTs = existsSync(resolve(root, "node_modules/typescript/package.json"));
|
|
10
|
-
|
|
11
|
-
it("resolvePackageRoot finds workspace packages", () => {
|
|
12
|
-
// typescript is a root devDep of the monorepo
|
|
13
|
-
const r = resolvePackageRoot("typescript", root);
|
|
14
|
-
if (r) {
|
|
15
|
-
expect(existsSync(r)).toBe(true);
|
|
16
|
-
} else {
|
|
17
|
-
// environment without typescript installed — skip soft
|
|
18
|
-
expect(r).toBeUndefined();
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it("harvestPackage on typescript if available", () => {
|
|
23
|
-
const r = resolvePackageRoot("typescript", root);
|
|
24
|
-
if (!r) {
|
|
25
|
-
expect(true).toBe(true);
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
const h = harvestPackage("typescript", root);
|
|
29
|
-
if ("error" in h) {
|
|
30
|
-
// no d.ts collected — still ok in constrained env
|
|
31
|
-
expect(h.error).toBeTruthy();
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
expect(h.dtsFiles.length).toBeGreaterThan(0);
|
|
35
|
-
expect(h.env.stats.symbols).toBeGreaterThan(0);
|
|
36
|
-
expect(formatHarvestSummary(h)).toContain("typescript");
|
|
37
|
-
});
|
|
38
|
-
});
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, afterAll } from "vitest";
|
|
2
|
-
import { writeFileSync, rmSync, mkdirSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { harvestedValueToAbs, bareSpecToAbsModules, evalAbsModuleGraph } from "@nudojs/service";
|
|
5
|
-
import { T, analyzeFn, numLit, absToString, getFnImpl } from "@nudojs/core";
|
|
6
|
-
|
|
7
|
-
const dirs: string[] = [];
|
|
8
|
-
afterAll(() => {
|
|
9
|
-
for (const d of dirs) rmSync(d, { recursive: true, force: true });
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
describe("harvest → Abs", () => {
|
|
13
|
-
it("fnSig becomes callable absFunction with declared return", () => {
|
|
14
|
-
const tv = T.fnSig([T.number], T.string);
|
|
15
|
-
const a = harvestedValueToAbs(tv);
|
|
16
|
-
expect(a.shape.k).toBe("fn");
|
|
17
|
-
expect(a.conf).toBe("mock");
|
|
18
|
-
expect(getFnImpl(a)?.apply).toBeTypeOf("function");
|
|
19
|
-
const r = getFnImpl(a)!.apply!([numLit(1)]);
|
|
20
|
-
expect(absToString(r)).toContain("string");
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it("bareSpecToAbsModules finds lodash from monorepo root", () => {
|
|
24
|
-
const file = join(process.cwd(), "packages/service/src/__probe.js");
|
|
25
|
-
const mods = bareSpecToAbsModules("lodash", file);
|
|
26
|
-
expect(mods).toBeDefined();
|
|
27
|
-
expect(Object.keys(mods!.named).length).toBeGreaterThan(0);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("module graph injects bare import into analyzeFn", () => {
|
|
31
|
-
// 放在 monorepo 内,harvest 才能找到 node_modules/@types/lodash
|
|
32
|
-
const dir = join(process.cwd(), "packages/service/src/__tmp-bare-mod");
|
|
33
|
-
mkdirSync(dir, { recursive: true });
|
|
34
|
-
dirs.push(dir);
|
|
35
|
-
const main = `import { chunk } from "lodash";
|
|
36
|
-
export function go(x) { return chunk(x, 2); }
|
|
37
|
-
`;
|
|
38
|
-
const mainPath = join(dir, "main.js");
|
|
39
|
-
writeFileSync(mainPath, main, "utf-8");
|
|
40
|
-
const { modules } = evalAbsModuleGraph(main, mainPath);
|
|
41
|
-
expect(modules["lodash"]).toBeDefined();
|
|
42
|
-
const r = analyzeFn(main, "go", [numLit(4)], undefined, undefined, undefined, modules);
|
|
43
|
-
expect(r).toBeDefined();
|
|
44
|
-
expect(["mock", "partial", "path", "exact", "widened", "opaque"]).toContain(r.conf);
|
|
45
|
-
});
|
|
46
|
-
});
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { getHoverAtPosition } from "../analyzer.ts";
|
|
3
|
-
|
|
4
|
-
describe("getHoverAtPosition lossless Abs", () => {
|
|
5
|
-
it("hover on function name shows Abs + intension", () => {
|
|
6
|
-
const source = `function scale(x) { return x + 1; }\nscale(2);\n`;
|
|
7
|
-
const hover = getHoverAtPosition("/t/hover.js", source, 1, 9);
|
|
8
|
-
expect(hover).not.toBeNull();
|
|
9
|
-
expect(hover!.intension ?? hover!.typeText).toContain("scale");
|
|
10
|
-
expect(hover!.intension ?? "").toContain("+");
|
|
11
|
-
// 无损 Abs:含 conf 标记
|
|
12
|
-
expect(hover!.abs).toBeDefined();
|
|
13
|
-
expect(hover!.abs).toContain("#");
|
|
14
|
-
expect(hover!.absMultiline).toBeDefined();
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it("hover on binding shows Abs from evalProgramAbs", () => {
|
|
18
|
-
const source = `const x = 1 + 2;\n`;
|
|
19
|
-
const hover = getHoverAtPosition("/t/h3.js", source, 1, 7);
|
|
20
|
-
if (hover?.abs) {
|
|
21
|
-
expect(hover.abs).toContain("3");
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("hover on expression uses Abs node table (lossless)", () => {
|
|
26
|
-
const source = `const n = 1 + 2;\n`;
|
|
27
|
-
// column of `2` is 14 (0-based)
|
|
28
|
-
const hover = getHoverAtPosition("/t/h4.js", source, 1, 14);
|
|
29
|
-
expect(hover).not.toBeNull();
|
|
30
|
-
expect(hover!.abs).toBeDefined();
|
|
31
|
-
expect(hover!.abs).toContain("2");
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it("hover on non-function still returns type", () => {
|
|
35
|
-
const source = `const n = 1;\n`;
|
|
36
|
-
const hover = getHoverAtPosition("/t/h2.js", source, 1, 7);
|
|
37
|
-
if (hover) expect(typeof hover.typeText).toBe("string");
|
|
38
|
-
});
|
|
39
|
-
});
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { analyzeFile, serializeInferJson } from "../index.ts";
|
|
3
|
-
|
|
4
|
-
describe("InferJson contract v1", () => {
|
|
5
|
-
it("self-contained function has abs in intension", () => {
|
|
6
|
-
const src = `function scale(x) { return x + 1; }\n`;
|
|
7
|
-
const result = analyzeFile("/t/scale.js", src);
|
|
8
|
-
const json = serializeInferJson(result, "/t/scale.js");
|
|
9
|
-
expect(json.version).toBe(1);
|
|
10
|
-
expect(json.file).toBe("/t/scale.js");
|
|
11
|
-
expect(json.summary.functions).toBeGreaterThan(0);
|
|
12
|
-
const fn = json.functions.find((f) => f.name === "scale");
|
|
13
|
-
expect(fn).toBeDefined();
|
|
14
|
-
expect(fn!.entryOnly).toBe(true);
|
|
15
|
-
const entry = fn!.cases.find((c) => c.name.startsWith("entry@"));
|
|
16
|
-
expect(entry).toBeDefined();
|
|
17
|
-
expect(entry!.intension?.abs).toContain("#");
|
|
18
|
-
expect(entry!.intension?.display).toContain("scale");
|
|
19
|
-
// 外延仍有 TypeValue 字符串
|
|
20
|
-
expect(typeof entry!.result).toBe("string");
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it("call site cases include args/result strings", () => {
|
|
24
|
-
const src = `
|
|
25
|
-
function add(a, b) { return a + b; }
|
|
26
|
-
const r = add(2, 3);
|
|
27
|
-
`;
|
|
28
|
-
const result = analyzeFile("/t/add.js", src);
|
|
29
|
-
const json = serializeInferJson(result, "/t/add.js");
|
|
30
|
-
const add = json.functions.find((f) => f.name === "add");
|
|
31
|
-
expect(add!.cases.length).toBeGreaterThan(0);
|
|
32
|
-
expect(add!.cases[0]!.args.length).toBe(2);
|
|
33
|
-
expect(add!.cases[0]!.result).toBe("5");
|
|
34
|
-
});
|
|
35
|
-
});
|