@nudojs/parser 0.3.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/package.json +11 -4
- package/CHANGELOG.md +0 -43
- package/src/__tests__/directives-inline.test.ts +0 -95
- package/src/__tests__/directives-phase2.test.ts +0 -113
- package/src/__tests__/directives-phase3.test.ts +0 -75
- package/src/__tests__/directives.test.ts +0 -411
- package/src/__tests__/mock-directive.test.ts +0 -115
- package/src/__tests__/sinon-directive.test.ts +0 -200
- package/src/directives.ts +0 -754
- package/src/index.ts +0 -23
- package/src/parse.ts +0 -15
- package/tsconfig.json +0 -9
|
@@ -1,411 +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
|
-
it("parses arrow function literal with parenthesized params", () => {
|
|
52
|
-
const result = parseTypeValueExpr("(x) => x * 2");
|
|
53
|
-
expect(result.kind).toBe("function");
|
|
54
|
-
if (result.kind === "function") {
|
|
55
|
-
expect(result.params).toEqual(["x"]);
|
|
56
|
-
expect(result.body.type).toBe("BinaryExpression");
|
|
57
|
-
expect(result.closure).toBeDefined();
|
|
58
|
-
expect((result as any)._paramPatterns).toBeDefined();
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it("parses arrow function literal without parens", () => {
|
|
63
|
-
const result = parseTypeValueExpr("x => x + 1");
|
|
64
|
-
expect(result.kind).toBe("function");
|
|
65
|
-
if (result.kind === "function") {
|
|
66
|
-
expect(result.params).toEqual(["x"]);
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it("parses arrow function literal with multiple params", () => {
|
|
71
|
-
const result = parseTypeValueExpr("(a, b) => a + b");
|
|
72
|
-
expect(result.kind).toBe("function");
|
|
73
|
-
if (result.kind === "function") {
|
|
74
|
-
expect(result.params).toEqual(["a", "b"]);
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it("parses function expression literal", () => {
|
|
79
|
-
const result = parseTypeValueExpr("function(x) { return x * 2; }");
|
|
80
|
-
expect(result.kind).toBe("function");
|
|
81
|
-
if (result.kind === "function") {
|
|
82
|
-
expect(result.params).toEqual(["x"]);
|
|
83
|
-
expect(result.body.type).toBe("BlockStatement");
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it("does not treat strings containing => as functions", () => {
|
|
88
|
-
const result = parseTypeValueExpr('"a => b"');
|
|
89
|
-
expect(result.kind).toBe("literal");
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
describe("extractDirectives", () => {
|
|
94
|
-
it("extracts @nudo:case directives from function", () => {
|
|
95
|
-
const source = `
|
|
96
|
-
/**
|
|
97
|
-
* @nudo:case "concrete" (1, 2)
|
|
98
|
-
* @nudo:case "symbolic" (T.number, T.number)
|
|
99
|
-
*/
|
|
100
|
-
function calc(a, b) {
|
|
101
|
-
return a + b;
|
|
102
|
-
}
|
|
103
|
-
`;
|
|
104
|
-
const ast = parse(source);
|
|
105
|
-
const results = extractDirectives(ast);
|
|
106
|
-
expect(results).toHaveLength(1);
|
|
107
|
-
expect(results[0].name).toBe("calc");
|
|
108
|
-
expect(results[0].directives).toHaveLength(2);
|
|
109
|
-
|
|
110
|
-
const d0 = results[0].directives[0];
|
|
111
|
-
expect(d0.kind).toBe("case");
|
|
112
|
-
if (d0.kind !== "case") throw new Error("expected case directive");
|
|
113
|
-
expect(d0.name).toBe("concrete");
|
|
114
|
-
expect(d0.args).toHaveLength(2);
|
|
115
|
-
expect(typeValueEquals(d0.args[0], T.literal(1))).toBe(true);
|
|
116
|
-
expect(typeValueEquals(d0.args[1], T.literal(2))).toBe(true);
|
|
117
|
-
|
|
118
|
-
const d1 = results[0].directives[1];
|
|
119
|
-
expect(d1.kind).toBe("case");
|
|
120
|
-
if (d1.kind !== "case") throw new Error("expected case directive");
|
|
121
|
-
expect(d1.name).toBe("symbolic");
|
|
122
|
-
expect(typeValueEquals(d1.args[0], T.number)).toBe(true);
|
|
123
|
-
expect(typeValueEquals(d1.args[1], T.number)).toBe(true);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it("extracts from multiple functions", () => {
|
|
127
|
-
const source = `
|
|
128
|
-
/**
|
|
129
|
-
* @nudo:case "test" (1)
|
|
130
|
-
*/
|
|
131
|
-
function foo(x) { return x; }
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* @nudo:case "test2" ("hello")
|
|
135
|
-
*/
|
|
136
|
-
function bar(s) { return s; }
|
|
137
|
-
`;
|
|
138
|
-
const ast = parse(source);
|
|
139
|
-
const results = extractDirectives(ast);
|
|
140
|
-
expect(results).toHaveLength(2);
|
|
141
|
-
expect(results[0].name).toBe("foo");
|
|
142
|
-
expect(results[1].name).toBe("bar");
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it("ignores functions without directives", () => {
|
|
146
|
-
const source = `
|
|
147
|
-
function noDirective(x) { return x; }
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* @nudo:case "test" (T.number)
|
|
151
|
-
*/
|
|
152
|
-
function withDirective(x) { return x + 1; }
|
|
153
|
-
`;
|
|
154
|
-
const ast = parse(source);
|
|
155
|
-
const results = extractDirectives(ast);
|
|
156
|
-
expect(results).toHaveLength(1);
|
|
157
|
-
expect(results[0].name).toBe("withDirective");
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it("handles string arguments in case", () => {
|
|
161
|
-
const source = `
|
|
162
|
-
/**
|
|
163
|
-
* @nudo:case "string test" ("hello", "world")
|
|
164
|
-
*/
|
|
165
|
-
function greet(a, b) { return a + b; }
|
|
166
|
-
`;
|
|
167
|
-
const ast = parse(source);
|
|
168
|
-
const results = extractDirectives(ast);
|
|
169
|
-
const d = results[0].directives[0];
|
|
170
|
-
if (d.kind !== "case") throw new Error("expected case directive");
|
|
171
|
-
expect(d.args).toHaveLength(2);
|
|
172
|
-
expect(typeValueEquals(d.args[0], T.literal("hello"))).toBe(true);
|
|
173
|
-
expect(typeValueEquals(d.args[1], T.literal("world"))).toBe(true);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it("extracts arrow function literal as case argument", () => {
|
|
177
|
-
const source = `
|
|
178
|
-
/**
|
|
179
|
-
* @nudo:case "x" ([1, 2, 3], (a) => a * 2)
|
|
180
|
-
*/
|
|
181
|
-
function apply(items, cb) { return items; }
|
|
182
|
-
`;
|
|
183
|
-
const ast = parse(source);
|
|
184
|
-
const results = extractDirectives(ast);
|
|
185
|
-
const d = results[0].directives[0];
|
|
186
|
-
expect(d.kind).toBe("case");
|
|
187
|
-
if (d.kind !== "case") throw new Error("expected case directive");
|
|
188
|
-
expect(d.args[0].kind).toBe("tuple");
|
|
189
|
-
expect(d.args[1].kind).toBe("function");
|
|
190
|
-
if (d.args[1].kind === "function") {
|
|
191
|
-
expect(d.args[1].params).toEqual(["a"]);
|
|
192
|
-
expect(d.args[1].body.type).toBe("BinaryExpression");
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it("extracts arrow function inside nested object literal in case", () => {
|
|
197
|
-
const source = `
|
|
198
|
-
/**
|
|
199
|
-
* @nudo:case "s" ({ fn: (a) => a })
|
|
200
|
-
*/
|
|
201
|
-
function use(opts) { return opts; }
|
|
202
|
-
`;
|
|
203
|
-
const ast = parse(source);
|
|
204
|
-
const results = extractDirectives(ast);
|
|
205
|
-
const d = results[0].directives[0];
|
|
206
|
-
expect(d.kind).toBe("case");
|
|
207
|
-
if (d.kind !== "case") throw new Error("expected case directive");
|
|
208
|
-
expect(d.args[0].kind).toBe("object");
|
|
209
|
-
if (d.args[0].kind === "object") {
|
|
210
|
-
const fn = d.args[0].properties.fn;
|
|
211
|
-
expect(fn.kind).toBe("function");
|
|
212
|
-
if (fn.kind === "function") {
|
|
213
|
-
expect(fn.params).toEqual(["a"]);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
// ---------------------------------------------------------------------------
|
|
220
|
-
// TS 剥除 pass:parse() 统一剥除 TS-only 语法(见 ../strip-types.ts)。
|
|
221
|
-
// 断言方式:剥除后的 TS 源 AST 与等价 JS 源 AST 在去除位置/注释元数据后
|
|
222
|
-
// 结构等价。
|
|
223
|
-
// ---------------------------------------------------------------------------
|
|
224
|
-
|
|
225
|
-
import { stripTypes } from "@nudojs/core";
|
|
226
|
-
|
|
227
|
-
const META_KEYS = new Set([
|
|
228
|
-
"loc", "start", "end", "range",
|
|
229
|
-
"leadingComments", "trailingComments", "innerComments", "comments", "tokens", "extra",
|
|
230
|
-
]);
|
|
231
|
-
|
|
232
|
-
function normalize(node: unknown): unknown {
|
|
233
|
-
if (Array.isArray(node)) return node.map(normalize);
|
|
234
|
-
if (node !== null && typeof node === "object") {
|
|
235
|
-
const out: Record<string, unknown> = {};
|
|
236
|
-
for (const k of Object.keys(node as Record<string, unknown>).sort()) {
|
|
237
|
-
if (META_KEYS.has(k)) continue;
|
|
238
|
-
out[k] = normalize((node as Record<string, unknown>)[k]);
|
|
239
|
-
}
|
|
240
|
-
return out;
|
|
241
|
-
}
|
|
242
|
-
return node;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function expectStrippedEquivalent(tsSource: string, jsSource: string): void {
|
|
246
|
-
expect(normalize(parse(tsSource))).toEqual(normalize(parse(jsSource)));
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
describe("stripTypes: parse() 统一 TS 剥除", () => {
|
|
250
|
-
it("参数/返回标注被剥除", () => {
|
|
251
|
-
expectStrippedEquivalent(
|
|
252
|
-
"function add(a: number, b: number): number { return a + b; }",
|
|
253
|
-
"function add(a, b) { return a + b; }",
|
|
254
|
-
);
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
it("箭头函数泛型/标注/可选参数被剥除", () => {
|
|
258
|
-
expectStrippedEquivalent(
|
|
259
|
-
"const f = <T,>(v: T, opt?: boolean): T => v;",
|
|
260
|
-
"const f = (v, opt) => v;",
|
|
261
|
-
);
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
it("as 断言与非空断言解包", () => {
|
|
265
|
-
expectStrippedEquivalent(
|
|
266
|
-
"const y = x as string; const z = y!.length;",
|
|
267
|
-
"const y = x; const z = y.length;",
|
|
268
|
-
);
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
it("as const 与 satisfies 解包", () => {
|
|
272
|
-
expectStrippedEquivalent(
|
|
273
|
-
"const c = { a: 1 } as const; const s = { b: 2 } satisfies Alias;",
|
|
274
|
-
"const c = { a: 1 }; const s = { b: 2 };",
|
|
275
|
-
);
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
it("尖括号断言 <T>x 在 typescript+jsx 插件组合下不可解析(Babel 按 JSX 处理),剥除器仍处理 TSTypeAssertion 节点", () => {
|
|
279
|
-
// 与 tsc 在 .tsx 中的行为一致:尖括号断言必须写 as。parse 层两个插件
|
|
280
|
-
// 同时启用,`<string>x` 恒走 JSX 分支,因此 TSTypeAssertion 只能来自
|
|
281
|
-
// 无歧义位置;unwrapExpression 分支保留以兜底。
|
|
282
|
-
expect(() => parse("const y = <string>x;")).toThrow();
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
it("interface / type alias / enum / declare 声明被删除", () => {
|
|
286
|
-
const ast = parse(`
|
|
287
|
-
interface User { name: string }
|
|
288
|
-
type Alias = { tag: "x" };
|
|
289
|
-
enum Color { Red }
|
|
290
|
-
declare const gone: string;
|
|
291
|
-
declare function goneFn(): void;
|
|
292
|
-
function kept(n) { return n; }
|
|
293
|
-
`);
|
|
294
|
-
const body = ast.program.body;
|
|
295
|
-
expect(body).toHaveLength(1);
|
|
296
|
-
expect(body[0].type).toBe("FunctionDeclaration");
|
|
297
|
-
expect((body[0] as any).id.name).toBe("kept");
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
it("export 包装的类型声明整体删除", () => {
|
|
301
|
-
const ast = parse(`
|
|
302
|
-
export interface User { name: string }
|
|
303
|
-
export type Alias = string;
|
|
304
|
-
export declare class Gone {}
|
|
305
|
-
export default interface Def {}
|
|
306
|
-
export const real = 1;
|
|
307
|
-
`);
|
|
308
|
-
const body = ast.program.body;
|
|
309
|
-
expect(body).toHaveLength(1);
|
|
310
|
-
expect(body[0].type).toBe("ExportNamedDeclaration");
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
it("import type 删除,混合 type specifier 剔除", () => {
|
|
314
|
-
expectStrippedEquivalent(
|
|
315
|
-
'import type { A } from "m";\nimport { type C, b } from "m";\nuse(b);',
|
|
316
|
-
'import { b } from "m";\nuse(b);',
|
|
317
|
-
);
|
|
318
|
-
const ast = parse('import { type Only } from "m";');
|
|
319
|
-
expect(ast.program.body).toHaveLength(0);
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
it("export type 与 type-only re-export 删除", () => {
|
|
323
|
-
const ast = parse(`
|
|
324
|
-
const A = 1;
|
|
325
|
-
const keep = 2;
|
|
326
|
-
export type { A } from "./m";
|
|
327
|
-
export { type A, keep };
|
|
328
|
-
`);
|
|
329
|
-
const body = ast.program.body;
|
|
330
|
-
expect(body).toHaveLength(3);
|
|
331
|
-
const last = body[2] as any;
|
|
332
|
-
expect(last.type).toBe("ExportNamedDeclaration");
|
|
333
|
-
expect(last.specifiers).toHaveLength(1);
|
|
334
|
-
expect(last.specifiers[0].local.name).toBe("keep");
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
it("泛型调用与 new 的类型实参剥除", () => {
|
|
338
|
-
expectStrippedEquivalent(
|
|
339
|
-
"const r = id<string>(1); const c = new Map<string, number>();",
|
|
340
|
-
"const r = id(1); const c = new Map();",
|
|
341
|
-
);
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
it("裸泛型引用(TSInstantiationExpression)解包", () => {
|
|
345
|
-
expectStrippedEquivalent(
|
|
346
|
-
"const g = id<number>;",
|
|
347
|
-
"const g = id;",
|
|
348
|
-
);
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
it("类成员:implements/索引签名/标注剥除,declare·abstract 成员删除", () => {
|
|
352
|
-
expectStrippedEquivalent(
|
|
353
|
-
`
|
|
354
|
-
class Pt implements Shape {
|
|
355
|
-
[key: string]: unknown;
|
|
356
|
-
x: number = 0;
|
|
357
|
-
declare hidden: string;
|
|
358
|
-
get(): number { return this.x; }
|
|
359
|
-
}
|
|
360
|
-
`,
|
|
361
|
-
`
|
|
362
|
-
class Pt {
|
|
363
|
-
x = 0;
|
|
364
|
-
get() { return this.x; }
|
|
365
|
-
}
|
|
366
|
-
`,
|
|
367
|
-
);
|
|
368
|
-
const ast = parse("declare abstract class AbstractBase { abstract m(): void; x: number; }");
|
|
369
|
-
expect(ast.program.body).toHaveLength(0);
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
it("declare module / declare namespace 删除", () => {
|
|
373
|
-
const ast = parse(`
|
|
374
|
-
declare module "x" { export const y: string; }
|
|
375
|
-
declare namespace NS { const z: string; }
|
|
376
|
-
function kept() {}
|
|
377
|
-
`);
|
|
378
|
-
expect(ast.program.body).toHaveLength(1);
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
it("loc 保持:删除类型声明不重排兄弟节点位置", () => {
|
|
382
|
-
const ast = parse("interface I {}\nfunction f() {}\nfunction g() {}");
|
|
383
|
-
const body = ast.program.body;
|
|
384
|
-
expect(body).toHaveLength(2);
|
|
385
|
-
expect(body[0].loc?.start.line).toBe(2);
|
|
386
|
-
expect(body[1].loc?.start.line).toBe(3);
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
it("纯 JS 源码剥除为 no-op 且返回同一棵 AST", () => {
|
|
390
|
-
const js = "/** @nudo:case \"a\" (T.number) */\nfunction f(x) { return x; }\n";
|
|
391
|
-
const once = parse(js);
|
|
392
|
-
const snapshot = JSON.stringify(normalize(once));
|
|
393
|
-
expect(stripTypes(once)).toBe(once);
|
|
394
|
-
expect(JSON.stringify(normalize(parse(js)))).toBe(snapshot);
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
it("@nudo:case 指令在 .ts 源上照常提取(注释 loc 对齐不受剥除影响)", () => {
|
|
398
|
-
const ts = `interface Ctx { id: number }
|
|
399
|
-
/**
|
|
400
|
-
* @nudo:case "ints" (1, 2)
|
|
401
|
-
* @nudo:case "syms" (T.number, T.number)
|
|
402
|
-
*/
|
|
403
|
-
function add(a: number, b: number): number { return a + b; }
|
|
404
|
-
`;
|
|
405
|
-
const results = extractDirectives(parse(ts));
|
|
406
|
-
expect(results).toHaveLength(1);
|
|
407
|
-
expect(results[0].name).toBe("add");
|
|
408
|
-
expect(results[0].directives).toHaveLength(2);
|
|
409
|
-
expect((results[0].directives[0] as any).commentLine).toBe(3);
|
|
410
|
-
});
|
|
411
|
-
});
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { parse, extractDirectives, type MockDirective } from "../index.ts";
|
|
3
|
-
|
|
4
|
-
describe("Mock Directive", () => {
|
|
5
|
-
describe("Arrow function support", () => {
|
|
6
|
-
it("parses arrow function with single param", () => {
|
|
7
|
-
const code = `
|
|
8
|
-
// @nudo:mock fn = (x) => x > 3
|
|
9
|
-
function filter(arr, fn) {
|
|
10
|
-
return arr.filter(fn);
|
|
11
|
-
}
|
|
12
|
-
`;
|
|
13
|
-
const ast = parse(code);
|
|
14
|
-
const directives = extractDirectives(ast);
|
|
15
|
-
expect(directives.length).toBe(1);
|
|
16
|
-
|
|
17
|
-
const mockDirectives = directives[0].directives.filter(
|
|
18
|
-
(d): d is MockDirective => d.kind === "mock"
|
|
19
|
-
);
|
|
20
|
-
expect(mockDirectives.length).toBe(1);
|
|
21
|
-
expect(mockDirectives[0].name).toBe("fn");
|
|
22
|
-
expect(mockDirectives[0].arrowFn).toBeDefined();
|
|
23
|
-
expect(mockDirectives[0].arrowFn!.params).toEqual(["x"]);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("parses arrow function with multiple params", () => {
|
|
27
|
-
const code = `
|
|
28
|
-
// @nudo:mock comparator = (a, b) => a - b
|
|
29
|
-
function sort(arr, comparator) {
|
|
30
|
-
return arr.sort(comparator);
|
|
31
|
-
}
|
|
32
|
-
`;
|
|
33
|
-
const ast = parse(code);
|
|
34
|
-
const directives = extractDirectives(ast);
|
|
35
|
-
const mockDirectives = directives[0].directives.filter(
|
|
36
|
-
(d): d is MockDirective => d.kind === "mock"
|
|
37
|
-
);
|
|
38
|
-
expect(mockDirectives[0].arrowFn).toBeDefined();
|
|
39
|
-
expect(mockDirectives[0].arrowFn!.params).toEqual(["a", "b"]);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it("parses arrow function with block body", () => {
|
|
43
|
-
const code = `
|
|
44
|
-
// @nudo:mock fn = (x) => { return x * 2; }
|
|
45
|
-
function double(arr, fn) {
|
|
46
|
-
return arr.map(fn);
|
|
47
|
-
}
|
|
48
|
-
`;
|
|
49
|
-
const ast = parse(code);
|
|
50
|
-
const directives = extractDirectives(ast);
|
|
51
|
-
const mockDirectives = directives[0].directives.filter(
|
|
52
|
-
(d): d is MockDirective => d.kind === "mock"
|
|
53
|
-
);
|
|
54
|
-
expect(mockDirectives[0].arrowFn).toBeDefined();
|
|
55
|
-
expect(mockDirectives[0].arrowFn!.params).toEqual(["x"]);
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
describe("Regular expression support", () => {
|
|
60
|
-
it("parses type expression", () => {
|
|
61
|
-
const code = `
|
|
62
|
-
// @nudo:mock data = T.string
|
|
63
|
-
function foo(data) {
|
|
64
|
-
return data;
|
|
65
|
-
}
|
|
66
|
-
`;
|
|
67
|
-
const ast = parse(code);
|
|
68
|
-
const directives = extractDirectives(ast);
|
|
69
|
-
const mockDirectives = directives[0].directives.filter(
|
|
70
|
-
(d): d is MockDirective => d.kind === "mock"
|
|
71
|
-
);
|
|
72
|
-
expect(mockDirectives[0].name).toBe("data");
|
|
73
|
-
expect(mockDirectives[0].expression).toBe("T.string");
|
|
74
|
-
expect(mockDirectives[0].arrowFn).toBeUndefined();
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it("parses from path", () => {
|
|
78
|
-
const code = `
|
|
79
|
-
// @nudo:mock data from "./mock-data.ts"
|
|
80
|
-
function foo(data) {
|
|
81
|
-
return data;
|
|
82
|
-
}
|
|
83
|
-
`;
|
|
84
|
-
const ast = parse(code);
|
|
85
|
-
const directives = extractDirectives(ast);
|
|
86
|
-
const mockDirectives = directives[0].directives.filter(
|
|
87
|
-
(d): d is MockDirective => d.kind === "mock"
|
|
88
|
-
);
|
|
89
|
-
expect(mockDirectives[0].name).toBe("data");
|
|
90
|
-
expect(mockDirectives[0].fromPath).toBe("./mock-data.ts");
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
describe("Multiple mocks", () => {
|
|
95
|
-
it("parses multiple mock directives", () => {
|
|
96
|
-
const code = `
|
|
97
|
-
// @nudo:mock fn = (x) => x > 3
|
|
98
|
-
// @nudo:mock data = [1, 2, 3, 4, 5]
|
|
99
|
-
function filterAndProcess(data, fn) {
|
|
100
|
-
return data.filter(fn);
|
|
101
|
-
}
|
|
102
|
-
`;
|
|
103
|
-
const ast = parse(code);
|
|
104
|
-
const directives = extractDirectives(ast);
|
|
105
|
-
const mockDirectives = directives[0].directives.filter(
|
|
106
|
-
(d): d is MockDirective => d.kind === "mock"
|
|
107
|
-
);
|
|
108
|
-
expect(mockDirectives.length).toBe(2);
|
|
109
|
-
expect(mockDirectives[0].name).toBe("fn");
|
|
110
|
-
expect(mockDirectives[0].arrowFn).toBeDefined();
|
|
111
|
-
expect(mockDirectives[1].name).toBe("data");
|
|
112
|
-
expect(mockDirectives[1].expression).toBe("[1, 2, 3, 4, 5]");
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
});
|