@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,106 +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 setup() {
|
|
13
|
-
const dir = mkdtempSync(join(tmpdir(), "nudo-imp-"));
|
|
14
|
-
dirs.push(dir);
|
|
15
|
-
writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "imp", version: "1.0.0" }));
|
|
16
|
-
writeFileSync(
|
|
17
|
-
join(dir, "util.js"),
|
|
18
|
-
`export function add(a, b) {
|
|
19
|
-
return a + b;
|
|
20
|
-
}
|
|
21
|
-
export default function mul(a, b) {
|
|
22
|
-
return a * b;
|
|
23
|
-
}
|
|
24
|
-
`,
|
|
25
|
-
);
|
|
26
|
-
return dir;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function goResult(dir: string, src: string) {
|
|
30
|
-
writeFileSync(join(dir, "index.js"), src);
|
|
31
|
-
const r = analyzeFile(join(dir, "index.js"), src);
|
|
32
|
-
const go = r.functions.find((f) => f.name === "go");
|
|
33
|
-
return { result: go?.cases[0]?.result, diagnostics: r.diagnostics };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
describe("B-path import default / namespace", () => {
|
|
37
|
-
it("named import", () => {
|
|
38
|
-
clearBPathCache();
|
|
39
|
-
const dir = setup();
|
|
40
|
-
const { result } = goResult(
|
|
41
|
-
dir,
|
|
42
|
-
`import { add } from "./util.js";
|
|
43
|
-
/**
|
|
44
|
-
* @nudo:case "nums" (1, 2)
|
|
45
|
-
*/
|
|
46
|
-
export function go(a, b) {
|
|
47
|
-
return add(a, b);
|
|
48
|
-
}
|
|
49
|
-
`,
|
|
50
|
-
);
|
|
51
|
-
expect(result).toMatchObject({ kind: "literal", value: 3 });
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("default import of export default function", () => {
|
|
55
|
-
clearBPathCache();
|
|
56
|
-
const dir = setup();
|
|
57
|
-
const { result, diagnostics } = goResult(
|
|
58
|
-
dir,
|
|
59
|
-
`import mul from "./util.js";
|
|
60
|
-
/**
|
|
61
|
-
* @nudo:case "nums" (3, 4)
|
|
62
|
-
*/
|
|
63
|
-
export function go(a, b) {
|
|
64
|
-
return mul(a, b);
|
|
65
|
-
}
|
|
66
|
-
`,
|
|
67
|
-
);
|
|
68
|
-
expect(result).toMatchObject({ kind: "literal", value: 12 });
|
|
69
|
-
expect(diagnostics.filter((d) => d.severity === "error")).toHaveLength(0);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("namespace import member call", () => {
|
|
73
|
-
clearBPathCache();
|
|
74
|
-
const dir = setup();
|
|
75
|
-
const { result, diagnostics } = goResult(
|
|
76
|
-
dir,
|
|
77
|
-
`import * as util from "./util.js";
|
|
78
|
-
/**
|
|
79
|
-
* @nudo:case "nums" (5, 6)
|
|
80
|
-
*/
|
|
81
|
-
export function go(a, b) {
|
|
82
|
-
return util.add(a, b);
|
|
83
|
-
}
|
|
84
|
-
`,
|
|
85
|
-
);
|
|
86
|
-
expect(result).toMatchObject({ kind: "literal", value: 11 });
|
|
87
|
-
expect(diagnostics.filter((d) => d.severity === "error")).toHaveLength(0);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it("namespace import default slot", () => {
|
|
91
|
-
clearBPathCache();
|
|
92
|
-
const dir = setup();
|
|
93
|
-
const { result } = goResult(
|
|
94
|
-
dir,
|
|
95
|
-
`import * as util from "./util.js";
|
|
96
|
-
/**
|
|
97
|
-
* @nudo:case "nums" (2, 5)
|
|
98
|
-
*/
|
|
99
|
-
export function go(a, b) {
|
|
100
|
-
return util.default(a, b);
|
|
101
|
-
}
|
|
102
|
-
`,
|
|
103
|
-
);
|
|
104
|
-
expect(result).toMatchObject({ kind: "literal", value: 10 });
|
|
105
|
-
});
|
|
106
|
-
});
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { it, expect, describe } from "vitest";
|
|
2
|
-
import { analyzeFile } from "@nudojs/service";
|
|
3
|
-
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
import { join } from "node:path";
|
|
6
|
-
|
|
7
|
-
function analyze(src: string) {
|
|
8
|
-
const dir = mkdtempSync(join(tmpdir(), "nudo-bdedup-"));
|
|
9
|
-
writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "bdedup", version: "1.0.0" }));
|
|
10
|
-
writeFileSync(join(dir, "index.js"), src);
|
|
11
|
-
return analyzeFile(join(dir, "index.js"), src);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
describe("B method-missing provenance + dedupe", () => {
|
|
15
|
-
it("no-method is not double-reported when B and TypeValue both see the call", () => {
|
|
16
|
-
// B case 执行 + evaluateProgram 顶层 const 调用都会碰到 toUpperCase 缺失;
|
|
17
|
-
// 按名去重后只应剩一条。
|
|
18
|
-
const src = `
|
|
19
|
-
/**
|
|
20
|
-
* @nudo:case "n" (42)
|
|
21
|
-
*/
|
|
22
|
-
function demo(n) {
|
|
23
|
-
return n.toUpperCase();
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const boom = demo(42);
|
|
27
|
-
`;
|
|
28
|
-
const r = analyze(src);
|
|
29
|
-
const methodDiags = r.diagnostics.filter(
|
|
30
|
-
(d) => d.code === "nudo:no-method" && d.message.includes("toUpperCase"),
|
|
31
|
-
);
|
|
32
|
-
expect(methodDiags.length).toBe(1);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("TypeValue-only method diags are kept when B does not report the name", () => {
|
|
36
|
-
const src = `
|
|
37
|
-
function badNum(n) {
|
|
38
|
-
return n.toUpperCase();
|
|
39
|
-
}
|
|
40
|
-
const boom = badNum(42);
|
|
41
|
-
`;
|
|
42
|
-
const r = analyze(src);
|
|
43
|
-
const diags = r.diagnostics.filter((d) => d.code === "nudo:no-method");
|
|
44
|
-
expect(diags.length).toBeGreaterThanOrEqual(1);
|
|
45
|
-
expect(diags.some((d) => d.message.includes("toUpperCase"))).toBe(true);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("same name:line is reported at most once", () => {
|
|
49
|
-
const src = `
|
|
50
|
-
/**
|
|
51
|
-
* @nudo:case "null" (null)
|
|
52
|
-
* @nudo:case "num" (1)
|
|
53
|
-
*/
|
|
54
|
-
function demo(o) {
|
|
55
|
-
return o.foo;
|
|
56
|
-
}
|
|
57
|
-
`;
|
|
58
|
-
const r = analyze(src);
|
|
59
|
-
const fooDiags = r.diagnostics.filter(
|
|
60
|
-
(d) => d.code === "nudo:no-method" && d.message.includes("'foo'"),
|
|
61
|
-
);
|
|
62
|
-
const keys = fooDiags.map((d) => `${d.range.start.line}:${d.message}`);
|
|
63
|
-
expect(new Set(keys).size).toBe(keys.length);
|
|
64
|
-
});
|
|
65
|
-
});
|
|
@@ -1,45 +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, tryBPathCallFull } from "@nudojs/service";
|
|
6
|
-
import { $lit } 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 method-missing diagnostics", () => {
|
|
14
|
-
it("records no-method on number receiver via B execution", () => {
|
|
15
|
-
clearBPathCache();
|
|
16
|
-
const dir = mkdtempSync(join(tmpdir(), "nudo-md-"));
|
|
17
|
-
dirs.push(dir);
|
|
18
|
-
const src = `
|
|
19
|
-
export function badNum(n) {
|
|
20
|
-
return n.toUpperCase();
|
|
21
|
-
}
|
|
22
|
-
`;
|
|
23
|
-
const p = join(dir, "m.js");
|
|
24
|
-
writeFileSync(p, src, "utf-8");
|
|
25
|
-
const full = tryBPathCallFull(src, p, "badNum", [$lit(42)]);
|
|
26
|
-
expect(full).toBeDefined();
|
|
27
|
-
expect(full!.memberDiags?.length).toBeGreaterThanOrEqual(1);
|
|
28
|
-
expect(full!.memberDiags![0]!.name).toBe("toUpperCase");
|
|
29
|
-
expect(full!.memberDiags![0]!.receiver).toBe("number");
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it("analyzeFile still reports no-method for top-level call", () => {
|
|
33
|
-
clearBPathCache();
|
|
34
|
-
const source = `
|
|
35
|
-
function badNum(n) {
|
|
36
|
-
return n.toUpperCase();
|
|
37
|
-
}
|
|
38
|
-
const boom = badNum(42);
|
|
39
|
-
`;
|
|
40
|
-
const result = analyzeFile("/test/badnum2.js", source);
|
|
41
|
-
const diags = result.diagnostics.filter((d) => d.code === "nudo:no-method");
|
|
42
|
-
expect(diags.length).toBeGreaterThanOrEqual(1);
|
|
43
|
-
expect(diags[0]!.message).toContain("toUpperCase");
|
|
44
|
-
});
|
|
45
|
-
});
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { it, expect, describe, afterAll } from "vitest";
|
|
2
|
-
import { analyzeFile, clearBPathCache, evalAbsModuleGraph } 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 setup(files: Record<string, string>) {
|
|
13
|
-
const dir = mkdtempSync(join(tmpdir(), "nudo-bdiag-"));
|
|
14
|
-
dirs.push(dir);
|
|
15
|
-
writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "bdiag", version: "1.0.0" }));
|
|
16
|
-
for (const [name, src] of Object.entries(files)) {
|
|
17
|
-
writeFileSync(join(dir, name), src);
|
|
18
|
-
}
|
|
19
|
-
return dir;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function analyze(dir: string, src: string, file = "index.js") {
|
|
23
|
-
writeFileSync(join(dir, file), src);
|
|
24
|
-
clearBPathCache();
|
|
25
|
-
return analyzeFile(join(dir, file), src);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
describe("B module-load diagnostics (host, no TypeValue double)", () => {
|
|
29
|
-
it("reports circular import once via B module graph", () => {
|
|
30
|
-
const dir = setup({
|
|
31
|
-
"a.js": `import { b } from "./b.js";
|
|
32
|
-
export function a(n) { return n <= 0 ? 0 : b(n - 1); }
|
|
33
|
-
`,
|
|
34
|
-
"b.js": `import { a } from "./a.js";
|
|
35
|
-
export function b(n) { return n <= 0 ? 1 : a(n - 1); }
|
|
36
|
-
`,
|
|
37
|
-
});
|
|
38
|
-
const src = `import { a } from "./a.js";
|
|
39
|
-
/**
|
|
40
|
-
* @nudo:case "n" (2)
|
|
41
|
-
*/
|
|
42
|
-
export function go(n) { return a(n); }
|
|
43
|
-
`;
|
|
44
|
-
const r = analyze(dir, src);
|
|
45
|
-
const cycles = r.diagnostics.filter((d) => d.code === "nudo:module-cycle");
|
|
46
|
-
expect(cycles.length).toBe(1);
|
|
47
|
-
expect(cycles[0]!.message).toContain("Circular module load");
|
|
48
|
-
expect(cycles[0]!.message).toContain("->");
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("reports missing relative import once via B module graph", () => {
|
|
52
|
-
const dir = setup({});
|
|
53
|
-
const src = `import { x } from "./nope.js";
|
|
54
|
-
/**
|
|
55
|
-
* @nudo:case
|
|
56
|
-
*/
|
|
57
|
-
export function go() { return x; }
|
|
58
|
-
`;
|
|
59
|
-
const r = analyze(dir, src);
|
|
60
|
-
const missing = r.diagnostics.filter((d) => d.code === "nudo:module-missing");
|
|
61
|
-
expect(missing.length).toBe(1);
|
|
62
|
-
expect(missing[0]!.message).toContain("nope.js");
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it("B module graph issues come from evalAbsModuleGraph", () => {
|
|
66
|
-
const dir = setup({
|
|
67
|
-
"a.js": `import { b } from "./b.js";
|
|
68
|
-
export function a() { return b(); }
|
|
69
|
-
`,
|
|
70
|
-
"b.js": `import { a } from "./a.js";
|
|
71
|
-
export function b() { return a(); }
|
|
72
|
-
`,
|
|
73
|
-
});
|
|
74
|
-
const src = `import { a } from "./a.js";
|
|
75
|
-
export function go() { return a(); }
|
|
76
|
-
`;
|
|
77
|
-
writeFileSync(join(dir, "index.js"), src);
|
|
78
|
-
clearBPathCache();
|
|
79
|
-
const g = evalAbsModuleGraph(src, join(dir, "index.js"));
|
|
80
|
-
expect(g.issues.some((i) => i.kind === "cycle")).toBe(true);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("recursion-truncated is not double-reported when Abs budget fires", () => {
|
|
84
|
-
const dir = setup({});
|
|
85
|
-
// 抽象参数触发调用预算截断;B Abs 路径与 TypeValue 都可能记 recursion:*
|
|
86
|
-
const src = `
|
|
87
|
-
/**
|
|
88
|
-
* @nudo:case "n" (T.number)
|
|
89
|
-
*/
|
|
90
|
-
export function deep(n) {
|
|
91
|
-
if (n <= 0) return 0;
|
|
92
|
-
return 1 + deep(n - 1);
|
|
93
|
-
}
|
|
94
|
-
const boom = deep(3);
|
|
95
|
-
`;
|
|
96
|
-
const r = analyze(dir, src);
|
|
97
|
-
const rec = r.diagnostics.filter((d) => d.code === "nudo:recursion-truncated");
|
|
98
|
-
// 至多一条(B 截断则压 TypeValue;否则 TypeValue 自报)
|
|
99
|
-
expect(rec.length).toBeLessThanOrEqual(1);
|
|
100
|
-
});
|
|
101
|
-
});
|
|
@@ -1,46 +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 { analyzeFileAsync, 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 path-based @nudo:env", () => {
|
|
14
|
-
it("loads ./custom.env.ts globals", async () => {
|
|
15
|
-
clearBPathCache();
|
|
16
|
-
const dir = mkdtempSync(join(tmpdir(), "nudo-path-env-"));
|
|
17
|
-
dirs.push(dir);
|
|
18
|
-
writeFileSync(
|
|
19
|
-
join(dir, "custom.env.ts"),
|
|
20
|
-
`import { T } from "@nudojs/core";
|
|
21
|
-
export function defineEnv() {
|
|
22
|
-
return {
|
|
23
|
-
globals: {
|
|
24
|
-
MAGIC: T.literal(99),
|
|
25
|
-
},
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
`,
|
|
29
|
-
);
|
|
30
|
-
const main = `/// @nudo:env ./custom.env.ts
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* @nudo:case "t" ()
|
|
34
|
-
*/
|
|
35
|
-
function getMagic() {
|
|
36
|
-
return MAGIC;
|
|
37
|
-
}
|
|
38
|
-
`;
|
|
39
|
-
const p = join(dir, "main.js");
|
|
40
|
-
writeFileSync(p, main, "utf-8");
|
|
41
|
-
const result = await analyzeFileAsync(p, main);
|
|
42
|
-
const fn = result.functions.find((f) => f.name === "getMagic");
|
|
43
|
-
expect(fn).toBeDefined();
|
|
44
|
-
expect(typeValueToString(fn!.cases[0].result)).toBe("99");
|
|
45
|
-
});
|
|
46
|
-
});
|
|
@@ -1,131 +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 setup(files: Record<string, string>) {
|
|
13
|
-
const dir = mkdtempSync(join(tmpdir(), "nudo-mod-"));
|
|
14
|
-
dirs.push(dir);
|
|
15
|
-
writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "mod", version: "1.0.0" }));
|
|
16
|
-
for (const [name, src] of Object.entries(files)) {
|
|
17
|
-
writeFileSync(join(dir, name), src);
|
|
18
|
-
}
|
|
19
|
-
return dir;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function goResult(dir: string, src: string) {
|
|
23
|
-
writeFileSync(join(dir, "index.js"), src);
|
|
24
|
-
clearBPathCache();
|
|
25
|
-
const r = analyzeFile(join(dir, "index.js"), src);
|
|
26
|
-
const f = r.functions.find((x) => x.name === "go");
|
|
27
|
-
return { result: f?.cases[0]?.result, diags: r.diagnostics };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
describe("B-path re-export / export *", () => {
|
|
31
|
-
it("export { add } from './core.js'", () => {
|
|
32
|
-
const dir = setup({
|
|
33
|
-
"core.js": `export function add(a, b) { return a + b; }
|
|
34
|
-
`,
|
|
35
|
-
"barrel.js": `export { add } from "./core.js";
|
|
36
|
-
`,
|
|
37
|
-
});
|
|
38
|
-
const { result } = goResult(
|
|
39
|
-
dir,
|
|
40
|
-
`import { add } from "./barrel.js";
|
|
41
|
-
/**
|
|
42
|
-
* @nudo:case "n" (2, 3)
|
|
43
|
-
*/
|
|
44
|
-
export function go(a, b) { return add(a, b); }
|
|
45
|
-
`,
|
|
46
|
-
);
|
|
47
|
-
expect(result).toMatchObject({ kind: "literal", value: 5 });
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("export { add as default } from './core.js'", () => {
|
|
51
|
-
const dir = setup({
|
|
52
|
-
"core.js": `export function add(a, b) { return a + b; }
|
|
53
|
-
`,
|
|
54
|
-
"barrel.js": `export { add as default } from "./core.js";
|
|
55
|
-
`,
|
|
56
|
-
});
|
|
57
|
-
const { result } = goResult(
|
|
58
|
-
dir,
|
|
59
|
-
`import add from "./barrel.js";
|
|
60
|
-
/**
|
|
61
|
-
* @nudo:case "n" (4, 6)
|
|
62
|
-
*/
|
|
63
|
-
export function go(a, b) { return add(a, b); }
|
|
64
|
-
`,
|
|
65
|
-
);
|
|
66
|
-
expect(result).toMatchObject({ kind: "literal", value: 10 });
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("export * from './core.js'", () => {
|
|
70
|
-
const dir = setup({
|
|
71
|
-
"core.js": `export function add(a, b) { return a + b; }
|
|
72
|
-
export function mul(a, b) { return a * b; }
|
|
73
|
-
`,
|
|
74
|
-
"barrel.js": `export * from "./core.js";
|
|
75
|
-
`,
|
|
76
|
-
});
|
|
77
|
-
const { result } = goResult(
|
|
78
|
-
dir,
|
|
79
|
-
`import { mul } from "./barrel.js";
|
|
80
|
-
/**
|
|
81
|
-
* @nudo:case "n" (3, 5)
|
|
82
|
-
*/
|
|
83
|
-
export function go(a, b) { return mul(a, b); }
|
|
84
|
-
`,
|
|
85
|
-
);
|
|
86
|
-
expect(result).toMatchObject({ kind: "literal", value: 15 });
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("class default export", () => {
|
|
90
|
-
const dir = setup({
|
|
91
|
-
"box.js": `export default class Box {
|
|
92
|
-
constructor(v) { this.v = v; }
|
|
93
|
-
get() { return this.v; }
|
|
94
|
-
}
|
|
95
|
-
`,
|
|
96
|
-
});
|
|
97
|
-
const { result } = goResult(
|
|
98
|
-
dir,
|
|
99
|
-
`import Box from "./box.js";
|
|
100
|
-
/**
|
|
101
|
-
* @nudo:case "v" (7)
|
|
102
|
-
*/
|
|
103
|
-
export function go(v) {
|
|
104
|
-
const b = new Box(v);
|
|
105
|
-
return b.get();
|
|
106
|
-
}
|
|
107
|
-
`,
|
|
108
|
-
);
|
|
109
|
-
expect(result).toMatchObject({ kind: "literal", value: 7 });
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it("async default export", () => {
|
|
113
|
-
const dir = setup({
|
|
114
|
-
"afn.js": `export default async function load(x) { return x + 1; }
|
|
115
|
-
`,
|
|
116
|
-
});
|
|
117
|
-
const { result } = goResult(
|
|
118
|
-
dir,
|
|
119
|
-
`import load from "./afn.js";
|
|
120
|
-
/**
|
|
121
|
-
* @nudo:case "x" (9)
|
|
122
|
-
*/
|
|
123
|
-
export async function go(x) { return await load(x); }
|
|
124
|
-
`,
|
|
125
|
-
);
|
|
126
|
-
expect(result).toMatchObject({
|
|
127
|
-
kind: "promise",
|
|
128
|
-
value: { kind: "literal", value: 10 },
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
});
|
|
@@ -1,52 +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:replace on B path", () => {
|
|
6
|
-
it("replaces only matching sub-expression", () => {
|
|
7
|
-
const source = `
|
|
8
|
-
/**
|
|
9
|
-
* @nudo:case "test" (5, 10)
|
|
10
|
-
*/
|
|
11
|
-
function compute(a, b) {
|
|
12
|
-
// @nudo:replace a T.literal(100)
|
|
13
|
-
const result = a + b;
|
|
14
|
-
return result;
|
|
15
|
-
}
|
|
16
|
-
`;
|
|
17
|
-
const result = analyzeFile("/test/replace.js", source);
|
|
18
|
-
expect(typeValueToString(result.functions[0].cases[0].result)).toBe("110");
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it("does not match partial identifiers", () => {
|
|
22
|
-
const source = `
|
|
23
|
-
/**
|
|
24
|
-
* @nudo:case "test" ()
|
|
25
|
-
*/
|
|
26
|
-
function test() {
|
|
27
|
-
const aa = 1;
|
|
28
|
-
const a = 2;
|
|
29
|
-
// @nudo:replace a T.literal(99)
|
|
30
|
-
const result = aa + a;
|
|
31
|
-
return result;
|
|
32
|
-
}
|
|
33
|
-
`;
|
|
34
|
-
const result = analyzeFile("/test/replace2.js", source);
|
|
35
|
-
expect(typeValueToString(result.functions[0].cases[0].result)).toBe("100");
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it("replaces call expression target", () => {
|
|
39
|
-
const source = `
|
|
40
|
-
/**
|
|
41
|
-
* @nudo:case "test" ("{\"id\":1}")
|
|
42
|
-
*/
|
|
43
|
-
function process(input) {
|
|
44
|
-
// @nudo:replace JSON.parse(input) T.object({ id: T.number })
|
|
45
|
-
const data = JSON.parse(input);
|
|
46
|
-
return data.id;
|
|
47
|
-
}
|
|
48
|
-
`;
|
|
49
|
-
const result = analyzeFile("/test/replace3.js", source);
|
|
50
|
-
expect(typeValueToString(result.functions[0].cases[0].result)).toBe("number");
|
|
51
|
-
});
|
|
52
|
-
});
|
|
@@ -1,66 +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 {
|
|
6
|
-
analyzeFile,
|
|
7
|
-
tryBPathCall,
|
|
8
|
-
isBPathCapable,
|
|
9
|
-
clearBPathCache,
|
|
10
|
-
} from "@nudojs/service";
|
|
11
|
-
import { typeValueToString, $lit, litValue } from "@nudojs/core";
|
|
12
|
-
|
|
13
|
-
const dirs: string[] = [];
|
|
14
|
-
afterAll(() => {
|
|
15
|
-
for (const d of dirs) rmSync(d, { recursive: true, force: true });
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
describe("B-path require", () => {
|
|
19
|
-
it("relative require injects Abs exports", () => {
|
|
20
|
-
clearBPathCache();
|
|
21
|
-
const dir = mkdtempSync(join(tmpdir(), "nudo-req-"));
|
|
22
|
-
dirs.push(dir);
|
|
23
|
-
writeFileSync(
|
|
24
|
-
join(dir, "math.js"),
|
|
25
|
-
`function double(x) { return x * 2; }\nexport { double };\n`,
|
|
26
|
-
);
|
|
27
|
-
const main = `
|
|
28
|
-
const math = require("./math.js");
|
|
29
|
-
/**
|
|
30
|
-
* @nudo:case "t" (21)
|
|
31
|
-
*/
|
|
32
|
-
export function go(n) {
|
|
33
|
-
return math.double(n);
|
|
34
|
-
}
|
|
35
|
-
`;
|
|
36
|
-
const p = join(dir, "main.js");
|
|
37
|
-
writeFileSync(p, main, "utf-8");
|
|
38
|
-
expect(isBPathCapable(main)).toBe(true);
|
|
39
|
-
const r = tryBPathCall(main, p, "go", [$lit(21)]);
|
|
40
|
-
expect(r).toBeDefined();
|
|
41
|
-
expect(litValue(r!)).toBe(42);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it("analyzeFile case works with require", () => {
|
|
45
|
-
clearBPathCache();
|
|
46
|
-
const dir = mkdtempSync(join(tmpdir(), "nudo-req2-"));
|
|
47
|
-
dirs.push(dir);
|
|
48
|
-
writeFileSync(join(dir, "util.js"), `export function inc(x) { return x + 1; }\n`);
|
|
49
|
-
const main = `
|
|
50
|
-
const util = require("./util.js");
|
|
51
|
-
/**
|
|
52
|
-
* @nudo:case "t" (1)
|
|
53
|
-
*/
|
|
54
|
-
function go(n) {
|
|
55
|
-
return util.inc(n);
|
|
56
|
-
}
|
|
57
|
-
`;
|
|
58
|
-
const p = join(dir, "main.js");
|
|
59
|
-
writeFileSync(p, main, "utf-8");
|
|
60
|
-
const result = analyzeFile(p, main);
|
|
61
|
-
const go = result.functions.find((f) => f.name === "go");
|
|
62
|
-
expect(go).toBeDefined();
|
|
63
|
-
const c = go!.cases.find((x) => x.name === "t");
|
|
64
|
-
expect(typeValueToString(c!.result)).toBe("2");
|
|
65
|
-
});
|
|
66
|
-
});
|