@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,165 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { typeValueToString } from "@nudojs/core";
3
- import { analyzeFile } from "../analyzer.ts";
4
-
5
- describe("@nudo:as directive", () => {
6
- it("overrides VariableDeclaration init value", () => {
7
- const source = `
8
- /// @nudo:env es
9
-
10
- /**
11
- * @nudo:case "test" (T.string)
12
- */
13
- function loadConfig(path) {
14
- // @nudo:as T.object({ port: T.number, host: T.string })
15
- const config = JSON.parse(path);
16
- return config;
17
- }
18
- `;
19
- const result = analyzeFile("/test/as.js", source);
20
- const res = typeValueToString(result.functions[0].cases[0].result);
21
- expect(res).toContain("port: number");
22
- expect(res).toContain("host: string");
23
- });
24
-
25
- it("overrides ReturnStatement value", () => {
26
- const source = `
27
- /// @nudo:env es
28
-
29
- /**
30
- * @nudo:case "test" (T.string)
31
- */
32
- function loadConfig(path) {
33
- // @nudo:as T.object({ name: T.string, age: T.number })
34
- return JSON.parse(path);
35
- }
36
- `;
37
- const result = analyzeFile("/test/as.js", source);
38
- const res = typeValueToString(result.functions[0].cases[0].result);
39
- expect(res).toContain("name: string");
40
- expect(res).toContain("age: number");
41
- });
42
-
43
- it("does not affect statements without @nudo:as", () => {
44
- const source = `
45
- /// @nudo:env es
46
-
47
- /**
48
- * @nudo:case "test" (5)
49
- */
50
- function add(x) {
51
- // @nudo:as T.string
52
- const a = x + 1;
53
- const b = x + 2;
54
- return b;
55
- }
56
- `;
57
- const result = analyzeFile("/test/as.js", source);
58
- expect(typeValueToString(result.functions[0].cases[0].result)).toBe("7");
59
- });
60
- });
61
-
62
- describe("@nudo:replace directive", () => {
63
- it("replaces a sub-expression by source text match", () => {
64
- const source = `
65
- /// @nudo:env es
66
-
67
- /**
68
- * @nudo:case "test" (T.string)
69
- */
70
- function process(input) {
71
- // @nudo:replace JSON.parse(input) T.object({ id: T.number })
72
- const data = JSON.parse(input);
73
- return data.id;
74
- }
75
- `;
76
- const result = analyzeFile("/test/replace.js", source);
77
- expect(typeValueToString(result.functions[0].cases[0].result)).toBe("number");
78
- });
79
-
80
- it("replaces only the matching sub-expression, not the whole line", () => {
81
- const source = `
82
- /**
83
- * @nudo:case "test" (5, 10)
84
- */
85
- function compute(a, b) {
86
- // @nudo:replace a T.literal(100)
87
- const result = a + b;
88
- return result;
89
- }
90
- `;
91
- const result = analyzeFile("/test/replace.js", source);
92
- expect(typeValueToString(result.functions[0].cases[0].result)).toBe("110");
93
- });
94
-
95
- it("does not match partial identifiers", () => {
96
- const source = `
97
- /**
98
- * @nudo:case "test" ()
99
- */
100
- function test() {
101
- const aa = 1;
102
- const a = 2;
103
- // @nudo:replace a T.literal(99)
104
- const result = aa + a;
105
- return result;
106
- }
107
- `;
108
- const result = analyzeFile("/test/replace.js", source);
109
- expect(typeValueToString(result.functions[0].cases[0].result)).toBe("100");
110
- });
111
-
112
- it("supports member expression replacement", () => {
113
- const source = `
114
- /**
115
- * @nudo:case "test" (T.object({ data: T.unknown }))
116
- */
117
- function process(res) {
118
- // @nudo:replace res.data T.array(T.object({ id: T.number, name: T.string }))
119
- const items = res.data;
120
- return items;
121
- }
122
- `;
123
- const result = analyzeFile("/test/replace.js", source);
124
- const res = typeValueToString(result.functions[0].cases[0].result);
125
- expect(res).toContain("id: number");
126
- expect(res).toContain("name: string");
127
- });
128
-
129
- it("replacement only affects the next statement", () => {
130
- const source = `
131
- /**
132
- * @nudo:case "test" (5, 10)
133
- */
134
- function compute(a, b) {
135
- // @nudo:replace a T.literal(100)
136
- const x = a + b;
137
- const y = a + b;
138
- return y;
139
- }
140
- `;
141
- const result = analyzeFile("/test/replace.js", source);
142
- expect(typeValueToString(result.functions[0].cases[0].result)).toBe("15");
143
- });
144
- });
145
-
146
- describe("@nudo:as and @nudo:replace coexistence", () => {
147
- it("both can be used in the same function", () => {
148
- const source = `
149
- /// @nudo:env es
150
-
151
- /**
152
- * @nudo:case "test" (T.string)
153
- */
154
- function process(input) {
155
- // @nudo:as T.object({ name: T.string, score: T.number })
156
- const data = JSON.parse(input);
157
- // @nudo:replace data.score T.literal(100)
158
- const result = data.score;
159
- return result;
160
- }
161
- `;
162
- const result = analyzeFile("/test/both.js", source);
163
- expect(typeValueToString(result.functions[0].cases[0].result)).toBe("100");
164
- });
165
- });
@@ -1,104 +0,0 @@
1
- import { it, expect, describe, afterAll } from "vitest";
2
- import { analyzeFile, clearBPathCache } from "@nudojs/service";
3
- import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
4
- import { tmpdir } from "node:os";
5
- import { join } from "node:path";
6
-
7
- const dirs: string[] = [];
8
- afterAll(() => {
9
- for (const d of dirs) rmSync(d, { recursive: true, force: true });
10
- });
11
-
12
- function analyze(src: string) {
13
- const dir = mkdtempSync(join(tmpdir(), "nudo-prov-"));
14
- dirs.push(dir);
15
- writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "prov", version: "1.0.0" }));
16
- writeFileSync(join(dir, "index.js"), src);
17
- clearBPathCache();
18
- return analyzeFile(join(dir, "index.js"), src);
19
- }
20
-
21
- describe("B argument provenance on method-missing", () => {
22
- it("origin points at the argument literal, not just the callee", () => {
23
- const src = `function badNum(n) {
24
- return n.toUpperCase();
25
- }
26
- const boom = badNum(42);
27
- `;
28
- const r = analyze(src);
29
- const diags = r.diagnostics.filter(
30
- (d) => d.code === "nudo:no-method" && d.message.includes("toUpperCase"),
31
- );
32
- expect(diags.length).toBe(1);
33
- const origin = diags[0]!.origin;
34
- expect(origin).toBeDefined();
35
- // `const boom = badNum(42);` — 42 starts at column 20
36
- expect(origin!.line).toBe(4);
37
- expect(origin!.column).toBeGreaterThanOrEqual(18);
38
- expect(origin!.column).toBeLessThanOrEqual(22);
39
- });
40
-
41
- it("mocked top-level call does not hit real fetch", () => {
42
- const src = `// @nudo:mock fetch = sinon.stub().onFirstCall().returns({ data: "first" })
43
- function run() { return fetch(); }
44
- const r = run();
45
- function badNum(n) {
46
- return n.toUpperCase();
47
- }
48
- const boom = badNum(7);
49
- `;
50
- const r = analyze(src);
51
- const fetchErrs = r.diagnostics.filter(
52
- (d) => d.message.includes("fetch") && d.severity === "error",
53
- );
54
- expect(fetchErrs).toHaveLength(0);
55
- const diags = r.diagnostics.filter(
56
- (d) => d.code === "nudo:no-method" && d.message.includes("toUpperCase"),
57
- );
58
- expect(diags.length).toBe(1);
59
- expect(diags[0]!.origin?.column).toBeGreaterThanOrEqual(16);
60
- });
61
-
62
- it("unknown-global is not double-reported with builtin-unknown", () => {
63
- const src = `function f() {
64
- return WeakRef;
65
- }
66
- const boom = f();
67
- `;
68
- const r = analyze(src);
69
- const globals = r.diagnostics.filter((d) => d.code === "nudo:unknown-global");
70
- const builtins = r.diagnostics.filter((d) => d.code === "nudo:builtin-unknown");
71
- expect(builtins.some((d) => d.message.includes("WeakRef"))).toBe(true);
72
- expect(globals.filter((d) => d.message.includes("WeakRef"))).toHaveLength(0);
73
- });
74
-
75
- it("B reports unknown-recv for method on unknown param (no TypeValue double)", () => {
76
- const src = `function lonely(u) {
77
- return u.toUpperCase();
78
- }
79
- `;
80
- const r = analyze(src);
81
- const unknownRecv = r.diagnostics.filter(
82
- (d) => d.code === "nudo:unknown-recv" && d.message.includes("toUpperCase"),
83
- );
84
- expect(unknownRecv.length).toBe(1);
85
- expect(unknownRecv[0]!.severity).toBe("warning");
86
- // B 已报则不应再有 no-method 同名
87
- expect(
88
- r.diagnostics.filter((d) => d.code === "nudo:no-method" && d.message.includes("toUpperCase")),
89
- ).toHaveLength(0);
90
- });
91
-
92
- it("B reports unknown-recv for property on unknown", () => {
93
- const src = `function lonely(u) {
94
- return u.foo;
95
- }
96
- `;
97
- const r = analyze(src);
98
- const diags = r.diagnostics.filter(
99
- (d) => (d.code === "nudo:unknown-recv" || d.code === "nudo:no-method") && d.message.includes("foo"),
100
- );
101
- expect(diags.length).toBeGreaterThanOrEqual(1);
102
- expect(diags.some((d) => d.code === "nudo:unknown-recv")).toBe(true);
103
- });
104
- });
@@ -1,54 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { analyzeFile } from "@nudojs/service";
3
- import { typeValueToString } from "@nudojs/core";
4
-
5
- describe("@nudo:as on B path", () => {
6
- it("overrides VariableDeclaration init value", () => {
7
- const source = `
8
- /**
9
- * @nudo:case "test" ("{}")
10
- */
11
- function loadConfig(path) {
12
- // @nudo:as T.object({ port: T.number, host: T.string })
13
- const config = JSON.parse(path);
14
- return config;
15
- }
16
- `;
17
- const result = analyzeFile("/test/as.js", source);
18
- const res = typeValueToString(result.functions[0].cases[0].result);
19
- expect(res).toContain("port: number");
20
- expect(res).toContain("host: string");
21
- });
22
-
23
- it("overrides ReturnStatement value", () => {
24
- const source = `
25
- /**
26
- * @nudo:case "test" ("{}")
27
- */
28
- function loadConfig(path) {
29
- // @nudo:as T.object({ name: T.string, age: T.number })
30
- return JSON.parse(path);
31
- }
32
- `;
33
- const result = analyzeFile("/test/as2.js", source);
34
- const res = typeValueToString(result.functions[0].cases[0].result);
35
- expect(res).toContain("name: string");
36
- expect(res).toContain("age: number");
37
- });
38
-
39
- it("does not affect statements without @nudo:as", () => {
40
- const source = `
41
- /**
42
- * @nudo:case "test" (5)
43
- */
44
- function add(x) {
45
- // @nudo:as T.literal(99)
46
- const y = x + 1;
47
- const z = x + 2;
48
- return z;
49
- }
50
- `;
51
- const result = analyzeFile("/test/as3.js", source);
52
- expect(typeValueToString(result.functions[0].cases[0].result)).toBe("7");
53
- });
54
- });
@@ -1,33 +0,0 @@
1
- import { it, expect, describe, afterEach } from "vitest";
2
- import { tryRunBPath, tryBPathCall, clearBPathCache } from "@nudojs/service";
3
- import { litValue } from "@nudojs/core";
4
-
5
- afterEach(() => clearBPathCache());
6
-
7
- describe("bRunCache key", () => {
8
- // 同长度、前 200+ 字符完全相同、尾部不同的两份源码:
9
- // 旧前缀截断键会命中同一条缓存,静默返回陈旧结果。
10
- const head = `// ${"x".repeat(210)}\nexport function f() { return `;
11
- const srcA = `${head}1; }`;
12
- const srcB = `${head}2; }`;
13
-
14
- it("same source twice returns the identical cached run", () => {
15
- const a = tryRunBPath(srcA, "/test/cache-key.js");
16
- const b = tryRunBPath(srcA, "/test/cache-key.js");
17
- expect(a).toBeDefined();
18
- expect(b).toBe(a);
19
- });
20
-
21
- it("same length + same prefix but different tail must not collide", () => {
22
- const r1 = tryRunBPath(srcA, "/test/cache-key.js");
23
- const r2 = tryRunBPath(srcB, "/test/cache-key.js");
24
- expect(r1).toBeDefined();
25
- expect(r2).toBeDefined();
26
- expect(r2).not.toBe(r1);
27
-
28
- // 语义正确性:srcB 的求值必须看到 f() = 2,而非 srcA 的陈旧 1
29
- const out = tryBPathCall(srcB, "/test/cache-key.js", "f", []);
30
- if (!out) throw new Error("expected f to resolve via B path");
31
- expect(litValue(out)).toBe(2);
32
- });
33
- });
@@ -1,55 +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 { tryBPathCallFull, analyzeFile } from "@nudojs/service";
6
- import { $lit, litValue, 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("B-path call site collection", () => {
14
- it("records calls during B evaluation", () => {
15
- const dir = mkdtempSync(join(tmpdir(), "nudo-bcall-"));
16
- dirs.push(dir);
17
- const src = `
18
- function helper(x) { return x + 1; }
19
- export function caller(n) { return helper(n); }
20
- `;
21
- const p = join(dir, "main.js");
22
- writeFileSync(p, src, "utf-8");
23
- const full = tryBPathCallFull(src, p, "caller", [$lit(4)], { collectCalls: true });
24
- expect(full).toBeDefined();
25
- expect(litValue(full!.result)).toBe(5);
26
- expect(full!.calls?.length).toBeGreaterThanOrEqual(1);
27
- expect(full!.calls!.map((c) => c.fnName)).toContain("helper");
28
- });
29
-
30
- it("analyzeFile still synthesizes call@ for uncalled helper", () => {
31
- const source = `
32
- function uncalled(x) {
33
- return x * 2;
34
- }
35
-
36
- /**
37
- * @nudo:case "t" (5)
38
- */
39
- function caller(y) {
40
- return uncalled(y);
41
- }
42
- `;
43
- const result = analyzeFile("/test/bcall.js", source);
44
- const uncalled = result.functions.find((f) => f.name === "uncalled");
45
- expect(uncalled).toBeDefined();
46
- expect(uncalled!.cases.length).toBeGreaterThanOrEqual(1);
47
- const names = uncalled!.cases.map((c) => c.name);
48
- expect(names.some((n) => n.startsWith("call@") || n.startsWith("entry@"))).toBe(true);
49
- // 结果应为 10
50
- const hit = uncalled!.cases.find((c) => c.name.startsWith("call@"));
51
- if (hit) {
52
- expect(typeValueToString(hit.result)).toBe("10");
53
- }
54
- });
55
- });
@@ -1,78 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { collectBPathDiagnostics } from "@nudojs/service";
3
-
4
- describe("B-path static diagnostics", () => {
5
- it("marks statements after return as unreachable", () => {
6
- const src = `
7
- function f() {
8
- return 1;
9
- const dead = 2;
10
- console.log("no");
11
- }
12
- `;
13
- const d = collectBPathDiagnostics(src);
14
- expect(d.unreachable.length).toBeGreaterThanOrEqual(1);
15
- // dead 声明与后续 console
16
- const lines = d.unreachable.map((u) => u.range.start.line);
17
- expect(lines.some((l) => l >= 4)).toBe(true);
18
- });
19
-
20
- it("marks statements after throw as unreachable", () => {
21
- const src = `
22
- function f(n) {
23
- if (n) {
24
- throw new Error("x");
25
- return 1;
26
- }
27
- return 0;
28
- }
29
- `;
30
- const d = collectBPathDiagnostics(src);
31
- expect(d.unreachable.length).toBeGreaterThanOrEqual(1);
32
- });
33
-
34
- it("does not flag code after if/else both return", () => {
35
- const src = `
36
- function f(n) {
37
- if (n) { return 1; } else { return 2; }
38
- }
39
- `;
40
- const d = collectBPathDiagnostics(src);
41
- // if 内无后续语句
42
- expect(d.unreachable).toHaveLength(0);
43
- });
44
-
45
- it("flags unknown global calls as builtin-unknown", () => {
46
- const src = `
47
- function f() {
48
- return someNativeApi(1);
49
- }
50
- `;
51
- const d = collectBPathDiagnostics(src);
52
- expect(d.builtinUnknown.map((b) => b.name)).toContain("someNativeApi");
53
- });
54
-
55
- it("extraKnown (@nudo:mock / @nudo:env covered) globals are not flagged", () => {
56
- const src = `
57
- function f() {
58
- return someNativeApi(1);
59
- }
60
- `;
61
- expect(collectBPathDiagnostics(src, ["someNativeApi"]).builtinUnknown).toHaveLength(0);
62
- // 无覆盖时仍报
63
- expect(collectBPathDiagnostics(src).builtinUnknown.map((b) => b.name)).toContain("someNativeApi");
64
- });
65
-
66
- it("does not flag locals, imports, or known globals", () => {
67
- const src = `
68
- import { helper } from "./h.js";
69
- function local(x) { return x; }
70
- function f() {
71
- const y = local(1);
72
- return helper(y) + Math.max(1, 2) + JSON.stringify(y);
73
- }
74
- `;
75
- const d = collectBPathDiagnostics(src);
76
- expect(d.builtinUnknown).toHaveLength(0);
77
- });
78
- });
@@ -1,30 +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 { analyzeFile, clearBPathCache } 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("B-path env modules (node:path)", () => {
14
- it("path.join via import from node:path", () => {
15
- clearBPathCache();
16
- const source = `/// @nudo:env node
17
-
18
- import { join } from "node:path";
19
-
20
- /**
21
- * @nudo:case "test" ("src", "utils", "index.js")
22
- */
23
- function buildPath(a, b, c) {
24
- return join(a, b, c);
25
- }
26
- `;
27
- const result = analyzeFile("/test/pathmod.js", source);
28
- expect(typeValueToString(result.functions[0].cases[0].result)).toBe('"src/utils/index.js"');
29
- });
30
- });
@@ -1,80 +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 { analyzeFile, tryBPathCall, isBPathCapable, clearBPathCache } from "@nudojs/service";
6
- import { typeValueToString, $lit, 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("B-path @nudo:env", () => {
14
- it("isBPathCapable allows builtin and path env", () => {
15
- expect(isBPathCapable("function f() { return 1; }", ["es"])).toBe(true);
16
- expect(isBPathCapable("function f() { return 1; }", ["./custom.ts"])).toBe(true);
17
- });
18
-
19
- it("JSON.parse via @nudo:env es", () => {
20
- clearBPathCache();
21
- const dir = mkdtempSync(join(tmpdir(), "nudo-env-"));
22
- dirs.push(dir);
23
- const main = `/// @nudo:env es
24
-
25
- /**
26
- * @nudo:case "t" ("{}")
27
- */
28
- export function parseId(s) {
29
- return typeof JSON.parse(s);
30
- }
31
- `;
32
- const p = join(dir, "main.js");
33
- writeFileSync(p, main, "utf-8");
34
- const r = tryBPathCall(main, p, "parseId", [$lit("{}")], { envNames: ["es"] });
35
- expect(r).toBeDefined();
36
- expect(absToString(r!)).toContain("string");
37
- });
38
-
39
- it("analyzeFile works with @nudo:env es", () => {
40
- clearBPathCache();
41
- const source = `/// @nudo:env es
42
-
43
- /**
44
- * @nudo:case "t" (5)
45
- */
46
- function addOne(n) {
47
- return Math.floor(n) + 1;
48
- }
49
- `;
50
- const result = analyzeFile("/test/env.js", source);
51
- const fn = result.functions.find((f) => f.name === "addOne");
52
- expect(fn).toBeDefined();
53
- expect(typeValueToString(fn!.cases[0].result)).toBe("6");
54
- });
55
-
56
- it("arrowFn @nudo:mock injects into B-path (no real fetch call)", () => {
57
- clearBPathCache();
58
- const dir = mkdtempSync(join(tmpdir(), "nudo-mock-b-"));
59
- dirs.push(dir);
60
- // 回归:arrowFn mock 落在 seedFns,此前 B 路径注入只吃 seedVars——
61
- // 函数体内的 fetch 调用落到真实原生 fetch,拿 Abs 当 URL 直接崩
62
- const source = `// @nudo:mock fetch = (url) => ({ ok: true, json: () => ({ id: 1, name: "ada" }) })
63
- async function loadUser(id) {
64
- const res = await fetch("/users/" + id);
65
- return res.json();
66
- }
67
- loadUser(42);
68
- `;
69
- const p = join(dir, "main.js");
70
- writeFileSync(p, source, "utf-8");
71
- const result = analyzeFile(p, source);
72
- const fn = result.functions.find((f) => f.name === "loadUser");
73
- expect(fn).toBeDefined();
74
- const call = fn!.cases.find((c) => c.name.startsWith("call@"));
75
- expect(call).toBeDefined();
76
- expect(call!.intension?.abs).toContain("ada");
77
- // mock 覆盖后 fetch 不再是 builtin-unknown
78
- expect(result.diagnostics.some((d) => d.code === "nudo:builtin-unknown")).toBe(false);
79
- });
80
- });
@@ -1,71 +0,0 @@
1
- import { it, expect, describe, afterAll } from "vitest";
2
- import { analyzeFile, clearBPathCache, isBPathCapable } from "@nudojs/service";
3
- import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
4
- import { tmpdir } from "node:os";
5
- import { join } from "node:path";
6
-
7
- const dirs: string[] = [];
8
- afterAll(() => {
9
- for (const d of dirs) rmSync(d, { recursive: true, force: true });
10
- });
11
-
12
- function analyze(src: string, file = "index.js") {
13
- const dir = mkdtempSync(join(tmpdir(), "nudo-hosted-"));
14
- dirs.push(dir);
15
- writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "hosted", version: "1.0.0" }));
16
- const p = join(dir, file);
17
- writeFileSync(p, src);
18
- clearBPathCache();
19
- return { result: analyzeFile(p, src), path: p, src };
20
- }
21
-
22
- describe("B-hosted skips TypeValue evaluateProgram", () => {
23
- it("capable file is B-hosted and still infers cases", () => {
24
- const src = `/**
25
- * @nudo:case "n" (3)
26
- */
27
- export function scale(x) {
28
- return x * 2 + 1;
29
- }
30
- `;
31
- const { result, path } = analyze(src);
32
- expect(isBPathCapable(src, [])).toBe(true);
33
- const fn = result.functions.find((f) => f.name === "scale");
34
- expect(fn?.cases).toHaveLength(1);
35
- expect(fn?.cases[0]?.result.kind).toBe("literal");
36
- // 无 TypeValue 专有叠报
37
- expect(result.diagnostics.filter((d) => d.code === "nudo:unknown-global")).toHaveLength(0);
38
- expect(result.bindings.has("scale")).toBe(true);
39
- });
40
-
41
- it("import + top-level call still yields call@ cases", () => {
42
- const dir = mkdtempSync(join(tmpdir(), "nudo-hosted2-"));
43
- dirs.push(dir);
44
- writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "h2", version: "1.0.0" }));
45
- writeFileSync(
46
- join(dir, "util.js"),
47
- `export function twice(n) { return n * 2; }
48
- `,
49
- );
50
- const src = `import { twice } from "./util.js";
51
- const boom = twice(21);
52
- export function go(n) { return twice(n); }
53
- `;
54
- writeFileSync(join(dir, "index.js"), src);
55
- clearBPathCache();
56
- const r = analyzeFile(join(dir, "index.js"), src);
57
- const go = r.functions.find((f) => f.name === "go");
58
- const twice = r.functions.find((f) => f.name === "twice");
59
- expect(go).toBeDefined();
60
- // 跨文件导出可能合成 externalFunctions 或本文件 twice
61
- expect(go!.cases.length + (twice?.cases.length ?? 0)).toBeGreaterThan(0);
62
- });
63
-
64
- it("nodeTypeMap is populated without TypeValue env walk", () => {
65
- const src = `const n = 1;
66
- export function id(x) { return x; }
67
- `;
68
- const { result } = analyze(src);
69
- expect(result.nodeTypeMap.size).toBeGreaterThan(0);
70
- });
71
- });