@player-lang/functional-dsl-generator 0.0.2-next.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.
- package/dist/cjs/index.cjs +2146 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/index.legacy-esm.js +2075 -0
- package/dist/index.mjs +2075 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +38 -0
- package/src/__tests__/__snapshots__/generator.test.ts.snap +886 -0
- package/src/__tests__/builder-class-generator.test.ts +627 -0
- package/src/__tests__/cli.test.ts +685 -0
- package/src/__tests__/default-value-generator.test.ts +365 -0
- package/src/__tests__/generator.test.ts +2860 -0
- package/src/__tests__/import-generator.test.ts +444 -0
- package/src/__tests__/path-utils.test.ts +174 -0
- package/src/__tests__/type-collector.test.ts +674 -0
- package/src/__tests__/type-transformer.test.ts +934 -0
- package/src/__tests__/utils.test.ts +597 -0
- package/src/builder-class-generator.ts +254 -0
- package/src/cli.ts +285 -0
- package/src/default-value-generator.ts +307 -0
- package/src/generator.ts +257 -0
- package/src/import-generator.ts +331 -0
- package/src/index.ts +38 -0
- package/src/path-utils.ts +155 -0
- package/src/ts-morph-type-finder.ts +319 -0
- package/src/type-categorizer.ts +131 -0
- package/src/type-collector.ts +296 -0
- package/src/type-resolver.ts +266 -0
- package/src/type-transformer.ts +487 -0
- package/src/utils.ts +762 -0
- package/types/builder-class-generator.d.ts +56 -0
- package/types/cli.d.ts +6 -0
- package/types/default-value-generator.d.ts +74 -0
- package/types/generator.d.ts +102 -0
- package/types/import-generator.d.ts +77 -0
- package/types/index.d.ts +12 -0
- package/types/path-utils.d.ts +65 -0
- package/types/ts-morph-type-finder.d.ts +73 -0
- package/types/type-categorizer.d.ts +46 -0
- package/types/type-collector.d.ts +62 -0
- package/types/type-resolver.d.ts +49 -0
- package/types/type-transformer.d.ts +74 -0
- package/types/utils.d.ts +205 -0
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach } from "vitest";
|
|
2
|
+
import { ImportGenerator } from "../import-generator";
|
|
3
|
+
|
|
4
|
+
describe("ImportGenerator", () => {
|
|
5
|
+
let generator: ImportGenerator;
|
|
6
|
+
|
|
7
|
+
describe("Basic Configuration", () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
generator = new ImportGenerator();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("generates default import paths", () => {
|
|
13
|
+
const imports = generator.generateImports("TextAsset");
|
|
14
|
+
|
|
15
|
+
// Default path uses kebab-case conversion
|
|
16
|
+
expect(imports).toContain('from "../types/');
|
|
17
|
+
expect(imports).toContain('from "@player-lang/functional-dsl"');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("uses custom functional import path", () => {
|
|
21
|
+
generator = new ImportGenerator({
|
|
22
|
+
functionalImportPath: "../../../gen/common.js",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const imports = generator.generateImports("TextAsset");
|
|
26
|
+
|
|
27
|
+
expect(imports).toContain('from "../../../gen/common.js"');
|
|
28
|
+
expect(imports).not.toContain('from "@player-lang/functional-dsl"');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("uses custom types import path", () => {
|
|
32
|
+
generator = new ImportGenerator({
|
|
33
|
+
typesImportPath: "../custom-types.js",
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
generator.setNeedsAssetImport(true);
|
|
37
|
+
const imports = generator.generateImports("TextAsset");
|
|
38
|
+
|
|
39
|
+
expect(imports).toContain('from "../custom-types.js"');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("uses custom type import path generator", () => {
|
|
43
|
+
generator = new ImportGenerator({
|
|
44
|
+
typeImportPathGenerator: (typeName) =>
|
|
45
|
+
`../types/${typeName.toLowerCase()}.js`,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const imports = generator.generateImports("TextAsset");
|
|
49
|
+
|
|
50
|
+
expect(imports).toContain('from "../types/textasset.js"');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("Type Tracking", () => {
|
|
55
|
+
beforeEach(() => {
|
|
56
|
+
generator = new ImportGenerator();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("tracks referenced types for same file", () => {
|
|
60
|
+
generator = new ImportGenerator({
|
|
61
|
+
sameFileTypes: new Set(["TypeA", "TypeB"]),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
generator.trackReferencedType("TypeA");
|
|
65
|
+
generator.trackReferencedType("TypeB");
|
|
66
|
+
|
|
67
|
+
const imports = generator.generateImports("MainType");
|
|
68
|
+
|
|
69
|
+
expect(imports).toMatch(/import type \{[^}]*MainType[^}]*\}/);
|
|
70
|
+
expect(imports).toMatch(/import type \{[^}]*TypeA[^}]*\}/);
|
|
71
|
+
expect(imports).toMatch(/import type \{[^}]*TypeB[^}]*\}/);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("tracks external types", () => {
|
|
75
|
+
const externalTypes = new Map<string, string>();
|
|
76
|
+
externalTypes.set("ExternalType", "@external/types");
|
|
77
|
+
|
|
78
|
+
generator = new ImportGenerator({ externalTypes });
|
|
79
|
+
|
|
80
|
+
generator.trackReferencedType("ExternalType");
|
|
81
|
+
|
|
82
|
+
const imports = generator.generateImports("MainType");
|
|
83
|
+
|
|
84
|
+
expect(imports).toContain(
|
|
85
|
+
'import type { ExternalType } from "@external/types"',
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("groups multiple types from same external package", () => {
|
|
90
|
+
const externalTypes = new Map<string, string>();
|
|
91
|
+
externalTypes.set("TypeA", "@external/types");
|
|
92
|
+
externalTypes.set("TypeB", "@external/types");
|
|
93
|
+
|
|
94
|
+
generator = new ImportGenerator({ externalTypes });
|
|
95
|
+
|
|
96
|
+
generator.trackReferencedType("TypeA");
|
|
97
|
+
generator.trackReferencedType("TypeB");
|
|
98
|
+
|
|
99
|
+
const imports = generator.generateImports("MainType");
|
|
100
|
+
|
|
101
|
+
// Both types should be in one import statement
|
|
102
|
+
expect(imports).toMatch(
|
|
103
|
+
/import type \{[^}]*TypeA[^}]*TypeB[^}]*\} from "@external\/types"/,
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("external types take precedence over sameFileTypes", () => {
|
|
108
|
+
const sameFileTypes = new Set(["SharedType"]);
|
|
109
|
+
const externalTypes = new Map<string, string>();
|
|
110
|
+
externalTypes.set("SharedType", "@external/shared");
|
|
111
|
+
|
|
112
|
+
generator = new ImportGenerator({ sameFileTypes, externalTypes });
|
|
113
|
+
|
|
114
|
+
generator.trackReferencedType("SharedType");
|
|
115
|
+
|
|
116
|
+
const imports = generator.generateImports("MainType");
|
|
117
|
+
|
|
118
|
+
expect(imports).toContain(
|
|
119
|
+
'import type { SharedType } from "@external/shared"',
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("Namespace Tracking", () => {
|
|
125
|
+
test("tracks namespace imports", () => {
|
|
126
|
+
const externalTypes = new Map<string, string>();
|
|
127
|
+
externalTypes.set("Validation", "@player-ui/types");
|
|
128
|
+
|
|
129
|
+
generator = new ImportGenerator({ externalTypes });
|
|
130
|
+
|
|
131
|
+
generator.trackNamespaceImport("Validation");
|
|
132
|
+
|
|
133
|
+
const imports = generator.generateImports("MainType");
|
|
134
|
+
|
|
135
|
+
expect(imports).toContain(
|
|
136
|
+
'import type { Validation } from "@player-ui/types"',
|
|
137
|
+
);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe("Asset Import", () => {
|
|
142
|
+
beforeEach(() => {
|
|
143
|
+
generator = new ImportGenerator();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("includes Asset import when needed", () => {
|
|
147
|
+
generator.setNeedsAssetImport(true);
|
|
148
|
+
|
|
149
|
+
const imports = generator.generateImports("TextAsset");
|
|
150
|
+
|
|
151
|
+
expect(imports).toContain(
|
|
152
|
+
'import type { Asset } from "@player-ui/types"',
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("does not include Asset import when not needed", () => {
|
|
157
|
+
generator.setNeedsAssetImport(false);
|
|
158
|
+
|
|
159
|
+
const imports = generator.generateImports("TextAsset");
|
|
160
|
+
|
|
161
|
+
expect(imports).not.toContain("import type { Asset }");
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("trackReferencedType for PLAYER_BUILTINS is a no-op", () => {
|
|
165
|
+
// Calling trackReferencedType with PLAYER_BUILTINS should not add them to any imports
|
|
166
|
+
generator.trackReferencedType("Asset");
|
|
167
|
+
generator.trackReferencedType("AssetWrapper");
|
|
168
|
+
generator.trackReferencedType("Binding");
|
|
169
|
+
generator.trackReferencedType("Expression");
|
|
170
|
+
|
|
171
|
+
const imports = generator.generateImports("MainType");
|
|
172
|
+
|
|
173
|
+
// None of these should be imported (they have special handling)
|
|
174
|
+
expect(imports).not.toContain("import type { Asset }");
|
|
175
|
+
expect(imports).not.toMatch(/import type \{[^}]*\bAsset\b[^}]*\} from/);
|
|
176
|
+
expect(imports).not.toMatch(
|
|
177
|
+
/import type \{[^}]*\bAssetWrapper\b[^}]*\} from/,
|
|
178
|
+
);
|
|
179
|
+
expect(imports).not.toMatch(/import type \{[^}]*\bBinding\b[^}]*\} from/);
|
|
180
|
+
expect(imports).not.toMatch(
|
|
181
|
+
/import type \{[^}]*\bExpression\b[^}]*\} from/,
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("Asset only appears via needsAssetImport flag, not trackReferencedType", () => {
|
|
186
|
+
// Track Asset but don't set needsAssetImport
|
|
187
|
+
generator.trackReferencedType("Asset");
|
|
188
|
+
|
|
189
|
+
let imports = generator.generateImports("MainType");
|
|
190
|
+
expect(imports).not.toContain("import type { Asset }");
|
|
191
|
+
|
|
192
|
+
// Now set needsAssetImport - Asset should appear
|
|
193
|
+
generator.setNeedsAssetImport(true);
|
|
194
|
+
imports = generator.generateImports("MainType");
|
|
195
|
+
expect(imports).toContain(
|
|
196
|
+
'import type { Asset } from "@player-ui/types"',
|
|
197
|
+
);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("Asset is not duplicated when both trackReferencedType and needsAssetImport are used", () => {
|
|
201
|
+
generator.trackReferencedType("Asset");
|
|
202
|
+
generator.setNeedsAssetImport(true);
|
|
203
|
+
|
|
204
|
+
const imports = generator.generateImports("MainType");
|
|
205
|
+
|
|
206
|
+
// Count Asset occurrences - should only appear once
|
|
207
|
+
const assetMatches = imports.match(/\bAsset\b/g);
|
|
208
|
+
expect(assetMatches?.length).toBe(1);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe("Import Statement Structure", () => {
|
|
213
|
+
test("generates functional utilities import", () => {
|
|
214
|
+
generator = new ImportGenerator();
|
|
215
|
+
|
|
216
|
+
const imports = generator.generateImports("TextAsset");
|
|
217
|
+
|
|
218
|
+
expect(imports).toContain("FunctionalBuilder");
|
|
219
|
+
expect(imports).toContain("BaseBuildContext");
|
|
220
|
+
expect(imports).toContain("FunctionalBuilderBase");
|
|
221
|
+
expect(imports).toContain("createInspectMethod");
|
|
222
|
+
expect(imports).toContain("TaggedTemplateValue");
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("generates type import for main type", () => {
|
|
226
|
+
generator = new ImportGenerator();
|
|
227
|
+
|
|
228
|
+
const imports = generator.generateImports("TextAsset");
|
|
229
|
+
|
|
230
|
+
expect(imports).toMatch(/import type \{[^}]*TextAsset[^}]*\}/);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe("Type Import Path Generator with sameFileTypes", () => {
|
|
235
|
+
test("uses typeImportPathGenerator for non-same-file types", () => {
|
|
236
|
+
generator = new ImportGenerator({
|
|
237
|
+
sameFileTypes: new Set(["LocalType"]),
|
|
238
|
+
typeImportPathGenerator: (typeName) =>
|
|
239
|
+
`../other/${typeName.toLowerCase()}.js`,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
generator.trackReferencedType("LocalType");
|
|
243
|
+
generator.trackReferencedType("ExternalType");
|
|
244
|
+
|
|
245
|
+
const imports = generator.generateImports("MainType");
|
|
246
|
+
|
|
247
|
+
// LocalType should be with MainType
|
|
248
|
+
expect(imports).toMatch(/import type \{[^}]*LocalType[^}]*\}/);
|
|
249
|
+
|
|
250
|
+
// ExternalType should use the generator
|
|
251
|
+
expect(imports).toContain(
|
|
252
|
+
'import type { ExternalType } from "../other/externaltype.js"',
|
|
253
|
+
);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe("Strip Generic Arguments", () => {
|
|
258
|
+
test("strips generic arguments from type names for imports", () => {
|
|
259
|
+
generator = new ImportGenerator({
|
|
260
|
+
sameFileTypes: new Set(["ListItem"]),
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
generator.trackReferencedType("ListItem<AnyAsset>");
|
|
264
|
+
|
|
265
|
+
const imports = generator.generateImports("MainType");
|
|
266
|
+
|
|
267
|
+
// Should import "ListItem", not "ListItem<AnyAsset>"
|
|
268
|
+
expect(imports).toMatch(/import type \{[^}]*ListItem[^}]*\}/);
|
|
269
|
+
expect(imports).not.toContain("ListItem<AnyAsset>");
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
describe("Namespaced Type Handling", () => {
|
|
274
|
+
test("handles namespaced types from external packages", () => {
|
|
275
|
+
const externalTypes = new Map<string, string>();
|
|
276
|
+
externalTypes.set("Validation", "@player-ui/types");
|
|
277
|
+
|
|
278
|
+
generator = new ImportGenerator({ externalTypes });
|
|
279
|
+
|
|
280
|
+
generator.trackReferencedType("Validation.CrossfieldReference");
|
|
281
|
+
|
|
282
|
+
const imports = generator.generateImports("MainType");
|
|
283
|
+
|
|
284
|
+
// Should import the namespace, not the full path
|
|
285
|
+
expect(imports).toContain(
|
|
286
|
+
'import type { Validation } from "@player-ui/types"',
|
|
287
|
+
);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("defaults namespaced types to @player-ui/types", () => {
|
|
291
|
+
generator = new ImportGenerator();
|
|
292
|
+
|
|
293
|
+
generator.trackReferencedType("Validation.CrossfieldReference");
|
|
294
|
+
|
|
295
|
+
const imports = generator.generateImports("MainType");
|
|
296
|
+
|
|
297
|
+
expect(imports).toContain(
|
|
298
|
+
'import type { Validation } from "@player-ui/types"',
|
|
299
|
+
);
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
describe("TypeTransformContext Interface", () => {
|
|
304
|
+
test("implements getGenericParamSymbols", () => {
|
|
305
|
+
generator = new ImportGenerator();
|
|
306
|
+
const symbols = generator.getGenericParamSymbols();
|
|
307
|
+
|
|
308
|
+
expect(symbols).toBeInstanceOf(Set);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test("implements getNamespaceMemberMap", () => {
|
|
312
|
+
generator = new ImportGenerator();
|
|
313
|
+
const map = generator.getNamespaceMemberMap();
|
|
314
|
+
|
|
315
|
+
expect(map).toBeInstanceOf(Map);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test("implements setNeedsAssetImport and getNeedsAssetImport", () => {
|
|
319
|
+
generator = new ImportGenerator();
|
|
320
|
+
|
|
321
|
+
expect(generator.getNeedsAssetImport()).toBe(false);
|
|
322
|
+
|
|
323
|
+
generator.setNeedsAssetImport(true);
|
|
324
|
+
expect(generator.getNeedsAssetImport()).toBe(true);
|
|
325
|
+
|
|
326
|
+
generator.setNeedsAssetImport(false);
|
|
327
|
+
expect(generator.getNeedsAssetImport()).toBe(false);
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
describe("Scoped Packages", () => {
|
|
332
|
+
test("handles scoped packages (@org/package)", () => {
|
|
333
|
+
const externalTypes = new Map<string, string>();
|
|
334
|
+
externalTypes.set("ScopedType", "@org/package");
|
|
335
|
+
|
|
336
|
+
generator = new ImportGenerator({ externalTypes });
|
|
337
|
+
|
|
338
|
+
generator.trackReferencedType("ScopedType");
|
|
339
|
+
|
|
340
|
+
const imports = generator.generateImports("MainType");
|
|
341
|
+
|
|
342
|
+
expect(imports).toContain(
|
|
343
|
+
'import type { ScopedType } from "@org/package"',
|
|
344
|
+
);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
test("groups multiple types from same scoped package", () => {
|
|
348
|
+
const externalTypes = new Map<string, string>();
|
|
349
|
+
externalTypes.set("TypeA", "@my-org/shared-types");
|
|
350
|
+
externalTypes.set("TypeB", "@my-org/shared-types");
|
|
351
|
+
externalTypes.set("TypeC", "@my-org/shared-types");
|
|
352
|
+
|
|
353
|
+
generator = new ImportGenerator({ externalTypes });
|
|
354
|
+
|
|
355
|
+
generator.trackReferencedType("TypeA");
|
|
356
|
+
generator.trackReferencedType("TypeB");
|
|
357
|
+
generator.trackReferencedType("TypeC");
|
|
358
|
+
|
|
359
|
+
const imports = generator.generateImports("MainType");
|
|
360
|
+
|
|
361
|
+
// All types should be in one import statement
|
|
362
|
+
expect(imports).toMatch(
|
|
363
|
+
/import type \{[^}]*TypeA[^}]*\} from "@my-org\/shared-types"/,
|
|
364
|
+
);
|
|
365
|
+
expect(imports).toMatch(
|
|
366
|
+
/import type \{[^}]*TypeB[^}]*\} from "@my-org\/shared-types"/,
|
|
367
|
+
);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test("handles deeply scoped packages (@org/category/package)", () => {
|
|
371
|
+
const externalTypes = new Map<string, string>();
|
|
372
|
+
externalTypes.set("DeepType", "@org/category/package");
|
|
373
|
+
|
|
374
|
+
generator = new ImportGenerator({ externalTypes });
|
|
375
|
+
|
|
376
|
+
generator.trackReferencedType("DeepType");
|
|
377
|
+
|
|
378
|
+
const imports = generator.generateImports("MainType");
|
|
379
|
+
|
|
380
|
+
expect(imports).toContain(
|
|
381
|
+
'import type { DeepType } from "@org/category/package"',
|
|
382
|
+
);
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
describe("Deduplication", () => {
|
|
387
|
+
test("deduplicates imports from same source", () => {
|
|
388
|
+
generator = new ImportGenerator({
|
|
389
|
+
sameFileTypes: new Set(["TypeA"]),
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// Track the same type multiple times
|
|
393
|
+
generator.trackReferencedType("TypeA");
|
|
394
|
+
generator.trackReferencedType("TypeA");
|
|
395
|
+
generator.trackReferencedType("TypeA");
|
|
396
|
+
|
|
397
|
+
const imports = generator.generateImports("MainType");
|
|
398
|
+
|
|
399
|
+
// TypeA should only appear once in the import
|
|
400
|
+
const typeAMatches = imports.match(/TypeA/g);
|
|
401
|
+
expect(typeAMatches?.length).toBe(1);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test("deduplicates external type imports", () => {
|
|
405
|
+
const externalTypes = new Map<string, string>();
|
|
406
|
+
externalTypes.set("ExternalType", "@external/types");
|
|
407
|
+
|
|
408
|
+
generator = new ImportGenerator({ externalTypes });
|
|
409
|
+
|
|
410
|
+
generator.trackReferencedType("ExternalType");
|
|
411
|
+
generator.trackReferencedType("ExternalType");
|
|
412
|
+
|
|
413
|
+
const imports = generator.generateImports("MainType");
|
|
414
|
+
|
|
415
|
+
// Should only have one import statement for ExternalType
|
|
416
|
+
const importStatements = imports
|
|
417
|
+
.split("\n")
|
|
418
|
+
.filter((line) => line.includes("ExternalType"));
|
|
419
|
+
expect(importStatements.length).toBe(1);
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
describe("Import Ordering", () => {
|
|
424
|
+
test("orders imports consistently", () => {
|
|
425
|
+
const externalTypes = new Map<string, string>();
|
|
426
|
+
externalTypes.set("ZType", "@z-package/types");
|
|
427
|
+
externalTypes.set("AType", "@a-package/types");
|
|
428
|
+
externalTypes.set("MType", "@m-package/types");
|
|
429
|
+
|
|
430
|
+
generator = new ImportGenerator({ externalTypes });
|
|
431
|
+
|
|
432
|
+
generator.trackReferencedType("ZType");
|
|
433
|
+
generator.trackReferencedType("AType");
|
|
434
|
+
generator.trackReferencedType("MType");
|
|
435
|
+
|
|
436
|
+
const imports = generator.generateImports("MainType");
|
|
437
|
+
|
|
438
|
+
// All imports should be present
|
|
439
|
+
expect(imports).toContain("@a-package/types");
|
|
440
|
+
expect(imports).toContain("@m-package/types");
|
|
441
|
+
expect(imports).toContain("@z-package/types");
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
});
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
isNodeModulesPath,
|
|
4
|
+
extractPackageNameFromPath,
|
|
5
|
+
createRelativeImportPath,
|
|
6
|
+
resolveRelativeImportPath,
|
|
7
|
+
} from "../path-utils";
|
|
8
|
+
|
|
9
|
+
describe("path-utils", () => {
|
|
10
|
+
describe("isNodeModulesPath", () => {
|
|
11
|
+
test("returns true for standard node_modules path", () => {
|
|
12
|
+
expect(isNodeModulesPath("/project/node_modules/lodash/index.d.ts")).toBe(
|
|
13
|
+
true,
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("returns true for scoped package in node_modules", () => {
|
|
18
|
+
expect(
|
|
19
|
+
isNodeModulesPath(
|
|
20
|
+
"/project/node_modules/@player-lang/types/index.d.ts",
|
|
21
|
+
),
|
|
22
|
+
).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("returns true for pnpm store path", () => {
|
|
26
|
+
expect(
|
|
27
|
+
isNodeModulesPath(
|
|
28
|
+
"/project/node_modules/.pnpm/@player-lang+types@1.0.0/node_modules/@player-lang/types/index.d.ts",
|
|
29
|
+
),
|
|
30
|
+
).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("returns false for local source file", () => {
|
|
34
|
+
expect(isNodeModulesPath("/project/src/types/foo.ts")).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("returns false for local file with 'modules' in path", () => {
|
|
38
|
+
expect(isNodeModulesPath("/project/src/modules/auth.ts")).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("extractPackageNameFromPath", () => {
|
|
43
|
+
test("extracts simple package name", () => {
|
|
44
|
+
expect(
|
|
45
|
+
extractPackageNameFromPath("/project/node_modules/lodash/index.d.ts"),
|
|
46
|
+
).toBe("lodash");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("extracts scoped package name", () => {
|
|
50
|
+
expect(
|
|
51
|
+
extractPackageNameFromPath(
|
|
52
|
+
"/project/node_modules/@player-lang/types/index.d.ts",
|
|
53
|
+
),
|
|
54
|
+
).toBe("@player-lang/types");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("extracts package from pnpm store structure", () => {
|
|
58
|
+
expect(
|
|
59
|
+
extractPackageNameFromPath(
|
|
60
|
+
"/project/node_modules/.pnpm/@player-lang+types@1.0.0/node_modules/@player-lang/types/index.d.ts",
|
|
61
|
+
),
|
|
62
|
+
).toBe("@player-lang/types");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("extracts package from nested pnpm path", () => {
|
|
66
|
+
expect(
|
|
67
|
+
extractPackageNameFromPath(
|
|
68
|
+
"/project/node_modules/.pnpm/react@18.2.0/node_modules/react/index.d.ts",
|
|
69
|
+
),
|
|
70
|
+
).toBe("react");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("handles deeply nested scoped package", () => {
|
|
74
|
+
expect(
|
|
75
|
+
extractPackageNameFromPath(
|
|
76
|
+
"/project/node_modules/@babel/core/lib/index.d.ts",
|
|
77
|
+
),
|
|
78
|
+
).toBe("@babel/core");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("returns null for local path without node_modules", () => {
|
|
82
|
+
expect(extractPackageNameFromPath("/project/src/types/foo.ts")).toBe(
|
|
83
|
+
null,
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("returns null for path ending at node_modules", () => {
|
|
88
|
+
expect(extractPackageNameFromPath("/project/node_modules")).toBe(null);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe("createRelativeImportPath", () => {
|
|
93
|
+
test("creates relative path for same directory", () => {
|
|
94
|
+
const result = createRelativeImportPath(
|
|
95
|
+
"/project/src/types/foo.ts",
|
|
96
|
+
"/project/src/types/bar.ts",
|
|
97
|
+
);
|
|
98
|
+
expect(result).toBe("./bar.js");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("creates relative path for parent directory", () => {
|
|
102
|
+
const result = createRelativeImportPath(
|
|
103
|
+
"/project/src/builders/foo.ts",
|
|
104
|
+
"/project/src/types/bar.ts",
|
|
105
|
+
);
|
|
106
|
+
expect(result).toBe("../types/bar.js");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("creates relative path for deeply nested file", () => {
|
|
110
|
+
const result = createRelativeImportPath(
|
|
111
|
+
"/project/src/a/b/c/foo.ts",
|
|
112
|
+
"/project/src/x/y/bar.ts",
|
|
113
|
+
);
|
|
114
|
+
expect(result).toBe("../../../x/y/bar.js");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("converts .ts extension to .js", () => {
|
|
118
|
+
const result = createRelativeImportPath(
|
|
119
|
+
"/project/src/foo.ts",
|
|
120
|
+
"/project/src/bar.ts",
|
|
121
|
+
);
|
|
122
|
+
expect(result).toContain(".js");
|
|
123
|
+
expect(result).not.toContain(".ts");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("converts .d.ts extension to .js", () => {
|
|
127
|
+
const result = createRelativeImportPath(
|
|
128
|
+
"/project/src/foo.ts",
|
|
129
|
+
"/project/src/bar.d.ts",
|
|
130
|
+
);
|
|
131
|
+
expect(result).toContain(".js");
|
|
132
|
+
expect(result).not.toContain(".d.ts");
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe("resolveRelativeImportPath", () => {
|
|
137
|
+
test("resolves relative import to absolute path", () => {
|
|
138
|
+
const result = resolveRelativeImportPath(
|
|
139
|
+
"/project/src/types/foo.ts",
|
|
140
|
+
"./bar",
|
|
141
|
+
);
|
|
142
|
+
expect(result).toBe("/project/src/types/bar.ts");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("resolves parent directory import", () => {
|
|
146
|
+
const result = resolveRelativeImportPath(
|
|
147
|
+
"/project/src/builders/foo.ts",
|
|
148
|
+
"../types/bar",
|
|
149
|
+
);
|
|
150
|
+
expect(result).toBe("/project/src/types/bar.ts");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("converts .js extension to .ts", () => {
|
|
154
|
+
const result = resolveRelativeImportPath(
|
|
155
|
+
"/project/src/foo.ts",
|
|
156
|
+
"./bar.js",
|
|
157
|
+
);
|
|
158
|
+
expect(result).toBe("/project/src/bar.ts");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("preserves .ts extension", () => {
|
|
162
|
+
const result = resolveRelativeImportPath(
|
|
163
|
+
"/project/src/foo.ts",
|
|
164
|
+
"./bar.ts",
|
|
165
|
+
);
|
|
166
|
+
expect(result).toBe("/project/src/bar.ts");
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("adds .ts extension if no extension provided", () => {
|
|
170
|
+
const result = resolveRelativeImportPath("/project/src/foo.ts", "./bar");
|
|
171
|
+
expect(result).toMatch(/\.ts$/);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
});
|