@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/src/directives.ts
DELETED
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
import type { Node, Comment } from "@babel/types";
|
|
2
|
-
import { type TypeValue, T } from "@nudojs/core";
|
|
3
|
-
|
|
4
|
-
export type CaseDirective = {
|
|
5
|
-
kind: "case";
|
|
6
|
-
name: string;
|
|
7
|
-
args: TypeValue[];
|
|
8
|
-
expected?: TypeValue;
|
|
9
|
-
commentLine?: number;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export type MockDirective = {
|
|
13
|
-
kind: "mock";
|
|
14
|
-
name: string;
|
|
15
|
-
expression?: string;
|
|
16
|
-
fromPath?: string;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export type PureDirective = {
|
|
20
|
-
kind: "pure";
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export type SkipDirective = {
|
|
24
|
-
kind: "skip";
|
|
25
|
-
returns?: TypeValue;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export type SampleDirective = {
|
|
29
|
-
kind: "sample";
|
|
30
|
-
count: number;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
export type ReturnsDirective = {
|
|
34
|
-
kind: "returns";
|
|
35
|
-
expected: TypeValue;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
export type Directive = CaseDirective | MockDirective | PureDirective | SkipDirective | SampleDirective | ReturnsDirective;
|
|
39
|
-
|
|
40
|
-
export type FunctionWithDirectives = {
|
|
41
|
-
node: Node;
|
|
42
|
-
name: string;
|
|
43
|
-
directives: Directive[];
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const CASE_NAME_REGEX = /@nudo:case\s+"([^"]+)"\s*\(/g;
|
|
47
|
-
const MOCK_INLINE_REGEX = /@nudo:mock\s+(\w+)\s*=\s*(.+)/g;
|
|
48
|
-
const MOCK_FROM_REGEX = /@nudo:mock\s+(\w+)\s+from\s+"([^"]+)"/g;
|
|
49
|
-
const PURE_REGEX = /@nudo:pure\b/g;
|
|
50
|
-
const SKIP_REGEX = /@nudo:skip(?:\s+(.+))?/g;
|
|
51
|
-
const SAMPLE_REGEX = /@nudo:sample\s+(\d+)/g;
|
|
52
|
-
const RETURNS_REGEX = /@nudo:returns\s*\(/g;
|
|
53
|
-
|
|
54
|
-
export function parseTypeValueExpr(expr: string): TypeValue {
|
|
55
|
-
const s = expr.trim();
|
|
56
|
-
|
|
57
|
-
if (s === "T.number") return T.number;
|
|
58
|
-
if (s === "T.string") return T.string;
|
|
59
|
-
if (s === "T.boolean") return T.boolean;
|
|
60
|
-
if (s === "T.unknown") return T.unknown;
|
|
61
|
-
if (s === "T.never") return T.never;
|
|
62
|
-
if (s === "T.null") return T.null;
|
|
63
|
-
if (s === "T.undefined") return T.undefined;
|
|
64
|
-
|
|
65
|
-
if (s === "true") return T.literal(true);
|
|
66
|
-
if (s === "false") return T.literal(false);
|
|
67
|
-
if (s === "null") return T.literal(null);
|
|
68
|
-
if (s === "undefined") return T.literal(undefined);
|
|
69
|
-
|
|
70
|
-
const literalMatch = s.match(/^T\.literal\((.+)\)$/);
|
|
71
|
-
if (literalMatch) {
|
|
72
|
-
return T.literal(parsePrimitiveValue(literalMatch[1].trim()));
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (s.startsWith("T.union(") && s.endsWith(")")) {
|
|
76
|
-
const inner = s.slice("T.union(".length, -1);
|
|
77
|
-
const args = splitTopLevelArgs(inner);
|
|
78
|
-
return T.union(...args.map(parseTypeValueExpr));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (s.startsWith("T.array(") && s.endsWith(")")) {
|
|
82
|
-
const inner = s.slice("T.array(".length, -1);
|
|
83
|
-
return T.array(parseTypeValueExpr(inner));
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (s.startsWith("T.tuple(") && s.endsWith(")")) {
|
|
87
|
-
const inner = s.slice("T.tuple(".length, -1).trim();
|
|
88
|
-
if (inner.startsWith("[") && inner.endsWith("]")) {
|
|
89
|
-
const elements = splitTopLevelArgs(inner.slice(1, -1));
|
|
90
|
-
return T.tuple(elements.map(parseTypeValueExpr));
|
|
91
|
-
}
|
|
92
|
-
return T.tuple([]);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (s.startsWith("T.object(") && s.endsWith(")")) {
|
|
96
|
-
const inner = s.slice("T.object(".length, -1).trim();
|
|
97
|
-
if (inner.startsWith("{") && inner.endsWith("}")) {
|
|
98
|
-
const content = inner.slice(1, -1).trim();
|
|
99
|
-
if (!content) return T.object({});
|
|
100
|
-
const entries = splitTopLevelArgs(content);
|
|
101
|
-
const props: Record<string, TypeValue> = {};
|
|
102
|
-
for (const entry of entries) {
|
|
103
|
-
const colonIdx = entry.indexOf(":");
|
|
104
|
-
if (colonIdx === -1) continue;
|
|
105
|
-
const key = entry.slice(0, colonIdx).trim();
|
|
106
|
-
const val = entry.slice(colonIdx + 1).trim();
|
|
107
|
-
props[key] = parseTypeValueExpr(val);
|
|
108
|
-
}
|
|
109
|
-
return T.object(props);
|
|
110
|
-
}
|
|
111
|
-
return T.object({});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (/^-?\d+(\.\d+)?$/.test(s)) return T.literal(Number(s));
|
|
115
|
-
|
|
116
|
-
if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) {
|
|
117
|
-
return T.literal(s.slice(1, -1));
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (s.startsWith("{") && s.endsWith("}")) {
|
|
121
|
-
const content = s.slice(1, -1).trim();
|
|
122
|
-
if (!content) return T.object({});
|
|
123
|
-
const entries = splitTopLevelArgs(content);
|
|
124
|
-
const props: Record<string, TypeValue> = {};
|
|
125
|
-
for (const entry of entries) {
|
|
126
|
-
const colonIdx = findTopLevelColon(entry);
|
|
127
|
-
if (colonIdx === -1) continue;
|
|
128
|
-
const key = entry.slice(0, colonIdx).trim().replace(/^["']|["']$/g, "");
|
|
129
|
-
const val = entry.slice(colonIdx + 1).trim();
|
|
130
|
-
props[key] = parseTypeValueExpr(val);
|
|
131
|
-
}
|
|
132
|
-
return T.object(props);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (s.startsWith("[") && s.endsWith("]")) {
|
|
136
|
-
const content = s.slice(1, -1).trim();
|
|
137
|
-
if (!content) return T.tuple([]);
|
|
138
|
-
const elements = splitTopLevelArgs(content);
|
|
139
|
-
return T.tuple(elements.map(parseTypeValueExpr));
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return T.unknown;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function parsePrimitiveValue(s: string): string | number | boolean | null | undefined {
|
|
146
|
-
if (s === "true") return true;
|
|
147
|
-
if (s === "false") return false;
|
|
148
|
-
if (s === "null") return null;
|
|
149
|
-
if (s === "undefined") return undefined;
|
|
150
|
-
if (/^-?\d+(\.\d+)?$/.test(s)) return Number(s);
|
|
151
|
-
if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) {
|
|
152
|
-
return s.slice(1, -1);
|
|
153
|
-
}
|
|
154
|
-
return s;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function splitTopLevelArgs(s: string): string[] {
|
|
158
|
-
const result: string[] = [];
|
|
159
|
-
let depth = 0;
|
|
160
|
-
let current = "";
|
|
161
|
-
for (const ch of s) {
|
|
162
|
-
if (ch === "(" || ch === "[" || ch === "{") depth++;
|
|
163
|
-
if (ch === ")" || ch === "]" || ch === "}") depth--;
|
|
164
|
-
if (ch === "," && depth === 0) {
|
|
165
|
-
result.push(current.trim());
|
|
166
|
-
current = "";
|
|
167
|
-
} else {
|
|
168
|
-
current += ch;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
if (current.trim()) result.push(current.trim());
|
|
172
|
-
return result;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function findTopLevelColon(s: string): number {
|
|
176
|
-
let depth = 0;
|
|
177
|
-
for (let i = 0; i < s.length; i++) {
|
|
178
|
-
const ch = s[i];
|
|
179
|
-
if (ch === "(" || ch === "[" || ch === "{") depth++;
|
|
180
|
-
if (ch === ")" || ch === "]" || ch === "}") depth--;
|
|
181
|
-
if (ch === ":" && depth === 0) return i;
|
|
182
|
-
}
|
|
183
|
-
return -1;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function extractBalancedParens(text: string, startIdx: number): string | null {
|
|
187
|
-
if (text[startIdx] !== "(") return null;
|
|
188
|
-
let depth = 0;
|
|
189
|
-
for (let i = startIdx; i < text.length; i++) {
|
|
190
|
-
if (text[i] === "(") depth++;
|
|
191
|
-
if (text[i] === ")") depth--;
|
|
192
|
-
if (depth === 0) return text.slice(startIdx + 1, i);
|
|
193
|
-
}
|
|
194
|
-
return null;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function parseDirectivesFromComments(comments: readonly Comment[]): Directive[] {
|
|
198
|
-
const directives: Directive[] = [];
|
|
199
|
-
for (const comment of comments) {
|
|
200
|
-
const text = comment.value;
|
|
201
|
-
|
|
202
|
-
MOCK_FROM_REGEX.lastIndex = 0;
|
|
203
|
-
let mockFromMatch: RegExpExecArray | null;
|
|
204
|
-
const mockFromRanges: [number, number][] = [];
|
|
205
|
-
while ((mockFromMatch = MOCK_FROM_REGEX.exec(text)) !== null) {
|
|
206
|
-
directives.push({
|
|
207
|
-
kind: "mock",
|
|
208
|
-
name: mockFromMatch[1],
|
|
209
|
-
fromPath: mockFromMatch[2],
|
|
210
|
-
});
|
|
211
|
-
mockFromRanges.push([mockFromMatch.index, mockFromMatch.index + mockFromMatch[0].length]);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
MOCK_INLINE_REGEX.lastIndex = 0;
|
|
215
|
-
let mockMatch: RegExpExecArray | null;
|
|
216
|
-
while ((mockMatch = MOCK_INLINE_REGEX.exec(text)) !== null) {
|
|
217
|
-
const inFromRange = mockFromRanges.some(
|
|
218
|
-
([s, e]) => mockMatch!.index >= s && mockMatch!.index < e,
|
|
219
|
-
);
|
|
220
|
-
if (inFromRange) continue;
|
|
221
|
-
directives.push({
|
|
222
|
-
kind: "mock",
|
|
223
|
-
name: mockMatch[1],
|
|
224
|
-
expression: mockMatch[2].trim(),
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
CASE_NAME_REGEX.lastIndex = 0;
|
|
229
|
-
let match: RegExpExecArray | null;
|
|
230
|
-
const commentStartLine = comment.loc?.start.line ?? 0;
|
|
231
|
-
while ((match = CASE_NAME_REGEX.exec(text)) !== null) {
|
|
232
|
-
const name = match[1];
|
|
233
|
-
const parenStart = match.index + match[0].length - 1;
|
|
234
|
-
const argsStr = extractBalancedParens(text, parenStart);
|
|
235
|
-
if (argsStr === null) continue;
|
|
236
|
-
const args = splitTopLevelArgs(argsStr).map(parseTypeValueExpr);
|
|
237
|
-
|
|
238
|
-
const afterParen = parenStart + argsStr.length + 2;
|
|
239
|
-
const restLine = text.slice(afterParen).split("\n")[0].trim();
|
|
240
|
-
const arrowMatch = restLine.match(/^=>\s*(.+)/);
|
|
241
|
-
const expected = arrowMatch ? parseTypeValueExpr(arrowMatch[1].trim()) : undefined;
|
|
242
|
-
|
|
243
|
-
const linesBeforeMatch = text.slice(0, match.index).split("\n").length - 1;
|
|
244
|
-
const commentLine = commentStartLine + linesBeforeMatch;
|
|
245
|
-
|
|
246
|
-
directives.push({ kind: "case", name, args, expected, commentLine });
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
PURE_REGEX.lastIndex = 0;
|
|
250
|
-
if (PURE_REGEX.test(text)) {
|
|
251
|
-
directives.push({ kind: "pure" });
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
SKIP_REGEX.lastIndex = 0;
|
|
255
|
-
let skipMatch: RegExpExecArray | null;
|
|
256
|
-
while ((skipMatch = SKIP_REGEX.exec(text)) !== null) {
|
|
257
|
-
const returnsExpr = skipMatch[1]?.trim();
|
|
258
|
-
directives.push({
|
|
259
|
-
kind: "skip",
|
|
260
|
-
returns: returnsExpr ? parseTypeValueExpr(returnsExpr) : undefined,
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
SAMPLE_REGEX.lastIndex = 0;
|
|
265
|
-
let sampleMatch: RegExpExecArray | null;
|
|
266
|
-
while ((sampleMatch = SAMPLE_REGEX.exec(text)) !== null) {
|
|
267
|
-
directives.push({ kind: "sample", count: Number(sampleMatch[1]) });
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
RETURNS_REGEX.lastIndex = 0;
|
|
271
|
-
let returnsMatch: RegExpExecArray | null;
|
|
272
|
-
while ((returnsMatch = RETURNS_REGEX.exec(text)) !== null) {
|
|
273
|
-
const parenStart = returnsMatch.index + returnsMatch[0].length - 1;
|
|
274
|
-
const argsStr = extractBalancedParens(text, parenStart);
|
|
275
|
-
if (argsStr === null) continue;
|
|
276
|
-
directives.push({ kind: "returns", expected: parseTypeValueExpr(argsStr) });
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
return directives;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function getFunctionName(node: Node): string {
|
|
283
|
-
if (node.type === "FunctionDeclaration" && node.id) return node.id.name;
|
|
284
|
-
if (node.type === "ExportDefaultDeclaration" && node.declaration.type === "FunctionDeclaration" && node.declaration.id) {
|
|
285
|
-
return node.declaration.id.name;
|
|
286
|
-
}
|
|
287
|
-
if (node.type === "VariableDeclaration") {
|
|
288
|
-
const decl = node.declarations[0];
|
|
289
|
-
if (decl.id.type === "Identifier") return decl.id.name;
|
|
290
|
-
}
|
|
291
|
-
return "<anonymous>";
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
export function extractDirectives(ast: Node): FunctionWithDirectives[] {
|
|
295
|
-
const results: FunctionWithDirectives[] = [];
|
|
296
|
-
|
|
297
|
-
if (ast.type !== "File") return results;
|
|
298
|
-
const body = ast.program.body;
|
|
299
|
-
|
|
300
|
-
for (const stmt of body) {
|
|
301
|
-
const leadingComments = stmt.leadingComments;
|
|
302
|
-
if (!leadingComments || leadingComments.length === 0) continue;
|
|
303
|
-
|
|
304
|
-
const directives = parseDirectivesFromComments(leadingComments);
|
|
305
|
-
if (directives.length === 0) continue;
|
|
306
|
-
|
|
307
|
-
const name = getFunctionName(stmt);
|
|
308
|
-
results.push({ node: stmt, name, directives });
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
return results;
|
|
312
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export { parse } from "./parse.ts";
|
|
2
|
-
export {
|
|
3
|
-
type Directive,
|
|
4
|
-
type CaseDirective,
|
|
5
|
-
type MockDirective,
|
|
6
|
-
type PureDirective,
|
|
7
|
-
type SkipDirective,
|
|
8
|
-
type SampleDirective,
|
|
9
|
-
type ReturnsDirective,
|
|
10
|
-
type FunctionWithDirectives,
|
|
11
|
-
extractDirectives,
|
|
12
|
-
parseTypeValueExpr,
|
|
13
|
-
} from "./directives.ts";
|
package/src/parse.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { parse as babelParse } from "@babel/parser";
|
|
2
|
-
import type { File } from "@babel/types";
|
|
3
|
-
|
|
4
|
-
export function parse(source: string): File {
|
|
5
|
-
return babelParse(source, {
|
|
6
|
-
sourceType: "module",
|
|
7
|
-
plugins: ["typescript", "jsx"],
|
|
8
|
-
attachComment: true,
|
|
9
|
-
});
|
|
10
|
-
}
|