@wx-sab/renkei 1.3.2 → 1.3.4
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/lib/cli.js +2 -2
- package/lib/commands/api.d.ts +9 -0
- package/lib/commands/api.js +18 -9
- package/lib/commands/api.test.d.ts +1 -0
- package/lib/commands/api.test.js +132 -0
- package/lib/core/constants.test.d.ts +1 -0
- package/lib/core/constants.test.js +31 -0
- package/lib/core/folder-selector.test.d.ts +1 -0
- package/lib/core/folder-selector.test.js +246 -0
- package/lib/core/generate.test.d.ts +1 -0
- package/lib/core/generate.test.js +341 -0
- package/lib/core/type-filter.d.ts +19 -0
- package/lib/core/type-filter.js +26 -10
- package/lib/core/type-filter.test.d.ts +1 -0
- package/lib/core/type-filter.test.js +564 -0
- package/package.json +1 -1
- package/templates/api.ejs +1 -1
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
const vitest_1 = require("vitest");
|
|
16
|
+
const type_filter_1 = require("./type-filter");
|
|
17
|
+
vitest_1.vi.mock('fs/promises', () => ({
|
|
18
|
+
default: {
|
|
19
|
+
readFile: vitest_1.vi.fn(),
|
|
20
|
+
writeFile: vitest_1.vi.fn()
|
|
21
|
+
}
|
|
22
|
+
}));
|
|
23
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
24
|
+
// ─── extractTypeReferences ───────────────────────────────────────────
|
|
25
|
+
(0, vitest_1.describe)('extractTypeReferences', () => {
|
|
26
|
+
(0, vitest_1.it)('should extract Model.ServiceName.TypeName patterns', () => {
|
|
27
|
+
const content = `
|
|
28
|
+
const a: Model.UserService.UserVO = {}
|
|
29
|
+
const b: Model.OrderService.OrderDTO = {}
|
|
30
|
+
`;
|
|
31
|
+
const result = (0, type_filter_1.extractTypeReferences)(content, 'Model');
|
|
32
|
+
(0, vitest_1.expect)(result.get('UserService')).toBeDefined();
|
|
33
|
+
(0, vitest_1.expect)(result.get('UserService').has('UserVO')).toBe(true);
|
|
34
|
+
(0, vitest_1.expect)(result.get('OrderService')).toBeDefined();
|
|
35
|
+
(0, vitest_1.expect)(result.get('OrderService').has('OrderDTO')).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
(0, vitest_1.it)('should return empty Map when no matches', () => {
|
|
38
|
+
const content = 'const x = 1';
|
|
39
|
+
const result = (0, type_filter_1.extractTypeReferences)(content, 'Model');
|
|
40
|
+
(0, vitest_1.expect)(result.size).toBe(0);
|
|
41
|
+
});
|
|
42
|
+
(0, vitest_1.it)('should return empty Map for empty content', () => {
|
|
43
|
+
const result = (0, type_filter_1.extractTypeReferences)('', 'Model');
|
|
44
|
+
(0, vitest_1.expect)(result.size).toBe(0);
|
|
45
|
+
});
|
|
46
|
+
(0, vitest_1.it)('should deduplicate refs for same service', () => {
|
|
47
|
+
const content = `
|
|
48
|
+
const a: Model.UserService.UserVO = {}
|
|
49
|
+
const b: Model.UserService.UserVO = {}
|
|
50
|
+
const c: Model.UserService.CreateUserDTO = {}
|
|
51
|
+
`;
|
|
52
|
+
const result = (0, type_filter_1.extractTypeReferences)(content, 'Model');
|
|
53
|
+
(0, vitest_1.expect)(result.get('UserService').size).toBe(2);
|
|
54
|
+
(0, vitest_1.expect)(result.get('UserService').has('UserVO')).toBe(true);
|
|
55
|
+
(0, vitest_1.expect)(result.get('UserService').has('CreateUserDTO')).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
(0, vitest_1.it)('should work with custom modelNamespace', () => {
|
|
58
|
+
const content = 'const x: CustomSvc.UserService.UserVO = {}';
|
|
59
|
+
const result = (0, type_filter_1.extractTypeReferences)(content, 'CustomSvc');
|
|
60
|
+
(0, vitest_1.expect)(result.get('UserService')).toBeDefined();
|
|
61
|
+
(0, vitest_1.expect)(result.get('UserService').has('UserVO')).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
// ─── stripComments ───────────────────────────────────────────────────
|
|
65
|
+
(0, vitest_1.describe)('stripComments', () => {
|
|
66
|
+
(0, vitest_1.it)('should strip single-line comments', () => {
|
|
67
|
+
const content = 'const x = 1 // this is a comment\nconst y = 2';
|
|
68
|
+
const result = (0, type_filter_1.stripComments)(content);
|
|
69
|
+
(0, vitest_1.expect)(result).toBe('const x = 1 \nconst y = 2');
|
|
70
|
+
});
|
|
71
|
+
(0, vitest_1.it)('should strip multi-line comments', () => {
|
|
72
|
+
const content = 'const x = 1 /* comment */ const y = 2';
|
|
73
|
+
const result = (0, type_filter_1.stripComments)(content);
|
|
74
|
+
(0, vitest_1.expect)(result).toBe('const x = 1 const y = 2');
|
|
75
|
+
});
|
|
76
|
+
(0, vitest_1.it)('should preserve strings with // inside', () => {
|
|
77
|
+
const content = 'const url = "http://example.com" // real comment';
|
|
78
|
+
const result = (0, type_filter_1.stripComments)(content);
|
|
79
|
+
(0, vitest_1.expect)(result).toBe('const url = "http://example.com" ');
|
|
80
|
+
});
|
|
81
|
+
(0, vitest_1.it)('should preserve template literals with // inside', () => {
|
|
82
|
+
const content = 'const url = `http://example.com` // real comment';
|
|
83
|
+
const result = (0, type_filter_1.stripComments)(content);
|
|
84
|
+
(0, vitest_1.expect)(result).toBe('const url = `http://example.com` ');
|
|
85
|
+
});
|
|
86
|
+
(0, vitest_1.it)('should handle escaped quotes in strings', () => {
|
|
87
|
+
const content = 'const s = "say \\"hello//" // comment';
|
|
88
|
+
const result = (0, type_filter_1.stripComments)(content);
|
|
89
|
+
(0, vitest_1.expect)(result).toBe('const s = "say \\"hello//" ');
|
|
90
|
+
});
|
|
91
|
+
(0, vitest_1.it)('should handle escaped quotes in single-quoted strings', () => {
|
|
92
|
+
const content = "const s = 'it\\'s // not a comment' // real comment";
|
|
93
|
+
const result = (0, type_filter_1.stripComments)(content);
|
|
94
|
+
(0, vitest_1.expect)(result).toBe("const s = 'it\\'s // not a comment' ");
|
|
95
|
+
});
|
|
96
|
+
(0, vitest_1.it)('should return empty string for empty input', () => {
|
|
97
|
+
(0, vitest_1.expect)((0, type_filter_1.stripComments)('')).toBe('');
|
|
98
|
+
});
|
|
99
|
+
(0, vitest_1.it)('should handle unterminated string gracefully', () => {
|
|
100
|
+
const content = 'const s = "unterminated';
|
|
101
|
+
const result = (0, type_filter_1.stripComments)(content);
|
|
102
|
+
(0, vitest_1.expect)(result).toBe('const s = "unterminated');
|
|
103
|
+
});
|
|
104
|
+
(0, vitest_1.it)('should strip multi-line block comments', () => {
|
|
105
|
+
const content = '/* line1\nline2 */ code';
|
|
106
|
+
const result = (0, type_filter_1.stripComments)(content);
|
|
107
|
+
(0, vitest_1.expect)(result).toBe(' code');
|
|
108
|
+
});
|
|
109
|
+
(0, vitest_1.it)('should preserve code without comments unchanged', () => {
|
|
110
|
+
const content = 'const x = 1\nconst y = 2';
|
|
111
|
+
(0, vitest_1.expect)((0, type_filter_1.stripComments)(content)).toBe(content);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
// ─── collectTypeDefBody ──────────────────────────────────────────────
|
|
115
|
+
(0, vitest_1.describe)('collectTypeDefBody', () => {
|
|
116
|
+
(0, vitest_1.it)('should handle single-line type without braces', () => {
|
|
117
|
+
const lines = [
|
|
118
|
+
'export type Name = string',
|
|
119
|
+
'export interface Foo {}'
|
|
120
|
+
];
|
|
121
|
+
const result = (0, type_filter_1.collectTypeDefBody)(lines, 0, []);
|
|
122
|
+
(0, vitest_1.expect)(result.typeDefLines).toEqual(['export type Name = string']);
|
|
123
|
+
(0, vitest_1.expect)(result.nextIndex).toBe(1);
|
|
124
|
+
});
|
|
125
|
+
(0, vitest_1.it)('should handle multi-line type with braces', () => {
|
|
126
|
+
const lines = [
|
|
127
|
+
'export interface User {',
|
|
128
|
+
' id: number',
|
|
129
|
+
' name: string',
|
|
130
|
+
'}',
|
|
131
|
+
'export type X = number'
|
|
132
|
+
];
|
|
133
|
+
const result = (0, type_filter_1.collectTypeDefBody)(lines, 0, []);
|
|
134
|
+
(0, vitest_1.expect)(result.typeDefLines).toEqual([
|
|
135
|
+
'export interface User {',
|
|
136
|
+
' id: number',
|
|
137
|
+
' name: string',
|
|
138
|
+
'}'
|
|
139
|
+
]);
|
|
140
|
+
(0, vitest_1.expect)(result.nextIndex).toBe(4);
|
|
141
|
+
});
|
|
142
|
+
(0, vitest_1.it)('should prepend initialLines (JSDoc)', () => {
|
|
143
|
+
const lines = [
|
|
144
|
+
'export interface User {',
|
|
145
|
+
' id: number',
|
|
146
|
+
'}'
|
|
147
|
+
];
|
|
148
|
+
const result = (0, type_filter_1.collectTypeDefBody)(lines, 0, ['/** User info */']);
|
|
149
|
+
(0, vitest_1.expect)(result.typeDefLines[0]).toBe('/** User info */');
|
|
150
|
+
(0, vitest_1.expect)(result.typeDefLines[1]).toBe('export interface User {');
|
|
151
|
+
});
|
|
152
|
+
(0, vitest_1.it)('should handle nested braces', () => {
|
|
153
|
+
const lines = [
|
|
154
|
+
'export interface Outer {',
|
|
155
|
+
' inner: {',
|
|
156
|
+
' value: number',
|
|
157
|
+
' }',
|
|
158
|
+
'}'
|
|
159
|
+
];
|
|
160
|
+
const result = (0, type_filter_1.collectTypeDefBody)(lines, 0, []);
|
|
161
|
+
(0, vitest_1.expect)(result.typeDefLines.length).toBe(5);
|
|
162
|
+
(0, vitest_1.expect)(result.nextIndex).toBe(5);
|
|
163
|
+
});
|
|
164
|
+
(0, vitest_1.it)('should handle type alias with brace on same line', () => {
|
|
165
|
+
const lines = [
|
|
166
|
+
'export type Result = { data: string }',
|
|
167
|
+
'export type X = number'
|
|
168
|
+
];
|
|
169
|
+
const result = (0, type_filter_1.collectTypeDefBody)(lines, 0, []);
|
|
170
|
+
(0, vitest_1.expect)(result.typeDefLines).toEqual(['export type Result = { data: string }']);
|
|
171
|
+
(0, vitest_1.expect)(result.nextIndex).toBe(1);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
// ─── extractAllTypeDefinitions ───────────────────────────────────────
|
|
175
|
+
(0, vitest_1.describe)('extractAllTypeDefinitions', () => {
|
|
176
|
+
(0, vitest_1.it)('should extract interface definitions', () => {
|
|
177
|
+
const content = `export interface User {
|
|
178
|
+
id: number
|
|
179
|
+
name: string
|
|
180
|
+
}`;
|
|
181
|
+
const result = (0, type_filter_1.extractAllTypeDefinitions)(content);
|
|
182
|
+
(0, vitest_1.expect)(result.has('User')).toBe(true);
|
|
183
|
+
(0, vitest_1.expect)(result.get('User')).toContain('export interface User');
|
|
184
|
+
});
|
|
185
|
+
(0, vitest_1.it)('should extract type definitions', () => {
|
|
186
|
+
const content = 'export type Status = "active" | "inactive"';
|
|
187
|
+
const result = (0, type_filter_1.extractAllTypeDefinitions)(content);
|
|
188
|
+
(0, vitest_1.expect)(result.has('Status')).toBe(true);
|
|
189
|
+
});
|
|
190
|
+
(0, vitest_1.it)('should extract enum definitions', () => {
|
|
191
|
+
const content = `export enum Color {
|
|
192
|
+
Red,
|
|
193
|
+
Green,
|
|
194
|
+
Blue
|
|
195
|
+
}`;
|
|
196
|
+
const result = (0, type_filter_1.extractAllTypeDefinitions)(content);
|
|
197
|
+
(0, vitest_1.expect)(result.has('Color')).toBe(true);
|
|
198
|
+
});
|
|
199
|
+
(0, vitest_1.it)('should preserve JSDoc comments before definitions', () => {
|
|
200
|
+
const content = `/** User info */
|
|
201
|
+
export interface User {
|
|
202
|
+
id: number
|
|
203
|
+
}`;
|
|
204
|
+
const result = (0, type_filter_1.extractAllTypeDefinitions)(content);
|
|
205
|
+
(0, vitest_1.expect)(result.get('User')).toContain('/** User info */');
|
|
206
|
+
});
|
|
207
|
+
(0, vitest_1.it)('should skip block comments not followed by type defs', () => {
|
|
208
|
+
const content = `/* This is just a comment */
|
|
209
|
+
const x = 1
|
|
210
|
+
export interface Foo {}`;
|
|
211
|
+
const result = (0, type_filter_1.extractAllTypeDefinitions)(content);
|
|
212
|
+
(0, vitest_1.expect)(result.has('Foo')).toBe(true);
|
|
213
|
+
(0, vitest_1.expect)(result.get('Foo')).not.toContain('This is just a comment');
|
|
214
|
+
});
|
|
215
|
+
(0, vitest_1.it)('should handle nested braces in type defs', () => {
|
|
216
|
+
const content = `export interface Outer {
|
|
217
|
+
inner: {
|
|
218
|
+
value: number
|
|
219
|
+
}
|
|
220
|
+
}`;
|
|
221
|
+
const result = (0, type_filter_1.extractAllTypeDefinitions)(content);
|
|
222
|
+
(0, vitest_1.expect)(result.has('Outer')).toBe(true);
|
|
223
|
+
(0, vitest_1.expect)(result.get('Outer')).toContain('inner:');
|
|
224
|
+
});
|
|
225
|
+
(0, vitest_1.it)('should return empty Map for empty content', () => {
|
|
226
|
+
const result = (0, type_filter_1.extractAllTypeDefinitions)('');
|
|
227
|
+
(0, vitest_1.expect)(result.size).toBe(0);
|
|
228
|
+
});
|
|
229
|
+
(0, vitest_1.it)('should extract multiple definitions', () => {
|
|
230
|
+
const content = `export interface User { id: number }
|
|
231
|
+
export type Status = "active" | "inactive"
|
|
232
|
+
export enum Color { Red, Green }`;
|
|
233
|
+
const result = (0, type_filter_1.extractAllTypeDefinitions)(content);
|
|
234
|
+
(0, vitest_1.expect)(result.size).toBe(3);
|
|
235
|
+
(0, vitest_1.expect)(result.has('User')).toBe(true);
|
|
236
|
+
(0, vitest_1.expect)(result.has('Status')).toBe(true);
|
|
237
|
+
(0, vitest_1.expect)(result.has('Color')).toBe(true);
|
|
238
|
+
});
|
|
239
|
+
(0, vitest_1.it)('should handle definitions without export keyword', () => {
|
|
240
|
+
const content = 'interface Internal { x: number }';
|
|
241
|
+
const result = (0, type_filter_1.extractAllTypeDefinitions)(content);
|
|
242
|
+
(0, vitest_1.expect)(result.has('Internal')).toBe(true);
|
|
243
|
+
});
|
|
244
|
+
(0, vitest_1.it)('should handle JSDoc spanning multiple lines', () => {
|
|
245
|
+
const content = `/**
|
|
246
|
+
* User description
|
|
247
|
+
* @description A user
|
|
248
|
+
*/
|
|
249
|
+
export interface User {
|
|
250
|
+
id: number
|
|
251
|
+
}`;
|
|
252
|
+
const result = (0, type_filter_1.extractAllTypeDefinitions)(content);
|
|
253
|
+
(0, vitest_1.expect)(result.has('User')).toBe(true);
|
|
254
|
+
(0, vitest_1.expect)(result.get('User').split('\n')[0]).toBe('/**');
|
|
255
|
+
(0, vitest_1.expect)(result.get('User')).toContain(' * User description');
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
// ─── findShortNameRefs ───────────────────────────────────────────────
|
|
259
|
+
(0, vitest_1.describe)('findShortNameRefs', () => {
|
|
260
|
+
(0, vitest_1.it)('should find type names using word boundary regex', () => {
|
|
261
|
+
const code = 'const x: UserVO = {}';
|
|
262
|
+
const result = (0, type_filter_1.findShortNameRefs)(code, new Set(['UserVO', 'OrderVO']));
|
|
263
|
+
(0, vitest_1.expect)(result.has('UserVO')).toBe(true);
|
|
264
|
+
(0, vitest_1.expect)(result.has('OrderVO')).toBe(false);
|
|
265
|
+
});
|
|
266
|
+
(0, vitest_1.it)('should return empty set for no matches', () => {
|
|
267
|
+
const code = 'const x = 1';
|
|
268
|
+
const result = (0, type_filter_1.findShortNameRefs)(code, new Set(['UserVO']));
|
|
269
|
+
(0, vitest_1.expect)(result.size).toBe(0);
|
|
270
|
+
});
|
|
271
|
+
(0, vitest_1.it)('should handle large name sets (CHUNK_SIZE = 200)', () => {
|
|
272
|
+
const names = [];
|
|
273
|
+
for (let i = 0; i < 300; i++) {
|
|
274
|
+
names.push(`Type${i}`);
|
|
275
|
+
}
|
|
276
|
+
const code = 'const x: Type250 = {}';
|
|
277
|
+
const result = (0, type_filter_1.findShortNameRefs)(code, new Set(names));
|
|
278
|
+
(0, vitest_1.expect)(result.has('Type250')).toBe(true);
|
|
279
|
+
});
|
|
280
|
+
(0, vitest_1.it)('should not match partial names', () => {
|
|
281
|
+
const code = 'const x: UserVOExtra = {}';
|
|
282
|
+
const result = (0, type_filter_1.findShortNameRefs)(code, new Set(['UserVO']));
|
|
283
|
+
(0, vitest_1.expect)(result.has('UserVO')).toBe(false);
|
|
284
|
+
});
|
|
285
|
+
(0, vitest_1.it)('should match multiple occurrences', () => {
|
|
286
|
+
const code = 'const x: UserVO = {} as UserVO';
|
|
287
|
+
const result = (0, type_filter_1.findShortNameRefs)(code, new Set(['UserVO']));
|
|
288
|
+
(0, vitest_1.expect)(result.has('UserVO')).toBe(true);
|
|
289
|
+
(0, vitest_1.expect)(result.size).toBe(1);
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
// ─── findTypeDependencies ────────────────────────────────────────────
|
|
293
|
+
(0, vitest_1.describe)('findTypeDependencies', () => {
|
|
294
|
+
(0, vitest_1.it)('should find transitive dependencies via short name refs', () => {
|
|
295
|
+
const typeDefs = new Map([
|
|
296
|
+
['UserVO', 'export interface UserVO { items: OrderItem[] }'],
|
|
297
|
+
['OrderItem', 'export interface OrderItem { productId: number }']
|
|
298
|
+
]);
|
|
299
|
+
const result = (0, type_filter_1.findTypeDependencies)(new Set(['UserVO']), typeDefs, 'Model', 'OrderService');
|
|
300
|
+
(0, vitest_1.expect)(result.has('UserVO')).toBe(true);
|
|
301
|
+
(0, vitest_1.expect)(result.has('OrderItem')).toBe(true);
|
|
302
|
+
});
|
|
303
|
+
(0, vitest_1.it)('should handle full qualified refs (Model.Svc.Type)', () => {
|
|
304
|
+
const typeDefs = new Map([
|
|
305
|
+
['UserVO', 'export interface UserVO { data: Model.UserService.DetailVO }'],
|
|
306
|
+
['DetailVO', 'export interface DetailVO { id: number }']
|
|
307
|
+
]);
|
|
308
|
+
const result = (0, type_filter_1.findTypeDependencies)(new Set(['UserVO']), typeDefs, 'Model', 'UserService');
|
|
309
|
+
(0, vitest_1.expect)(result.has('UserVO')).toBe(true);
|
|
310
|
+
(0, vitest_1.expect)(result.has('DetailVO')).toBe(true);
|
|
311
|
+
});
|
|
312
|
+
(0, vitest_1.it)('should handle circular references', () => {
|
|
313
|
+
const typeDefs = new Map([
|
|
314
|
+
['NodeA', 'export interface NodeA { child: NodeB }'],
|
|
315
|
+
['NodeB', 'export interface NodeB { parent: NodeA }']
|
|
316
|
+
]);
|
|
317
|
+
const result = (0, type_filter_1.findTypeDependencies)(new Set(['NodeA']), typeDefs, 'Model', 'TestService');
|
|
318
|
+
(0, vitest_1.expect)(result.has('NodeA')).toBe(true);
|
|
319
|
+
(0, vitest_1.expect)(result.has('NodeB')).toBe(true);
|
|
320
|
+
});
|
|
321
|
+
(0, vitest_1.it)('should skip unknown type references', () => {
|
|
322
|
+
const typeDefs = new Map([
|
|
323
|
+
['UserVO', 'export interface UserVO { role: UnknownType }']
|
|
324
|
+
]);
|
|
325
|
+
const result = (0, type_filter_1.findTypeDependencies)(new Set(['UserVO']), typeDefs, 'Model', 'TestService');
|
|
326
|
+
(0, vitest_1.expect)(result.has('UserVO')).toBe(true);
|
|
327
|
+
(0, vitest_1.expect)(result.size).toBe(1);
|
|
328
|
+
});
|
|
329
|
+
(0, vitest_1.it)('should handle empty root types', () => {
|
|
330
|
+
const typeDefs = new Map([
|
|
331
|
+
['UserVO', 'export interface UserVO { id: number }']
|
|
332
|
+
]);
|
|
333
|
+
const result = (0, type_filter_1.findTypeDependencies)(new Set(), typeDefs, 'Model', 'TestService');
|
|
334
|
+
(0, vitest_1.expect)(result.size).toBe(0);
|
|
335
|
+
});
|
|
336
|
+
(0, vitest_1.it)('should find deeply nested dependencies', () => {
|
|
337
|
+
const typeDefs = new Map([
|
|
338
|
+
['OrderVO', 'export interface OrderVO { items: OrderItem[] }'],
|
|
339
|
+
['OrderItem', 'export interface OrderItem { product: Product }'],
|
|
340
|
+
['Product', 'export interface Product { category: Category }'],
|
|
341
|
+
['Category', 'export interface Category { name: string }']
|
|
342
|
+
]);
|
|
343
|
+
const result = (0, type_filter_1.findTypeDependencies)(new Set(['OrderVO']), typeDefs, 'Model', 'OrderService');
|
|
344
|
+
(0, vitest_1.expect)(result.size).toBe(4);
|
|
345
|
+
(0, vitest_1.expect)(result.has('OrderVO')).toBe(true);
|
|
346
|
+
(0, vitest_1.expect)(result.has('Category')).toBe(true);
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
// ─── detectNamespaceIndent ───────────────────────────────────────────
|
|
350
|
+
(0, vitest_1.describe)('detectNamespaceIndent', () => {
|
|
351
|
+
(0, vitest_1.it)('should detect indent from export namespace line', () => {
|
|
352
|
+
const lines = [
|
|
353
|
+
'declare namespace Model {',
|
|
354
|
+
' export namespace UserService {',
|
|
355
|
+
' }',
|
|
356
|
+
'}'
|
|
357
|
+
];
|
|
358
|
+
const result = (0, type_filter_1.detectNamespaceIndent)(lines, 'UserService');
|
|
359
|
+
(0, vitest_1.expect)(result).toBe(' ');
|
|
360
|
+
});
|
|
361
|
+
(0, vitest_1.it)('should detect deeper indent', () => {
|
|
362
|
+
const lines = [
|
|
363
|
+
'declare namespace Model {',
|
|
364
|
+
' export namespace OrderService {',
|
|
365
|
+
' }',
|
|
366
|
+
'}'
|
|
367
|
+
];
|
|
368
|
+
const result = (0, type_filter_1.detectNamespaceIndent)(lines, 'OrderService');
|
|
369
|
+
(0, vitest_1.expect)(result).toBe(' ');
|
|
370
|
+
});
|
|
371
|
+
(0, vitest_1.it)('should return default indent when namespace not found', () => {
|
|
372
|
+
const lines = ['declare namespace Model {', '}'];
|
|
373
|
+
const result = (0, type_filter_1.detectNamespaceIndent)(lines, 'UnknownService');
|
|
374
|
+
(0, vitest_1.expect)(result).toBe(' ');
|
|
375
|
+
});
|
|
376
|
+
(0, vitest_1.it)('should handle empty lines array', () => {
|
|
377
|
+
const result = (0, type_filter_1.detectNamespaceIndent)([], 'UserService');
|
|
378
|
+
(0, vitest_1.expect)(result).toBe(' ');
|
|
379
|
+
});
|
|
380
|
+
(0, vitest_1.it)('should handle no-indent namespace', () => {
|
|
381
|
+
const lines = ['export namespace UserService {'];
|
|
382
|
+
const result = (0, type_filter_1.detectNamespaceIndent)(lines, 'UserService');
|
|
383
|
+
(0, vitest_1.expect)(result).toBe(' ');
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
// ─── filterModelTypes ────────────────────────────────────────────────
|
|
387
|
+
(0, vitest_1.describe)('filterModelTypes', () => {
|
|
388
|
+
const modelContent = `declare namespace Model {
|
|
389
|
+
export namespace UserService {
|
|
390
|
+
export interface UserVO {
|
|
391
|
+
id: number
|
|
392
|
+
name: string
|
|
393
|
+
}
|
|
394
|
+
export interface CreateUserDTO {
|
|
395
|
+
name: string
|
|
396
|
+
email: string
|
|
397
|
+
}
|
|
398
|
+
export interface UnusedVO {
|
|
399
|
+
data: string
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}`;
|
|
403
|
+
(0, vitest_1.it)('should reconstruct model file with only used types', () => {
|
|
404
|
+
const result = (0, type_filter_1.filterModelTypes)(modelContent, new Set(['UserVO']), 'Model', 'UserService');
|
|
405
|
+
(0, vitest_1.expect)(result).toContain('export interface UserVO');
|
|
406
|
+
(0, vitest_1.expect)(result).toContain('id: number');
|
|
407
|
+
(0, vitest_1.expect)(result).not.toContain('CreateUserDTO');
|
|
408
|
+
(0, vitest_1.expect)(result).not.toContain('UnusedVO');
|
|
409
|
+
});
|
|
410
|
+
(0, vitest_1.it)('should keep namespace structure', () => {
|
|
411
|
+
const result = (0, type_filter_1.filterModelTypes)(modelContent, new Set(['UserVO']), 'Model', 'UserService');
|
|
412
|
+
(0, vitest_1.expect)(result).toContain('declare namespace Model');
|
|
413
|
+
(0, vitest_1.expect)(result).toContain('export namespace UserService');
|
|
414
|
+
(0, vitest_1.expect)(result).toContain('}');
|
|
415
|
+
});
|
|
416
|
+
(0, vitest_1.it)('should remove unused type definitions', () => {
|
|
417
|
+
const result = (0, type_filter_1.filterModelTypes)(modelContent, new Set(['UserVO', 'CreateUserDTO']), 'Model', 'UserService');
|
|
418
|
+
(0, vitest_1.expect)(result).toContain('export interface UserVO');
|
|
419
|
+
(0, vitest_1.expect)(result).toContain('export interface CreateUserDTO');
|
|
420
|
+
(0, vitest_1.expect)(result).not.toContain('UnusedVO');
|
|
421
|
+
});
|
|
422
|
+
(0, vitest_1.it)('should preserve dependency chains', () => {
|
|
423
|
+
const contentWithDeps = `declare namespace Model {
|
|
424
|
+
export namespace OrderService {
|
|
425
|
+
export interface OrderVO {
|
|
426
|
+
id: number
|
|
427
|
+
items: OrderItem[]
|
|
428
|
+
}
|
|
429
|
+
export interface OrderItem {
|
|
430
|
+
productId: number
|
|
431
|
+
productName: string
|
|
432
|
+
}
|
|
433
|
+
export interface UnusedType {
|
|
434
|
+
data: string
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}`;
|
|
438
|
+
const result = (0, type_filter_1.filterModelTypes)(contentWithDeps, new Set(['OrderVO']), 'Model', 'OrderService');
|
|
439
|
+
(0, vitest_1.expect)(result).toContain('export interface OrderVO');
|
|
440
|
+
(0, vitest_1.expect)(result).toContain('export interface OrderItem');
|
|
441
|
+
(0, vitest_1.expect)(result).not.toContain('UnusedType');
|
|
442
|
+
});
|
|
443
|
+
(0, vitest_1.it)('should handle empty used types set', () => {
|
|
444
|
+
const result = (0, type_filter_1.filterModelTypes)(modelContent, new Set(), 'Model', 'UserService');
|
|
445
|
+
// No type defs should be included, only namespace structure
|
|
446
|
+
(0, vitest_1.expect)(result).not.toContain('export interface');
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
// ─── filterUnusedTypes (integration) ─────────────────────────────────
|
|
450
|
+
(0, vitest_1.describe)('filterUnusedTypes', () => {
|
|
451
|
+
(0, vitest_1.beforeEach)(() => {
|
|
452
|
+
vitest_1.vi.clearAllMocks();
|
|
453
|
+
});
|
|
454
|
+
const modelContent = `declare namespace Model {
|
|
455
|
+
export namespace UserService {
|
|
456
|
+
export interface UserVO {
|
|
457
|
+
id: number
|
|
458
|
+
name: string
|
|
459
|
+
}
|
|
460
|
+
export interface CreateUserDTO {
|
|
461
|
+
name: string
|
|
462
|
+
email: string
|
|
463
|
+
}
|
|
464
|
+
export interface UnusedVO {
|
|
465
|
+
data: string
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}`;
|
|
469
|
+
(0, vitest_1.it)('should filter unused types based on index file references', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
470
|
+
const indexContent = `
|
|
471
|
+
const a: Model.UserService.UserVO = {}
|
|
472
|
+
const b: Model.UserService.CreateUserDTO = {}
|
|
473
|
+
`;
|
|
474
|
+
vitest_1.vi.mocked(promises_1.default.readFile).mockImplementation((filePath) => __awaiter(void 0, void 0, void 0, function* () {
|
|
475
|
+
if (filePath.includes('index.ts'))
|
|
476
|
+
return indexContent;
|
|
477
|
+
if (filePath.includes('model.d.ts'))
|
|
478
|
+
return modelContent;
|
|
479
|
+
throw new Error('Unknown file');
|
|
480
|
+
}));
|
|
481
|
+
vitest_1.vi.mocked(promises_1.default.writeFile).mockResolvedValue(undefined);
|
|
482
|
+
yield (0, type_filter_1.filterUnusedTypes)('/output', 'user-service', 'UserService', 'Model');
|
|
483
|
+
(0, vitest_1.expect)(promises_1.default.writeFile).toHaveBeenCalledTimes(1);
|
|
484
|
+
const writtenContent = vitest_1.vi.mocked(promises_1.default.writeFile).mock.calls[0][1];
|
|
485
|
+
(0, vitest_1.expect)(writtenContent).toContain('export interface UserVO');
|
|
486
|
+
(0, vitest_1.expect)(writtenContent).toContain('export interface CreateUserDTO');
|
|
487
|
+
(0, vitest_1.expect)(writtenContent).not.toContain('UnusedVO');
|
|
488
|
+
}));
|
|
489
|
+
(0, vitest_1.it)('should skip when no type refs found for target service', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
490
|
+
const indexContent = 'const a: Model.OrderService.OrderVO = {}';
|
|
491
|
+
vitest_1.vi.mocked(promises_1.default.readFile).mockImplementation((filePath) => __awaiter(void 0, void 0, void 0, function* () {
|
|
492
|
+
if (filePath.includes('index.ts'))
|
|
493
|
+
return indexContent;
|
|
494
|
+
if (filePath.includes('model.d.ts'))
|
|
495
|
+
return modelContent;
|
|
496
|
+
throw new Error('Unknown file');
|
|
497
|
+
}));
|
|
498
|
+
yield (0, type_filter_1.filterUnusedTypes)('/output', 'user-service', 'UserService', 'Model');
|
|
499
|
+
(0, vitest_1.expect)(promises_1.default.writeFile).not.toHaveBeenCalled();
|
|
500
|
+
}));
|
|
501
|
+
(0, vitest_1.it)('should skip when used types set is empty', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
502
|
+
const indexContent = 'const x = 1 // no model refs';
|
|
503
|
+
vitest_1.vi.mocked(promises_1.default.readFile).mockImplementation((filePath) => __awaiter(void 0, void 0, void 0, function* () {
|
|
504
|
+
if (filePath.includes('index.ts'))
|
|
505
|
+
return indexContent;
|
|
506
|
+
if (filePath.includes('model.d.ts'))
|
|
507
|
+
return modelContent;
|
|
508
|
+
throw new Error('Unknown file');
|
|
509
|
+
}));
|
|
510
|
+
yield (0, type_filter_1.filterUnusedTypes)('/output', 'user-service', 'UserService', 'Model');
|
|
511
|
+
(0, vitest_1.expect)(promises_1.default.writeFile).not.toHaveBeenCalled();
|
|
512
|
+
}));
|
|
513
|
+
(0, vitest_1.it)('should handle file read errors gracefully', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
514
|
+
vitest_1.vi.mocked(promises_1.default.readFile).mockRejectedValue(new Error('File not found'));
|
|
515
|
+
// Should not throw
|
|
516
|
+
yield (0, type_filter_1.filterUnusedTypes)('/output', 'user-service', 'UserService', 'Model');
|
|
517
|
+
(0, vitest_1.expect)(promises_1.default.writeFile).not.toHaveBeenCalled();
|
|
518
|
+
}));
|
|
519
|
+
(0, vitest_1.it)('should preserve dependency chains (OrderVO -> OrderItem)', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
520
|
+
const modelWithDeps = `declare namespace Model {
|
|
521
|
+
export namespace OrderService {
|
|
522
|
+
export interface OrderVO {
|
|
523
|
+
id: number
|
|
524
|
+
items: OrderItem[]
|
|
525
|
+
}
|
|
526
|
+
export interface OrderItem {
|
|
527
|
+
productId: number
|
|
528
|
+
productName: string
|
|
529
|
+
}
|
|
530
|
+
export interface UnusedType {
|
|
531
|
+
data: string
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}`;
|
|
535
|
+
const indexContent = 'const order: Model.OrderService.OrderVO = {}';
|
|
536
|
+
vitest_1.vi.mocked(promises_1.default.readFile).mockImplementation((filePath) => __awaiter(void 0, void 0, void 0, function* () {
|
|
537
|
+
if (filePath.includes('index.ts'))
|
|
538
|
+
return indexContent;
|
|
539
|
+
if (filePath.includes('model.d.ts'))
|
|
540
|
+
return modelWithDeps;
|
|
541
|
+
throw new Error('Unknown file');
|
|
542
|
+
}));
|
|
543
|
+
vitest_1.vi.mocked(promises_1.default.writeFile).mockResolvedValue(undefined);
|
|
544
|
+
yield (0, type_filter_1.filterUnusedTypes)('/output', 'order-service', 'OrderService', 'Model');
|
|
545
|
+
(0, vitest_1.expect)(promises_1.default.writeFile).toHaveBeenCalledTimes(1);
|
|
546
|
+
const writtenContent = vitest_1.vi.mocked(promises_1.default.writeFile).mock.calls[0][1];
|
|
547
|
+
(0, vitest_1.expect)(writtenContent).toContain('export interface OrderVO');
|
|
548
|
+
(0, vitest_1.expect)(writtenContent).toContain('export interface OrderItem');
|
|
549
|
+
(0, vitest_1.expect)(writtenContent).not.toContain('UnusedType');
|
|
550
|
+
}));
|
|
551
|
+
(0, vitest_1.it)('should call writeFile with correct file path', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
552
|
+
const indexContent = 'const a: Model.UserService.UserVO = {}';
|
|
553
|
+
vitest_1.vi.mocked(promises_1.default.readFile).mockImplementation((filePath) => __awaiter(void 0, void 0, void 0, function* () {
|
|
554
|
+
if (filePath.includes('index.ts'))
|
|
555
|
+
return indexContent;
|
|
556
|
+
if (filePath.includes('model.d.ts'))
|
|
557
|
+
return modelContent;
|
|
558
|
+
throw new Error('Unknown file');
|
|
559
|
+
}));
|
|
560
|
+
vitest_1.vi.mocked(promises_1.default.writeFile).mockResolvedValue(undefined);
|
|
561
|
+
yield (0, type_filter_1.filterUnusedTypes)('/output', 'user-service', 'UserService', 'Model');
|
|
562
|
+
(0, vitest_1.expect)(promises_1.default.writeFile).toHaveBeenCalledWith(vitest_1.expect.stringContaining('model.d.ts'), vitest_1.expect.any(String), 'utf-8');
|
|
563
|
+
}));
|
|
564
|
+
});
|
package/package.json
CHANGED
package/templates/api.ejs
CHANGED