@nudojs/service 0.3.0 → 0.3.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.
Files changed (72) hide show
  1. package/dist/index.d.ts +18 -3
  2. package/dist/index.js +80 -55
  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,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
- });