@nudojs/service 0.1.0

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.
@@ -0,0 +1,137 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { resolve } from "node:path";
3
+ import { T, typeValueToString } from "@nudojs/core";
4
+ import { analyzeFile, getTypeAtPosition, getCompletionsAtPosition } from "../analyzer.ts";
5
+
6
+ const FIXTURE_PATH = resolve(import.meta.dirname, "fixtures", "sample.js");
7
+
8
+ const SAMPLE_SOURCE = `
9
+ /**
10
+ * @nudo:case "concrete" (1, 2)
11
+ * @nudo:case "symbolic" (T.number, T.number)
12
+ */
13
+ function add(a, b) {
14
+ return a + b;
15
+ }
16
+
17
+ /**
18
+ * @nudo:case "test" ({ name: "Alice", age: 30 })
19
+ */
20
+ function greet({ name, age }) {
21
+ return name;
22
+ }
23
+ `;
24
+
25
+ const THROWS_SOURCE = `
26
+ /**
27
+ * @nudo:case "valid" (10)
28
+ * @nudo:case "negative" (-1)
29
+ */
30
+ function safeSqrt(x) {
31
+ if (x < 0) {
32
+ throw new RangeError("negative input");
33
+ }
34
+ return x;
35
+ }
36
+ `;
37
+
38
+ const OBJ_SOURCE = `
39
+ const obj = { x: 1, y: "hello", z: true };
40
+
41
+ /**
42
+ * @nudo:case "test" (T.number)
43
+ */
44
+ function identity(x) {
45
+ return x;
46
+ }
47
+ `;
48
+
49
+ describe("analyzeFile", () => {
50
+ it("analyzes functions with @nudo:case directives", () => {
51
+ const result = analyzeFile("/test/sample.js", SAMPLE_SOURCE);
52
+ expect(result.functions).toHaveLength(2);
53
+
54
+ const addFn = result.functions[0];
55
+ expect(addFn.name).toBe("add");
56
+ expect(addFn.cases).toHaveLength(2);
57
+ expect(addFn.cases[0].name).toBe("concrete");
58
+ expect(typeValueToString(addFn.cases[0].result)).toBe("3");
59
+ expect(addFn.cases[1].name).toBe("symbolic");
60
+ expect(typeValueToString(addFn.cases[1].result)).toBe("number");
61
+ });
62
+
63
+ it("provides combined type for multiple cases", () => {
64
+ const result = analyzeFile("/test/sample.js", SAMPLE_SOURCE);
65
+ const addFn = result.functions[0];
66
+ expect(addFn.combined).toBeDefined();
67
+ expect(typeValueToString(addFn.combined!)).toBe("3 | number");
68
+ });
69
+
70
+ it("reports throws as diagnostics", () => {
71
+ const result = analyzeFile("/test/throws.js", THROWS_SOURCE);
72
+ expect(result.functions).toHaveLength(1);
73
+ const fn = result.functions[0];
74
+ expect(fn.cases).toHaveLength(2);
75
+
76
+ const negativeCaseThrows = fn.cases[1].throws;
77
+ expect(negativeCaseThrows.kind).not.toBe("never");
78
+ });
79
+
80
+ it("returns source locations for functions", () => {
81
+ const result = analyzeFile("/test/sample.js", SAMPLE_SOURCE);
82
+ const addFn = result.functions[0];
83
+ expect(addFn.loc.start.line).toBeGreaterThan(0);
84
+ expect(addFn.loc.end.line).toBeGreaterThan(addFn.loc.start.line);
85
+ });
86
+
87
+ it("collects top-level bindings", () => {
88
+ const result = analyzeFile("/test/obj.js", OBJ_SOURCE);
89
+ expect(result.bindings.has("obj")).toBe(true);
90
+ const objBinding = result.bindings.get("obj")!;
91
+ expect(objBinding.type.kind).toBe("object");
92
+ });
93
+ });
94
+
95
+ describe("getTypeAtPosition", () => {
96
+ it("returns type for identifier at position", () => {
97
+ const source = `const x = 42;\nconst y = x;\n`;
98
+ const tv = getTypeAtPosition("/test/pos.js", source, 1, 6);
99
+ expect(tv).not.toBeNull();
100
+ });
101
+
102
+ it("returns null for empty position", () => {
103
+ const source = `\n\n\n`;
104
+ const tv = getTypeAtPosition("/test/empty.js", source, 2, 0);
105
+ expect(tv).toBeNull();
106
+ });
107
+ });
108
+
109
+ describe("getCompletionsAtPosition", () => {
110
+ it("returns variable completions without dot trigger", () => {
111
+ const source = `const x = 42;\nfunction add(a, b) { return a + b; }\n`;
112
+ const completions = getCompletionsAtPosition("/test/comp.js", source, 2, 0);
113
+ expect(completions.length).toBeGreaterThan(0);
114
+ const names = completions.map((c) => c.label);
115
+ expect(names).toContain("x");
116
+ expect(names).toContain("add");
117
+ });
118
+
119
+ it("returns property completions for object after dot", () => {
120
+ const source = `const obj = { x: 1, y: "hello" };\nobj.x;\n`;
121
+ const completions = getCompletionsAtPosition("/test/dot.js", source, 2, 4);
122
+ expect(completions.length).toBeGreaterThan(0);
123
+ const names = completions.map((c) => c.label);
124
+ expect(names).toContain("x");
125
+ expect(names).toContain("y");
126
+ });
127
+
128
+ it("returns array method completions after dot", () => {
129
+ const source = `const arr = [1, 2, 3];\narr.map;\n`;
130
+ const completions = getCompletionsAtPosition("/test/arr.js", source, 2, 4);
131
+ const names = completions.map((c) => c.label);
132
+ expect(names).toContain("map");
133
+ expect(names).toContain("filter");
134
+ expect(names).toContain("reduce");
135
+ expect(names).toContain("length");
136
+ });
137
+ });
@@ -0,0 +1,98 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { T } from "@nudojs/core";
3
+ import { typeValueToTSType, generateDts } from "../dts-generator.ts";
4
+ import { analyzeFile } from "../analyzer.ts";
5
+
6
+ describe("typeValueToTSType", () => {
7
+ it("converts literal number", () => {
8
+ expect(typeValueToTSType(T.literal(42))).toBe("42");
9
+ });
10
+
11
+ it("converts literal string", () => {
12
+ expect(typeValueToTSType(T.literal("hello"))).toBe('"hello"');
13
+ });
14
+
15
+ it("converts literal boolean", () => {
16
+ expect(typeValueToTSType(T.literal(true))).toBe("true");
17
+ });
18
+
19
+ it("converts null and undefined", () => {
20
+ expect(typeValueToTSType(T.null)).toBe("null");
21
+ expect(typeValueToTSType(T.undefined)).toBe("undefined");
22
+ });
23
+
24
+ it("converts primitive types", () => {
25
+ expect(typeValueToTSType(T.number)).toBe("number");
26
+ expect(typeValueToTSType(T.string)).toBe("string");
27
+ expect(typeValueToTSType(T.boolean)).toBe("boolean");
28
+ });
29
+
30
+ it("converts object type", () => {
31
+ const obj = T.object({ x: T.number, y: T.string });
32
+ expect(typeValueToTSType(obj)).toBe("{ x: number; y: string }");
33
+ });
34
+
35
+ it("converts array type", () => {
36
+ expect(typeValueToTSType(T.array(T.number))).toBe("number[]");
37
+ });
38
+
39
+ it("converts union array type with parens", () => {
40
+ expect(typeValueToTSType(T.array(T.union(T.number, T.string)))).toBe("(number | string)[]");
41
+ });
42
+
43
+ it("converts tuple type", () => {
44
+ expect(typeValueToTSType(T.tuple([T.number, T.string]))).toBe("[number, string]");
45
+ });
46
+
47
+ it("converts promise type", () => {
48
+ expect(typeValueToTSType(T.promise(T.number))).toBe("Promise<number>");
49
+ });
50
+
51
+ it("converts instance type", () => {
52
+ expect(typeValueToTSType(T.instanceOf("Error"))).toBe("Error");
53
+ });
54
+
55
+ it("converts union type", () => {
56
+ expect(typeValueToTSType(T.union(T.number, T.string))).toBe("number | string");
57
+ });
58
+
59
+ it("converts never and unknown", () => {
60
+ expect(typeValueToTSType(T.never)).toBe("never");
61
+ expect(typeValueToTSType(T.unknown)).toBe("unknown");
62
+ });
63
+ });
64
+
65
+ describe("generateDts", () => {
66
+ it("generates .d.ts from analysis result", () => {
67
+ const source = `
68
+ /**
69
+ * @nudo:case "concrete" (1, 2)
70
+ * @nudo:case "symbolic" (T.number, T.number)
71
+ */
72
+ function add(a, b) {
73
+ return a + b;
74
+ }
75
+ `;
76
+ const result = analyzeFile("/test/gen.js", source);
77
+ const dts = generateDts(result);
78
+ expect(dts).toContain("export declare function add");
79
+ expect(dts).toContain("): 3;");
80
+ expect(dts).toContain("): number;");
81
+ });
82
+
83
+ it("generates single overload for single case", () => {
84
+ const source = `
85
+ /**
86
+ * @nudo:case "test" (T.number)
87
+ */
88
+ function identity(x) {
89
+ return x;
90
+ }
91
+ `;
92
+ const result = analyzeFile("/test/single.js", source);
93
+ const dts = generateDts(result);
94
+ const lines = dts.trim().split("\n");
95
+ expect(lines).toHaveLength(1);
96
+ expect(lines[0]).toContain("export declare function identity");
97
+ });
98
+ });