@nudojs/parser 0.2.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.
- package/README.md +25 -0
- package/dist/index.d.ts +51 -7
- package/dist/index.js +337 -14603
- package/package.json +23 -4
- package/CHANGELOG.md +0 -27
- package/src/__tests__/directives-phase2.test.ts +0 -113
- package/src/__tests__/directives-phase3.test.ts +0 -93
- package/src/__tests__/directives.test.ts +0 -129
- package/src/directives.ts +0 -312
- package/src/index.ts +0 -13
- package/src/parse.ts +0 -10
- package/tsconfig.json +0 -9
package/package.json
CHANGED
|
@@ -1,23 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nudojs/parser",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "Babel-based parser and directive extraction for Nudo",
|
|
4
5
|
"type": "module",
|
|
5
|
-
"
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"nudo",
|
|
9
|
+
"type-inference",
|
|
10
|
+
"javascript",
|
|
11
|
+
"parser",
|
|
12
|
+
"babel",
|
|
13
|
+
"directives"
|
|
14
|
+
],
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
6
17
|
"exports": {
|
|
7
|
-
".":
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
}
|
|
8
22
|
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
9
26
|
"repository": {
|
|
10
27
|
"type": "git",
|
|
11
28
|
"url": "git+https://github.com/nudojs/nudo.git",
|
|
12
29
|
"directory": "packages/parser"
|
|
13
30
|
},
|
|
31
|
+
"homepage": "https://github.com/nudojs/nudo/tree/main/packages/parser",
|
|
32
|
+
"bugs": "https://github.com/nudojs/nudo/issues",
|
|
14
33
|
"publishConfig": {
|
|
15
34
|
"access": "public",
|
|
16
35
|
"provenance": true,
|
|
17
36
|
"registry": "https://registry.npmjs.org"
|
|
18
37
|
},
|
|
19
38
|
"dependencies": {
|
|
20
|
-
"@nudojs/core": "0.
|
|
39
|
+
"@nudojs/core": "0.3.1"
|
|
21
40
|
},
|
|
22
41
|
"scripts": {
|
|
23
42
|
"build": "tsup src/index.ts --format esm --dts"
|
package/CHANGELOG.md
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# @nudojs/parser
|
|
2
|
-
|
|
3
|
-
## 0.2.0
|
|
4
|
-
|
|
5
|
-
### Minor Changes
|
|
6
|
-
|
|
7
|
-
- 6c38283: docs and ci
|
|
8
|
-
- 9f7f819: fix pkg info
|
|
9
|
-
- c175f71: version
|
|
10
|
-
|
|
11
|
-
### Patch Changes
|
|
12
|
-
|
|
13
|
-
- Updated dependencies [6c38283]
|
|
14
|
-
- Updated dependencies [9f7f819]
|
|
15
|
-
- Updated dependencies [c175f71]
|
|
16
|
-
- @nudojs/core@0.2.0
|
|
17
|
-
|
|
18
|
-
## 0.1.0
|
|
19
|
-
|
|
20
|
-
### Minor Changes
|
|
21
|
-
|
|
22
|
-
- Conceptual design and basic implementation.
|
|
23
|
-
|
|
24
|
-
### Patch Changes
|
|
25
|
-
|
|
26
|
-
- Updated dependencies
|
|
27
|
-
- @nudojs/core@0.1.0
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { parse } from "../parse.ts";
|
|
3
|
-
import { extractDirectives, parseTypeValueExpr } from "../directives.ts";
|
|
4
|
-
import { T, typeValueEquals, typeValueToString } from "@nudojs/core";
|
|
5
|
-
|
|
6
|
-
describe("parseTypeValueExpr: Phase 2 enhancements", () => {
|
|
7
|
-
it("parses T.array(T.number)", () => {
|
|
8
|
-
const result = parseTypeValueExpr("T.array(T.number)");
|
|
9
|
-
expect(result.kind).toBe("array");
|
|
10
|
-
if (result.kind === "array") {
|
|
11
|
-
expect(typeValueEquals(result.element, T.number)).toBe(true);
|
|
12
|
-
}
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it("parses T.tuple([T.number, T.string])", () => {
|
|
16
|
-
const result = parseTypeValueExpr("T.tuple([T.number, T.string])");
|
|
17
|
-
expect(result.kind).toBe("tuple");
|
|
18
|
-
if (result.kind === "tuple") {
|
|
19
|
-
expect(result.elements).toHaveLength(2);
|
|
20
|
-
expect(typeValueEquals(result.elements[0], T.number)).toBe(true);
|
|
21
|
-
expect(typeValueEquals(result.elements[1], T.string)).toBe(true);
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("parses T.object({ x: T.number, y: T.string })", () => {
|
|
26
|
-
const result = parseTypeValueExpr("T.object({ x: T.number, y: T.string })");
|
|
27
|
-
expect(result.kind).toBe("object");
|
|
28
|
-
if (result.kind === "object") {
|
|
29
|
-
expect(typeValueEquals(result.properties.x, T.number)).toBe(true);
|
|
30
|
-
expect(typeValueEquals(result.properties.y, T.string)).toBe(true);
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it("parses nested T.object with T.array", () => {
|
|
35
|
-
const result = parseTypeValueExpr("T.object({ items: T.array(T.number) })");
|
|
36
|
-
expect(result.kind).toBe("object");
|
|
37
|
-
if (result.kind === "object") {
|
|
38
|
-
expect(result.properties.items.kind).toBe("array");
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it("parses T.object({})", () => {
|
|
43
|
-
const result = parseTypeValueExpr("T.object({})");
|
|
44
|
-
expect(result.kind).toBe("object");
|
|
45
|
-
if (result.kind === "object") {
|
|
46
|
-
expect(Object.keys(result.properties)).toHaveLength(0);
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("parses T.tuple([])", () => {
|
|
51
|
-
const result = parseTypeValueExpr("T.tuple([])");
|
|
52
|
-
expect(result.kind).toBe("tuple");
|
|
53
|
-
if (result.kind === "tuple") {
|
|
54
|
-
expect(result.elements).toHaveLength(0);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
describe("extractDirectives: @nudo:mock", () => {
|
|
60
|
-
it("extracts inline mock directive", () => {
|
|
61
|
-
const source = `
|
|
62
|
-
/**
|
|
63
|
-
* @nudo:mock fetch = T.unknown
|
|
64
|
-
* @nudo:case "test" (1)
|
|
65
|
-
*/
|
|
66
|
-
function foo(x) { return x; }
|
|
67
|
-
`;
|
|
68
|
-
const ast = parse(source);
|
|
69
|
-
const results = extractDirectives(ast);
|
|
70
|
-
expect(results).toHaveLength(1);
|
|
71
|
-
const mocks = results[0].directives.filter((d) => d.kind === "mock");
|
|
72
|
-
expect(mocks).toHaveLength(1);
|
|
73
|
-
expect(mocks[0].kind).toBe("mock");
|
|
74
|
-
if (mocks[0].kind === "mock") {
|
|
75
|
-
expect(mocks[0].name).toBe("fetch");
|
|
76
|
-
expect(mocks[0].expression).toBe("T.unknown");
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("extracts mock from file directive", () => {
|
|
81
|
-
const source = `
|
|
82
|
-
/**
|
|
83
|
-
* @nudo:mock utils from "./utils.mock.js"
|
|
84
|
-
* @nudo:case "test" (1)
|
|
85
|
-
*/
|
|
86
|
-
function foo(x) { return x; }
|
|
87
|
-
`;
|
|
88
|
-
const ast = parse(source);
|
|
89
|
-
const results = extractDirectives(ast);
|
|
90
|
-
const mocks = results[0].directives.filter((d) => d.kind === "mock");
|
|
91
|
-
expect(mocks).toHaveLength(1);
|
|
92
|
-
if (mocks[0].kind === "mock") {
|
|
93
|
-
expect(mocks[0].name).toBe("utils");
|
|
94
|
-
expect(mocks[0].fromPath).toBe("./utils.mock.js");
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it("extracts both mock and case directives", () => {
|
|
99
|
-
const source = `
|
|
100
|
-
/**
|
|
101
|
-
* @nudo:mock helper = T.number
|
|
102
|
-
* @nudo:case "test" (T.number)
|
|
103
|
-
*/
|
|
104
|
-
function foo(x) { return x; }
|
|
105
|
-
`;
|
|
106
|
-
const ast = parse(source);
|
|
107
|
-
const results = extractDirectives(ast);
|
|
108
|
-
const mocks = results[0].directives.filter((d) => d.kind === "mock");
|
|
109
|
-
const cases = results[0].directives.filter((d) => d.kind === "case");
|
|
110
|
-
expect(mocks).toHaveLength(1);
|
|
111
|
-
expect(cases).toHaveLength(1);
|
|
112
|
-
});
|
|
113
|
-
});
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { T } from "@nudojs/core";
|
|
3
|
-
import { extractDirectives } from "../directives.ts";
|
|
4
|
-
import { parse } from "../parse.ts";
|
|
5
|
-
|
|
6
|
-
function getDirectives(source: string) {
|
|
7
|
-
const ast = parse(source);
|
|
8
|
-
return extractDirectives(ast);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
describe("@nudo:pure directive", () => {
|
|
12
|
-
it("parses @nudo:pure", () => {
|
|
13
|
-
const fns = getDirectives(`
|
|
14
|
-
/**
|
|
15
|
-
* @nudo:pure
|
|
16
|
-
* @nudo:case "test" (1, 2)
|
|
17
|
-
*/
|
|
18
|
-
function add(a, b) { return a + b; }
|
|
19
|
-
`);
|
|
20
|
-
expect(fns.length).toBe(1);
|
|
21
|
-
const directives = fns[0].directives;
|
|
22
|
-
expect(directives.some((d) => d.kind === "pure")).toBe(true);
|
|
23
|
-
expect(directives.some((d) => d.kind === "case")).toBe(true);
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe("@nudo:skip directive", () => {
|
|
28
|
-
it("parses @nudo:skip without return type", () => {
|
|
29
|
-
const fns = getDirectives(`
|
|
30
|
-
/**
|
|
31
|
-
* @nudo:skip
|
|
32
|
-
*/
|
|
33
|
-
function external() {}
|
|
34
|
-
`);
|
|
35
|
-
expect(fns.length).toBe(1);
|
|
36
|
-
const skip = fns[0].directives.find((d) => d.kind === "skip");
|
|
37
|
-
expect(skip).toBeDefined();
|
|
38
|
-
if (skip && skip.kind === "skip") {
|
|
39
|
-
expect(skip.returns).toBeUndefined();
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("parses @nudo:skip with return type", () => {
|
|
44
|
-
const fns = getDirectives(`
|
|
45
|
-
/**
|
|
46
|
-
* @nudo:skip T.number
|
|
47
|
-
*/
|
|
48
|
-
function external() {}
|
|
49
|
-
`);
|
|
50
|
-
expect(fns.length).toBe(1);
|
|
51
|
-
const skip = fns[0].directives.find((d) => d.kind === "skip");
|
|
52
|
-
expect(skip).toBeDefined();
|
|
53
|
-
if (skip && skip.kind === "skip") {
|
|
54
|
-
expect(skip.returns).toEqual(T.number);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
describe("@nudo:sample directive", () => {
|
|
60
|
-
it("parses @nudo:sample with count", () => {
|
|
61
|
-
const fns = getDirectives(`
|
|
62
|
-
/**
|
|
63
|
-
* @nudo:sample 5
|
|
64
|
-
* @nudo:case "test" (T.number)
|
|
65
|
-
*/
|
|
66
|
-
function loop(n) { return n; }
|
|
67
|
-
`);
|
|
68
|
-
expect(fns.length).toBe(1);
|
|
69
|
-
const sample = fns[0].directives.find((d) => d.kind === "sample");
|
|
70
|
-
expect(sample).toBeDefined();
|
|
71
|
-
if (sample && sample.kind === "sample") {
|
|
72
|
-
expect(sample.count).toBe(5);
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
describe("@nudo:returns directive", () => {
|
|
78
|
-
it("parses @nudo:returns with type", () => {
|
|
79
|
-
const fns = getDirectives(`
|
|
80
|
-
/**
|
|
81
|
-
* @nudo:returns (T.number)
|
|
82
|
-
* @nudo:case "test" (1, 2)
|
|
83
|
-
*/
|
|
84
|
-
function add(a, b) { return a + b; }
|
|
85
|
-
`);
|
|
86
|
-
expect(fns.length).toBe(1);
|
|
87
|
-
const returns = fns[0].directives.find((d) => d.kind === "returns");
|
|
88
|
-
expect(returns).toBeDefined();
|
|
89
|
-
if (returns && returns.kind === "returns") {
|
|
90
|
-
expect(returns.expected).toEqual(T.number);
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
});
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { parse } from "../parse.ts";
|
|
3
|
-
import { extractDirectives, parseTypeValueExpr } from "../directives.ts";
|
|
4
|
-
import { T, typeValueEquals } from "@nudojs/core";
|
|
5
|
-
|
|
6
|
-
describe("parseTypeValueExpr", () => {
|
|
7
|
-
it("parses T.number", () => {
|
|
8
|
-
expect(typeValueEquals(parseTypeValueExpr("T.number"), T.number)).toBe(true);
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it("parses T.string", () => {
|
|
12
|
-
expect(typeValueEquals(parseTypeValueExpr("T.string"), T.string)).toBe(true);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it("parses T.boolean", () => {
|
|
16
|
-
expect(typeValueEquals(parseTypeValueExpr("T.boolean"), T.boolean)).toBe(true);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it("parses numeric literals", () => {
|
|
20
|
-
expect(typeValueEquals(parseTypeValueExpr("42"), T.literal(42))).toBe(true);
|
|
21
|
-
expect(typeValueEquals(parseTypeValueExpr("-3"), T.literal(-3))).toBe(true);
|
|
22
|
-
expect(typeValueEquals(parseTypeValueExpr("1.5"), T.literal(1.5))).toBe(true);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("parses string literals", () => {
|
|
26
|
-
expect(typeValueEquals(parseTypeValueExpr('"hello"'), T.literal("hello"))).toBe(true);
|
|
27
|
-
expect(typeValueEquals(parseTypeValueExpr("'world'"), T.literal("world"))).toBe(true);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("parses boolean literals", () => {
|
|
31
|
-
expect(typeValueEquals(parseTypeValueExpr("true"), T.literal(true))).toBe(true);
|
|
32
|
-
expect(typeValueEquals(parseTypeValueExpr("false"), T.literal(false))).toBe(true);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("parses null and undefined", () => {
|
|
36
|
-
expect(typeValueEquals(parseTypeValueExpr("null"), T.literal(null))).toBe(true);
|
|
37
|
-
expect(typeValueEquals(parseTypeValueExpr("undefined"), T.literal(undefined))).toBe(true);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it("parses T.literal(...)", () => {
|
|
41
|
-
expect(typeValueEquals(parseTypeValueExpr("T.literal(42)"), T.literal(42))).toBe(true);
|
|
42
|
-
expect(typeValueEquals(parseTypeValueExpr('T.literal("hi")'), T.literal("hi"))).toBe(true);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it("parses T.union(...)", () => {
|
|
46
|
-
const result = parseTypeValueExpr("T.union(T.number, T.string)");
|
|
47
|
-
const expected = T.union(T.number, T.string);
|
|
48
|
-
expect(typeValueEquals(result, expected)).toBe(true);
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
describe("extractDirectives", () => {
|
|
53
|
-
it("extracts @nudo:case directives from function", () => {
|
|
54
|
-
const source = `
|
|
55
|
-
/**
|
|
56
|
-
* @nudo:case "concrete" (1, 2)
|
|
57
|
-
* @nudo:case "symbolic" (T.number, T.number)
|
|
58
|
-
*/
|
|
59
|
-
function calc(a, b) {
|
|
60
|
-
return a + b;
|
|
61
|
-
}
|
|
62
|
-
`;
|
|
63
|
-
const ast = parse(source);
|
|
64
|
-
const results = extractDirectives(ast);
|
|
65
|
-
expect(results).toHaveLength(1);
|
|
66
|
-
expect(results[0].name).toBe("calc");
|
|
67
|
-
expect(results[0].directives).toHaveLength(2);
|
|
68
|
-
|
|
69
|
-
const d0 = results[0].directives[0];
|
|
70
|
-
expect(d0.kind).toBe("case");
|
|
71
|
-
expect(d0.name).toBe("concrete");
|
|
72
|
-
expect(d0.args).toHaveLength(2);
|
|
73
|
-
expect(typeValueEquals(d0.args[0], T.literal(1))).toBe(true);
|
|
74
|
-
expect(typeValueEquals(d0.args[1], T.literal(2))).toBe(true);
|
|
75
|
-
|
|
76
|
-
const d1 = results[0].directives[1];
|
|
77
|
-
expect(d1.name).toBe("symbolic");
|
|
78
|
-
expect(typeValueEquals(d1.args[0], T.number)).toBe(true);
|
|
79
|
-
expect(typeValueEquals(d1.args[1], T.number)).toBe(true);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it("extracts from multiple functions", () => {
|
|
83
|
-
const source = `
|
|
84
|
-
/**
|
|
85
|
-
* @nudo:case "test" (1)
|
|
86
|
-
*/
|
|
87
|
-
function foo(x) { return x; }
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* @nudo:case "test2" ("hello")
|
|
91
|
-
*/
|
|
92
|
-
function bar(s) { return s; }
|
|
93
|
-
`;
|
|
94
|
-
const ast = parse(source);
|
|
95
|
-
const results = extractDirectives(ast);
|
|
96
|
-
expect(results).toHaveLength(2);
|
|
97
|
-
expect(results[0].name).toBe("foo");
|
|
98
|
-
expect(results[1].name).toBe("bar");
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("ignores functions without directives", () => {
|
|
102
|
-
const source = `
|
|
103
|
-
function noDirective(x) { return x; }
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* @nudo:case "test" (T.number)
|
|
107
|
-
*/
|
|
108
|
-
function withDirective(x) { return x + 1; }
|
|
109
|
-
`;
|
|
110
|
-
const ast = parse(source);
|
|
111
|
-
const results = extractDirectives(ast);
|
|
112
|
-
expect(results).toHaveLength(1);
|
|
113
|
-
expect(results[0].name).toBe("withDirective");
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it("handles string arguments in case", () => {
|
|
117
|
-
const source = `
|
|
118
|
-
/**
|
|
119
|
-
* @nudo:case "string test" ("hello", "world")
|
|
120
|
-
*/
|
|
121
|
-
function greet(a, b) { return a + b; }
|
|
122
|
-
`;
|
|
123
|
-
const ast = parse(source);
|
|
124
|
-
const results = extractDirectives(ast);
|
|
125
|
-
expect(results[0].directives[0].args).toHaveLength(2);
|
|
126
|
-
expect(typeValueEquals(results[0].directives[0].args[0], T.literal("hello"))).toBe(true);
|
|
127
|
-
expect(typeValueEquals(results[0].directives[0].args[1], T.literal("world"))).toBe(true);
|
|
128
|
-
});
|
|
129
|
-
});
|