@nestia/migrate 12.0.0-rc.2 → 12.0.0-rc.3

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.
Files changed (66) hide show
  1. package/LICENSE +21 -21
  2. package/lib/_virtual/_tstl.mjs +2 -2
  3. package/lib/_virtual/index.mjs +2 -2
  4. package/lib/_virtual/index2.mjs +2 -2
  5. package/lib/_virtual/index3.mjs +2 -2
  6. package/lib/archivers/NestiaMigrateFileArchiver2.mjs +2 -2
  7. package/lib/archivers/NestiaMigrateFileArchiver2.mjs.map +1 -1
  8. package/lib/bundles/NEST_TEMPLATE.js +47 -47
  9. package/lib/bundles/NEST_TEMPLATE.js.map +1 -1
  10. package/lib/bundles/NEST_TEMPLATE2.mjs +47 -47
  11. package/lib/bundles/NEST_TEMPLATE2.mjs.map +1 -1
  12. package/lib/bundles/SDK_TEMPLATE.js +20 -20
  13. package/lib/bundles/SDK_TEMPLATE.js.map +1 -1
  14. package/lib/bundles/SDK_TEMPLATE2.mjs +20 -20
  15. package/lib/bundles/SDK_TEMPLATE2.mjs.map +1 -1
  16. package/lib/index2.mjs +1 -1
  17. package/lib/module2.mjs +2 -2
  18. package/lib/programmers/NestiaMigrateApiProgrammer2.mjs +4 -4
  19. package/lib/programmers/NestiaMigrateApiProgrammer2.mjs.map +1 -1
  20. package/lib/programmers/index2.mjs +1 -1
  21. package/lib/structures/index2.mjs +1 -1
  22. package/package.json +6 -6
  23. package/src/NestiaMigrateApplication.ts +196 -196
  24. package/src/analyzers/NestiaMigrateControllerAnalyzer.ts +51 -51
  25. package/src/archivers/NestiaMigrateFileArchiver.ts +28 -28
  26. package/src/bundles/NEST_TEMPLATE.ts +47 -47
  27. package/src/bundles/SDK_TEMPLATE.ts +20 -20
  28. package/src/executable/NestiaMigrateCommander.ts +115 -115
  29. package/src/executable/NestiaMigrateInquirer.ts +106 -106
  30. package/src/executable/bundle.js +323 -349
  31. package/src/executable/migrate.ts +7 -7
  32. package/src/factories/ExpressionFactory.ts +23 -23
  33. package/src/factories/FormatCheatSheet.ts +71 -71
  34. package/src/factories/IdentifierFactory.ts +84 -84
  35. package/src/factories/LiteralFactory.ts +54 -54
  36. package/src/factories/StatementFactory.ts +56 -56
  37. package/src/factories/TypeFactory.ts +27 -27
  38. package/src/factories/TypeLiteralFactory.ts +62 -62
  39. package/src/index.ts +4 -4
  40. package/src/internal/ts.ts +75 -75
  41. package/src/module.ts +6 -6
  42. package/src/programmers/NestiaMigrateApiFileProgrammer.ts +55 -55
  43. package/src/programmers/NestiaMigrateApiFunctionProgrammer.ts +358 -358
  44. package/src/programmers/NestiaMigrateApiNamespaceProgrammer.ts +511 -511
  45. package/src/programmers/NestiaMigrateApiProgrammer.ts +108 -108
  46. package/src/programmers/NestiaMigrateApiSimulationProgrammer.ts +309 -309
  47. package/src/programmers/NestiaMigrateApiStartProgrammer.ts +198 -198
  48. package/src/programmers/NestiaMigrateDtoProgrammer.ts +117 -117
  49. package/src/programmers/NestiaMigrateE2eFileProgrammer.ts +155 -155
  50. package/src/programmers/NestiaMigrateE2eProgrammer.ts +48 -48
  51. package/src/programmers/NestiaMigrateImportProgrammer.ts +119 -119
  52. package/src/programmers/NestiaMigrateNestControllerProgrammer.ts +70 -70
  53. package/src/programmers/NestiaMigrateNestMethodProgrammer.ts +402 -402
  54. package/src/programmers/NestiaMigrateNestModuleProgrammer.ts +64 -64
  55. package/src/programmers/NestiaMigrateNestProgrammer.ts +89 -89
  56. package/src/programmers/NestiaMigrateSchemaProgrammer.ts +475 -475
  57. package/src/programmers/index.ts +15 -15
  58. package/src/structures/INestiaMigrateConfig.ts +19 -19
  59. package/src/structures/INestiaMigrateContext.ts +9 -9
  60. package/src/structures/INestiaMigrateController.ts +8 -8
  61. package/src/structures/INestiaMigrateFile.ts +5 -5
  62. package/src/structures/index.ts +4 -4
  63. package/src/utils/FilePrinter.ts +44 -44
  64. package/src/utils/MapUtil.ts +13 -13
  65. package/src/utils/StringUtil.ts +109 -109
  66. package/README.md +0 -93
@@ -1,475 +1,475 @@
1
- import { SyntaxKind, factory } from "@ttsc/factory";
2
- import * as typiaUtils from "@typia/utils";
3
- import type { OpenApi } from "typia";
4
-
5
- import { FormatCheatSheet } from "../factories/FormatCheatSheet";
6
- import { TypeFactory } from "../factories/TypeFactory";
7
- import ts from "../internal/ts";
8
- import { FilePrinter } from "../utils/FilePrinter";
9
- import { StringUtil } from "../utils/StringUtil";
10
- import { NestiaMigrateImportProgrammer } from "./NestiaMigrateImportProgrammer";
11
-
12
- const { NamingConvention, OpenApiTypeChecker } =
13
- (typiaUtils as { default?: typeof typiaUtils }).default ?? typiaUtils;
14
-
15
- export namespace NestiaMigrateSchemaProgrammer {
16
- /* -----------------------------------------------------------
17
- FACADE
18
- ----------------------------------------------------------- */
19
- export const write = (props: {
20
- components: OpenApi.IComponents;
21
- importer: NestiaMigrateImportProgrammer;
22
- schema: OpenApi.IJsonSchema;
23
- }): ts.TypeNode => {
24
- // CONSIDER ANY TYPE CASE
25
- const union: ts.TypeNode[] = [];
26
- if (OpenApiTypeChecker.isUnknown(props.schema))
27
- return TypeFactory.keyword("any");
28
-
29
- // ITERATION
30
- const type: ts.TypeNode = (() => {
31
- // ATOMIC
32
- if (OpenApiTypeChecker.isConstant(props.schema))
33
- return writeConstant({
34
- importer: props.importer,
35
- schema: props.schema,
36
- });
37
- else if (OpenApiTypeChecker.isBoolean(props.schema))
38
- return writeBoolean();
39
- else if (OpenApiTypeChecker.isInteger(props.schema))
40
- return writeInteger({
41
- importer: props.importer,
42
- schema: props.schema,
43
- });
44
- else if (OpenApiTypeChecker.isNumber(props.schema))
45
- return writeNumber({
46
- importer: props.importer,
47
- schema: props.schema,
48
- });
49
- else if (OpenApiTypeChecker.isString(props.schema))
50
- return writeString({
51
- importer: props.importer,
52
- schema: props.schema,
53
- });
54
- // INSTANCES
55
- else if (OpenApiTypeChecker.isArray(props.schema))
56
- return writeArray({
57
- components: props.components,
58
- importer: props.importer,
59
- schema: props.schema,
60
- });
61
- else if (OpenApiTypeChecker.isTuple(props.schema))
62
- return writeTuple({
63
- components: props.components,
64
- importer: props.importer,
65
- schema: props.schema,
66
- });
67
- else if (OpenApiTypeChecker.isObject(props.schema))
68
- return writeObject({
69
- components: props.components,
70
- importer: props.importer,
71
- schema: props.schema,
72
- });
73
- else if (OpenApiTypeChecker.isReference(props.schema))
74
- return writeReference({
75
- importer: props.importer,
76
- schema: props.schema,
77
- });
78
- // UNION
79
- else if (OpenApiTypeChecker.isOneOf(props.schema))
80
- return writeUnion({
81
- components: props.components,
82
- importer: props.importer,
83
- elements: props.schema.oneOf,
84
- });
85
- else if (OpenApiTypeChecker.isNull(props.schema))
86
- return createNode("null");
87
- else return TypeFactory.keyword("any");
88
- })();
89
- union.push(type);
90
-
91
- // DETERMINE
92
- if (union.length === 0) return TypeFactory.keyword("any");
93
- else if (union.length === 1) return union[0]!;
94
- return factory.createUnionTypeNode(union);
95
- };
96
-
97
- /* -----------------------------------------------------------
98
- ATOMICS
99
- ----------------------------------------------------------- */
100
- const writeConstant = (props: {
101
- importer: NestiaMigrateImportProgrammer;
102
- schema: OpenApi.IJsonSchema.IConstant;
103
- }): ts.TypeNode => {
104
- return factory.createLiteralTypeNode(
105
- typeof props.schema.const === "boolean"
106
- ? props.schema.const === true
107
- ? factory.createTrue()
108
- : factory.createFalse()
109
- : typeof props.schema.const === "number"
110
- ? props.schema.const < 0
111
- ? factory.createPrefixUnaryExpression(
112
- SyntaxKind.MinusToken,
113
- factory.createNumericLiteral(-props.schema.const),
114
- )
115
- : factory.createNumericLiteral(props.schema.const)
116
- : factory.createStringLiteral(props.schema.const),
117
- );
118
- };
119
-
120
- const writeBoolean = (): ts.TypeNode => TypeFactory.keyword("boolean");
121
-
122
- const writeInteger = (props: {
123
- importer: NestiaMigrateImportProgrammer;
124
- schema: OpenApi.IJsonSchema.IInteger;
125
- }): ts.TypeNode =>
126
- writeNumeric({
127
- factory: () => [
128
- TypeFactory.keyword("number"),
129
- props.importer.tag("Type", "int32"),
130
- ],
131
- importer: props.importer,
132
- schema: props.schema,
133
- });
134
-
135
- const writeNumber = (props: {
136
- importer: NestiaMigrateImportProgrammer;
137
- schema: OpenApi.IJsonSchema.INumber;
138
- }): ts.TypeNode =>
139
- writeNumeric({
140
- factory: () => [TypeFactory.keyword("number")],
141
- importer: props.importer,
142
- schema: props.schema,
143
- });
144
-
145
- const writeNumeric = (props: {
146
- factory: () => ts.TypeNode[];
147
- importer: NestiaMigrateImportProgrammer;
148
- schema: OpenApi.IJsonSchema.IInteger | OpenApi.IJsonSchema.INumber;
149
- }): ts.TypeNode => {
150
- const intersection: ts.TypeNode[] = props.factory();
151
- if (props.schema.default !== undefined)
152
- intersection.push(props.importer.tag("Default", props.schema.default));
153
- if (props.schema.minimum !== undefined)
154
- intersection.push(
155
- props.importer.tag(
156
- props.schema.exclusiveMinimum ? "ExclusiveMinimum" : "Minimum",
157
- props.schema.minimum,
158
- ),
159
- );
160
- if (props.schema.maximum !== undefined)
161
- intersection.push(
162
- props.importer.tag(
163
- props.schema.exclusiveMaximum ? "ExclusiveMaximum" : "Maximum",
164
- props.schema.maximum,
165
- ),
166
- );
167
- if (props.schema.multipleOf !== undefined)
168
- intersection.push(
169
- props.importer.tag("MultipleOf", props.schema.multipleOf),
170
- );
171
- return intersection.length === 1
172
- ? intersection[0]!
173
- : factory.createIntersectionTypeNode(intersection);
174
- };
175
-
176
- const writeString = (props: {
177
- importer: NestiaMigrateImportProgrammer;
178
- schema: OpenApi.IJsonSchema.IString;
179
- }): ts.TypeNode => {
180
- if (props.schema.format === "binary")
181
- return factory.createTypeReferenceNode("File");
182
-
183
- const intersection: ts.TypeNode[] = [TypeFactory.keyword("string")];
184
- if (props.schema.default !== undefined)
185
- intersection.push(props.importer.tag("Default", props.schema.default));
186
- if (props.schema.minLength !== undefined)
187
- intersection.push(
188
- props.importer.tag("MinLength", props.schema.minLength),
189
- );
190
- if (props.schema.maxLength !== undefined)
191
- intersection.push(
192
- props.importer.tag("MaxLength", props.schema.maxLength),
193
- );
194
- if (props.schema.pattern !== undefined)
195
- intersection.push(props.importer.tag("Pattern", props.schema.pattern));
196
- if (
197
- props.schema.format !== undefined &&
198
- (FormatCheatSheet as Record<string, string>)[props.schema.format] !==
199
- undefined
200
- )
201
- intersection.push(props.importer.tag("Format", props.schema.format));
202
- if (props.schema.contentMediaType !== undefined)
203
- intersection.push(
204
- props.importer.tag("ContentMediaType", props.schema.contentMediaType),
205
- );
206
- return intersection.length === 1
207
- ? intersection[0]!
208
- : factory.createIntersectionTypeNode(intersection);
209
- };
210
-
211
- /* -----------------------------------------------------------
212
- INSTANCES
213
- ----------------------------------------------------------- */
214
- const writeArray = (props: {
215
- components: OpenApi.IComponents;
216
- importer: NestiaMigrateImportProgrammer;
217
- schema: OpenApi.IJsonSchema.IArray;
218
- }): ts.TypeNode => {
219
- const intersection: ts.TypeNode[] = [
220
- factory.createArrayTypeNode(
221
- write({
222
- components: props.components,
223
- importer: props.importer,
224
- schema: props.schema.items,
225
- }),
226
- ),
227
- ];
228
- if (props.schema.minItems !== undefined)
229
- intersection.push(props.importer.tag("MinItems", props.schema.minItems));
230
- if (props.schema.maxItems !== undefined)
231
- intersection.push(props.importer.tag("MaxItems", props.schema.maxItems));
232
- if (props.schema.uniqueItems === true)
233
- intersection.push(props.importer.tag("UniqueItems"));
234
- return intersection.length === 1
235
- ? intersection[0]!
236
- : factory.createIntersectionTypeNode(intersection);
237
- };
238
-
239
- const writeTuple = (props: {
240
- components: OpenApi.IComponents;
241
- importer: NestiaMigrateImportProgrammer;
242
- schema: OpenApi.IJsonSchema.ITuple;
243
- }): ts.TypeNode =>
244
- factory.createTupleTypeNode([
245
- ...props.schema.prefixItems.map((item) =>
246
- write({
247
- components: props.components,
248
- importer: props.importer,
249
- schema: item,
250
- }),
251
- ),
252
- ...(typeof props.schema.additionalItems === "object" &&
253
- props.schema.additionalItems !== null
254
- ? [
255
- factory.createRestTypeNode(
256
- write({
257
- components: props.components,
258
- importer: props.importer,
259
- schema: props.schema.additionalItems,
260
- }),
261
- ),
262
- ]
263
- : props.schema.additionalItems === true
264
- ? [
265
- factory.createRestTypeNode(
266
- factory.createArrayTypeNode(
267
- factory.createKeywordTypeNode(SyntaxKind.AnyKeyword),
268
- ),
269
- ),
270
- ]
271
- : []),
272
- ]);
273
-
274
- const writeObject = (props: {
275
- components: OpenApi.IComponents;
276
- importer: NestiaMigrateImportProgrammer;
277
- schema: OpenApi.IJsonSchema.IObject;
278
- }): ts.TypeNode => {
279
- const regular = () =>
280
- factory.createTypeLiteralNode(
281
- Object.entries(props.schema.properties ?? [])
282
- .map(([key, value], index) => [
283
- ...(index !== 0 &&
284
- (!!value.title?.length || !!value.description?.length)
285
- ? [factory.createIdentifier("\n") as any]
286
- : []),
287
- writeRegularProperty({
288
- components: props.components,
289
- importer: props.importer,
290
- required: props.schema.required ?? [],
291
- key,
292
- value,
293
- }),
294
- ])
295
- .flat(),
296
- );
297
- const dynamic = () =>
298
- factory.createTypeLiteralNode([
299
- writeDynamicProperty({
300
- components: props.components,
301
- importer: props.importer,
302
- schema: props.schema.additionalProperties as OpenApi.IJsonSchema,
303
- }),
304
- ]);
305
- return !!props.schema.properties?.length &&
306
- typeof props.schema.additionalProperties === "object"
307
- ? factory.createIntersectionTypeNode([regular(), dynamic()])
308
- : typeof props.schema.additionalProperties === "object"
309
- ? dynamic()
310
- : regular();
311
- };
312
-
313
- const writeRegularProperty = (props: {
314
- components: OpenApi.IComponents;
315
- importer: NestiaMigrateImportProgrammer;
316
- required: string[];
317
- key: string;
318
- value: OpenApi.IJsonSchema;
319
- }) => {
320
- const valueTypeNode: ts.TypeNode = write({
321
- components: props.components,
322
- importer: props.importer,
323
- schema: props.value,
324
- });
325
- return FilePrinter.description(
326
- factory.createPropertySignature(
327
- props.value.readOnly
328
- ? [factory.createToken(SyntaxKind.ReadonlyKeyword)]
329
- : undefined,
330
- NamingConvention.variable(props.key)
331
- ? factory.createIdentifier(props.key)
332
- : factory.createStringLiteral(props.key),
333
- props.required.includes(props.key)
334
- ? undefined
335
- : factory.createToken(SyntaxKind.QuestionToken),
336
- props.required.includes(props.key)
337
- ? valueTypeNode
338
- : ts.isUnionTypeNode(valueTypeNode)
339
- ? factory.createUnionTypeNode([
340
- ...(valueTypeNode.types ?? []),
341
- factory.createTypeReferenceNode("undefined"),
342
- ])
343
- : factory.createUnionTypeNode([
344
- valueTypeNode,
345
- factory.createTypeReferenceNode("undefined"),
346
- ]),
347
- ),
348
- writeComment(props.value),
349
- );
350
- };
351
-
352
- const writeDynamicProperty = (props: {
353
- components: OpenApi.IComponents;
354
- importer: NestiaMigrateImportProgrammer;
355
- schema: OpenApi.IJsonSchema;
356
- }) =>
357
- FilePrinter.description(
358
- factory.createIndexSignature(
359
- undefined,
360
- [
361
- factory.createParameterDeclaration(
362
- undefined,
363
- undefined,
364
- factory.createIdentifier("key"),
365
- undefined,
366
- TypeFactory.keyword("string"),
367
- ),
368
- ],
369
- write(props),
370
- ),
371
- writeComment(props.schema),
372
- );
373
-
374
- const writeReference = (props: {
375
- importer: NestiaMigrateImportProgrammer;
376
- schema: OpenApi.IJsonSchema.IReference;
377
- }): ts.TypeReferenceNode | ts.KeywordTypeNode => {
378
- if (props.schema.$ref.startsWith("#/components/schemas") === false)
379
- return TypeFactory.keyword("any");
380
- const name: string = props.schema.$ref
381
- .split("/")
382
- .slice(3)
383
- .filter((str) => str.length !== 0)
384
- .map(StringUtil.escapeNonVariable)
385
- .join("");
386
- if (name === "") return TypeFactory.keyword("any");
387
- return props.importer.dto(name);
388
- };
389
-
390
- /* -----------------------------------------------------------
391
- UNIONS
392
- ----------------------------------------------------------- */
393
- const writeUnion = (props: {
394
- components: OpenApi.IComponents;
395
- importer: NestiaMigrateImportProgrammer;
396
- elements: OpenApi.IJsonSchema[];
397
- }): ts.UnionTypeNode =>
398
- factory.createUnionTypeNode(
399
- props.elements.map((schema) =>
400
- write({
401
- components: props.components,
402
- importer: props.importer,
403
- schema,
404
- }),
405
- ),
406
- );
407
- }
408
- const createNode = (text: string) => factory.createTypeReferenceNode(text);
409
-
410
- const writeComment = (schema: OpenApi.IJsonSchema): string => {
411
- interface IPlugin {
412
- key: string;
413
- value: undefined | string | number | boolean;
414
- }
415
- const plugins: IPlugin[] = [];
416
- if (schema.title !== undefined)
417
- plugins.push({
418
- key: "title",
419
- value: schema.title,
420
- });
421
- if (schema.deprecated === true)
422
- plugins.push({
423
- key: "deprecated",
424
- value: undefined,
425
- });
426
- for (const [key, value] of Object.entries(schema))
427
- if (key.startsWith("x-") && isExtensionValue(value))
428
- plugins.push({
429
- key,
430
- value,
431
- });
432
- return [
433
- ...(schema.description?.length
434
- ? [eraseCommentTags(schema.description)]
435
- : []),
436
- ...(schema.description?.length && plugins.length !== 0 ? [""] : []),
437
- ...plugins.map((p) =>
438
- p.value === undefined ? `@${p.key}` : `@${p.key} ${String(p.value)}`,
439
- ),
440
- ].join("\n");
441
- };
442
-
443
- const isExtensionValue = (value: unknown): value is boolean | number | string =>
444
- typeof value === "boolean" ||
445
- typeof value === "number" ||
446
- typeof value === "string";
447
-
448
- const eraseCommentTags = (description: string): string => {
449
- const lines: string[] = description.split("\n");
450
- return lines
451
- .filter((s) => COMMENT_TAGS.every((tag) => !s.includes(tag)))
452
- .join("\n");
453
- };
454
-
455
- const COMMENT_TAGS = [
456
- // string
457
- "@format",
458
- "@pattern",
459
- "@length",
460
- "@minLength",
461
- "@maxLength",
462
- "@contentMediaType",
463
- // number
464
- "@type",
465
- "@minimum",
466
- "@maximum",
467
- "@exclusiveMinimum",
468
- "@exclusiveMaximum",
469
- "@multipleOf",
470
- // array
471
- "@items",
472
- "@minItems",
473
- "@maxItems",
474
- "@uniqueItems",
475
- ];
1
+ import { SyntaxKind, factory } from "@ttsc/factory";
2
+ import * as typiaUtils from "@typia/utils";
3
+ import type { OpenApi } from "typia";
4
+
5
+ import { FormatCheatSheet } from "../factories/FormatCheatSheet";
6
+ import { TypeFactory } from "../factories/TypeFactory";
7
+ import ts from "../internal/ts";
8
+ import { FilePrinter } from "../utils/FilePrinter";
9
+ import { StringUtil } from "../utils/StringUtil";
10
+ import { NestiaMigrateImportProgrammer } from "./NestiaMigrateImportProgrammer";
11
+
12
+ const { NamingConvention, OpenApiTypeChecker } =
13
+ (typiaUtils as { default?: typeof typiaUtils }).default ?? typiaUtils;
14
+
15
+ export namespace NestiaMigrateSchemaProgrammer {
16
+ /* -----------------------------------------------------------
17
+ FACADE
18
+ ----------------------------------------------------------- */
19
+ export const write = (props: {
20
+ components: OpenApi.IComponents;
21
+ importer: NestiaMigrateImportProgrammer;
22
+ schema: OpenApi.IJsonSchema;
23
+ }): ts.TypeNode => {
24
+ // CONSIDER ANY TYPE CASE
25
+ const union: ts.TypeNode[] = [];
26
+ if (OpenApiTypeChecker.isUnknown(props.schema))
27
+ return TypeFactory.keyword("any");
28
+
29
+ // ITERATION
30
+ const type: ts.TypeNode = (() => {
31
+ // ATOMIC
32
+ if (OpenApiTypeChecker.isConstant(props.schema))
33
+ return writeConstant({
34
+ importer: props.importer,
35
+ schema: props.schema,
36
+ });
37
+ else if (OpenApiTypeChecker.isBoolean(props.schema))
38
+ return writeBoolean();
39
+ else if (OpenApiTypeChecker.isInteger(props.schema))
40
+ return writeInteger({
41
+ importer: props.importer,
42
+ schema: props.schema,
43
+ });
44
+ else if (OpenApiTypeChecker.isNumber(props.schema))
45
+ return writeNumber({
46
+ importer: props.importer,
47
+ schema: props.schema,
48
+ });
49
+ else if (OpenApiTypeChecker.isString(props.schema))
50
+ return writeString({
51
+ importer: props.importer,
52
+ schema: props.schema,
53
+ });
54
+ // INSTANCES
55
+ else if (OpenApiTypeChecker.isArray(props.schema))
56
+ return writeArray({
57
+ components: props.components,
58
+ importer: props.importer,
59
+ schema: props.schema,
60
+ });
61
+ else if (OpenApiTypeChecker.isTuple(props.schema))
62
+ return writeTuple({
63
+ components: props.components,
64
+ importer: props.importer,
65
+ schema: props.schema,
66
+ });
67
+ else if (OpenApiTypeChecker.isObject(props.schema))
68
+ return writeObject({
69
+ components: props.components,
70
+ importer: props.importer,
71
+ schema: props.schema,
72
+ });
73
+ else if (OpenApiTypeChecker.isReference(props.schema))
74
+ return writeReference({
75
+ importer: props.importer,
76
+ schema: props.schema,
77
+ });
78
+ // UNION
79
+ else if (OpenApiTypeChecker.isOneOf(props.schema))
80
+ return writeUnion({
81
+ components: props.components,
82
+ importer: props.importer,
83
+ elements: props.schema.oneOf,
84
+ });
85
+ else if (OpenApiTypeChecker.isNull(props.schema))
86
+ return createNode("null");
87
+ else return TypeFactory.keyword("any");
88
+ })();
89
+ union.push(type);
90
+
91
+ // DETERMINE
92
+ if (union.length === 0) return TypeFactory.keyword("any");
93
+ else if (union.length === 1) return union[0]!;
94
+ return factory.createUnionTypeNode(union);
95
+ };
96
+
97
+ /* -----------------------------------------------------------
98
+ ATOMICS
99
+ ----------------------------------------------------------- */
100
+ const writeConstant = (props: {
101
+ importer: NestiaMigrateImportProgrammer;
102
+ schema: OpenApi.IJsonSchema.IConstant;
103
+ }): ts.TypeNode => {
104
+ return factory.createLiteralTypeNode(
105
+ typeof props.schema.const === "boolean"
106
+ ? props.schema.const === true
107
+ ? factory.createTrue()
108
+ : factory.createFalse()
109
+ : typeof props.schema.const === "number"
110
+ ? props.schema.const < 0
111
+ ? factory.createPrefixUnaryExpression(
112
+ SyntaxKind.MinusToken,
113
+ factory.createNumericLiteral(-props.schema.const),
114
+ )
115
+ : factory.createNumericLiteral(props.schema.const)
116
+ : factory.createStringLiteral(props.schema.const),
117
+ );
118
+ };
119
+
120
+ const writeBoolean = (): ts.TypeNode => TypeFactory.keyword("boolean");
121
+
122
+ const writeInteger = (props: {
123
+ importer: NestiaMigrateImportProgrammer;
124
+ schema: OpenApi.IJsonSchema.IInteger;
125
+ }): ts.TypeNode =>
126
+ writeNumeric({
127
+ factory: () => [
128
+ TypeFactory.keyword("number"),
129
+ props.importer.tag("Type", "int32"),
130
+ ],
131
+ importer: props.importer,
132
+ schema: props.schema,
133
+ });
134
+
135
+ const writeNumber = (props: {
136
+ importer: NestiaMigrateImportProgrammer;
137
+ schema: OpenApi.IJsonSchema.INumber;
138
+ }): ts.TypeNode =>
139
+ writeNumeric({
140
+ factory: () => [TypeFactory.keyword("number")],
141
+ importer: props.importer,
142
+ schema: props.schema,
143
+ });
144
+
145
+ const writeNumeric = (props: {
146
+ factory: () => ts.TypeNode[];
147
+ importer: NestiaMigrateImportProgrammer;
148
+ schema: OpenApi.IJsonSchema.IInteger | OpenApi.IJsonSchema.INumber;
149
+ }): ts.TypeNode => {
150
+ const intersection: ts.TypeNode[] = props.factory();
151
+ if (props.schema.default !== undefined)
152
+ intersection.push(props.importer.tag("Default", props.schema.default));
153
+ if (props.schema.minimum !== undefined)
154
+ intersection.push(
155
+ props.importer.tag(
156
+ props.schema.exclusiveMinimum ? "ExclusiveMinimum" : "Minimum",
157
+ props.schema.minimum,
158
+ ),
159
+ );
160
+ if (props.schema.maximum !== undefined)
161
+ intersection.push(
162
+ props.importer.tag(
163
+ props.schema.exclusiveMaximum ? "ExclusiveMaximum" : "Maximum",
164
+ props.schema.maximum,
165
+ ),
166
+ );
167
+ if (props.schema.multipleOf !== undefined)
168
+ intersection.push(
169
+ props.importer.tag("MultipleOf", props.schema.multipleOf),
170
+ );
171
+ return intersection.length === 1
172
+ ? intersection[0]!
173
+ : factory.createIntersectionTypeNode(intersection);
174
+ };
175
+
176
+ const writeString = (props: {
177
+ importer: NestiaMigrateImportProgrammer;
178
+ schema: OpenApi.IJsonSchema.IString;
179
+ }): ts.TypeNode => {
180
+ if (props.schema.format === "binary")
181
+ return factory.createTypeReferenceNode("File");
182
+
183
+ const intersection: ts.TypeNode[] = [TypeFactory.keyword("string")];
184
+ if (props.schema.default !== undefined)
185
+ intersection.push(props.importer.tag("Default", props.schema.default));
186
+ if (props.schema.minLength !== undefined)
187
+ intersection.push(
188
+ props.importer.tag("MinLength", props.schema.minLength),
189
+ );
190
+ if (props.schema.maxLength !== undefined)
191
+ intersection.push(
192
+ props.importer.tag("MaxLength", props.schema.maxLength),
193
+ );
194
+ if (props.schema.pattern !== undefined)
195
+ intersection.push(props.importer.tag("Pattern", props.schema.pattern));
196
+ if (
197
+ props.schema.format !== undefined &&
198
+ (FormatCheatSheet as Record<string, string>)[props.schema.format] !==
199
+ undefined
200
+ )
201
+ intersection.push(props.importer.tag("Format", props.schema.format));
202
+ if (props.schema.contentMediaType !== undefined)
203
+ intersection.push(
204
+ props.importer.tag("ContentMediaType", props.schema.contentMediaType),
205
+ );
206
+ return intersection.length === 1
207
+ ? intersection[0]!
208
+ : factory.createIntersectionTypeNode(intersection);
209
+ };
210
+
211
+ /* -----------------------------------------------------------
212
+ INSTANCES
213
+ ----------------------------------------------------------- */
214
+ const writeArray = (props: {
215
+ components: OpenApi.IComponents;
216
+ importer: NestiaMigrateImportProgrammer;
217
+ schema: OpenApi.IJsonSchema.IArray;
218
+ }): ts.TypeNode => {
219
+ const intersection: ts.TypeNode[] = [
220
+ factory.createArrayTypeNode(
221
+ write({
222
+ components: props.components,
223
+ importer: props.importer,
224
+ schema: props.schema.items,
225
+ }),
226
+ ),
227
+ ];
228
+ if (props.schema.minItems !== undefined)
229
+ intersection.push(props.importer.tag("MinItems", props.schema.minItems));
230
+ if (props.schema.maxItems !== undefined)
231
+ intersection.push(props.importer.tag("MaxItems", props.schema.maxItems));
232
+ if (props.schema.uniqueItems === true)
233
+ intersection.push(props.importer.tag("UniqueItems"));
234
+ return intersection.length === 1
235
+ ? intersection[0]!
236
+ : factory.createIntersectionTypeNode(intersection);
237
+ };
238
+
239
+ const writeTuple = (props: {
240
+ components: OpenApi.IComponents;
241
+ importer: NestiaMigrateImportProgrammer;
242
+ schema: OpenApi.IJsonSchema.ITuple;
243
+ }): ts.TypeNode =>
244
+ factory.createTupleTypeNode([
245
+ ...props.schema.prefixItems.map((item) =>
246
+ write({
247
+ components: props.components,
248
+ importer: props.importer,
249
+ schema: item,
250
+ }),
251
+ ),
252
+ ...(typeof props.schema.additionalItems === "object" &&
253
+ props.schema.additionalItems !== null
254
+ ? [
255
+ factory.createRestTypeNode(
256
+ write({
257
+ components: props.components,
258
+ importer: props.importer,
259
+ schema: props.schema.additionalItems,
260
+ }),
261
+ ),
262
+ ]
263
+ : props.schema.additionalItems === true
264
+ ? [
265
+ factory.createRestTypeNode(
266
+ factory.createArrayTypeNode(
267
+ factory.createKeywordTypeNode(SyntaxKind.AnyKeyword),
268
+ ),
269
+ ),
270
+ ]
271
+ : []),
272
+ ]);
273
+
274
+ const writeObject = (props: {
275
+ components: OpenApi.IComponents;
276
+ importer: NestiaMigrateImportProgrammer;
277
+ schema: OpenApi.IJsonSchema.IObject;
278
+ }): ts.TypeNode => {
279
+ const regular = () =>
280
+ factory.createTypeLiteralNode(
281
+ Object.entries(props.schema.properties ?? [])
282
+ .map(([key, value], index) => [
283
+ ...(index !== 0 &&
284
+ (!!value.title?.length || !!value.description?.length)
285
+ ? [factory.createIdentifier("\n") as any]
286
+ : []),
287
+ writeRegularProperty({
288
+ components: props.components,
289
+ importer: props.importer,
290
+ required: props.schema.required ?? [],
291
+ key,
292
+ value,
293
+ }),
294
+ ])
295
+ .flat(),
296
+ );
297
+ const dynamic = () =>
298
+ factory.createTypeLiteralNode([
299
+ writeDynamicProperty({
300
+ components: props.components,
301
+ importer: props.importer,
302
+ schema: props.schema.additionalProperties as OpenApi.IJsonSchema,
303
+ }),
304
+ ]);
305
+ return !!props.schema.properties?.length &&
306
+ typeof props.schema.additionalProperties === "object"
307
+ ? factory.createIntersectionTypeNode([regular(), dynamic()])
308
+ : typeof props.schema.additionalProperties === "object"
309
+ ? dynamic()
310
+ : regular();
311
+ };
312
+
313
+ const writeRegularProperty = (props: {
314
+ components: OpenApi.IComponents;
315
+ importer: NestiaMigrateImportProgrammer;
316
+ required: string[];
317
+ key: string;
318
+ value: OpenApi.IJsonSchema;
319
+ }) => {
320
+ const valueTypeNode: ts.TypeNode = write({
321
+ components: props.components,
322
+ importer: props.importer,
323
+ schema: props.value,
324
+ });
325
+ return FilePrinter.description(
326
+ factory.createPropertySignature(
327
+ props.value.readOnly
328
+ ? [factory.createToken(SyntaxKind.ReadonlyKeyword)]
329
+ : undefined,
330
+ NamingConvention.variable(props.key)
331
+ ? factory.createIdentifier(props.key)
332
+ : factory.createStringLiteral(props.key),
333
+ props.required.includes(props.key)
334
+ ? undefined
335
+ : factory.createToken(SyntaxKind.QuestionToken),
336
+ props.required.includes(props.key)
337
+ ? valueTypeNode
338
+ : ts.isUnionTypeNode(valueTypeNode)
339
+ ? factory.createUnionTypeNode([
340
+ ...(valueTypeNode.types ?? []),
341
+ factory.createTypeReferenceNode("undefined"),
342
+ ])
343
+ : factory.createUnionTypeNode([
344
+ valueTypeNode,
345
+ factory.createTypeReferenceNode("undefined"),
346
+ ]),
347
+ ),
348
+ writeComment(props.value),
349
+ );
350
+ };
351
+
352
+ const writeDynamicProperty = (props: {
353
+ components: OpenApi.IComponents;
354
+ importer: NestiaMigrateImportProgrammer;
355
+ schema: OpenApi.IJsonSchema;
356
+ }) =>
357
+ FilePrinter.description(
358
+ factory.createIndexSignature(
359
+ undefined,
360
+ [
361
+ factory.createParameterDeclaration(
362
+ undefined,
363
+ undefined,
364
+ factory.createIdentifier("key"),
365
+ undefined,
366
+ TypeFactory.keyword("string"),
367
+ ),
368
+ ],
369
+ write(props),
370
+ ),
371
+ writeComment(props.schema),
372
+ );
373
+
374
+ const writeReference = (props: {
375
+ importer: NestiaMigrateImportProgrammer;
376
+ schema: OpenApi.IJsonSchema.IReference;
377
+ }): ts.TypeReferenceNode | ts.KeywordTypeNode => {
378
+ if (props.schema.$ref.startsWith("#/components/schemas") === false)
379
+ return TypeFactory.keyword("any");
380
+ const name: string = props.schema.$ref
381
+ .split("/")
382
+ .slice(3)
383
+ .filter((str) => str.length !== 0)
384
+ .map(StringUtil.escapeNonVariable)
385
+ .join("");
386
+ if (name === "") return TypeFactory.keyword("any");
387
+ return props.importer.dto(name);
388
+ };
389
+
390
+ /* -----------------------------------------------------------
391
+ UNIONS
392
+ ----------------------------------------------------------- */
393
+ const writeUnion = (props: {
394
+ components: OpenApi.IComponents;
395
+ importer: NestiaMigrateImportProgrammer;
396
+ elements: OpenApi.IJsonSchema[];
397
+ }): ts.UnionTypeNode =>
398
+ factory.createUnionTypeNode(
399
+ props.elements.map((schema) =>
400
+ write({
401
+ components: props.components,
402
+ importer: props.importer,
403
+ schema,
404
+ }),
405
+ ),
406
+ );
407
+ }
408
+ const createNode = (text: string) => factory.createTypeReferenceNode(text);
409
+
410
+ const writeComment = (schema: OpenApi.IJsonSchema): string => {
411
+ interface IPlugin {
412
+ key: string;
413
+ value: undefined | string | number | boolean;
414
+ }
415
+ const plugins: IPlugin[] = [];
416
+ if (schema.title !== undefined)
417
+ plugins.push({
418
+ key: "title",
419
+ value: schema.title,
420
+ });
421
+ if (schema.deprecated === true)
422
+ plugins.push({
423
+ key: "deprecated",
424
+ value: undefined,
425
+ });
426
+ for (const [key, value] of Object.entries(schema))
427
+ if (key.startsWith("x-") && isExtensionValue(value))
428
+ plugins.push({
429
+ key,
430
+ value,
431
+ });
432
+ return [
433
+ ...(schema.description?.length
434
+ ? [eraseCommentTags(schema.description)]
435
+ : []),
436
+ ...(schema.description?.length && plugins.length !== 0 ? [""] : []),
437
+ ...plugins.map((p) =>
438
+ p.value === undefined ? `@${p.key}` : `@${p.key} ${String(p.value)}`,
439
+ ),
440
+ ].join("\n");
441
+ };
442
+
443
+ const isExtensionValue = (value: unknown): value is boolean | number | string =>
444
+ typeof value === "boolean" ||
445
+ typeof value === "number" ||
446
+ typeof value === "string";
447
+
448
+ const eraseCommentTags = (description: string): string => {
449
+ const lines: string[] = description.split("\n");
450
+ return lines
451
+ .filter((s) => COMMENT_TAGS.every((tag) => !s.includes(tag)))
452
+ .join("\n");
453
+ };
454
+
455
+ const COMMENT_TAGS = [
456
+ // string
457
+ "@format",
458
+ "@pattern",
459
+ "@length",
460
+ "@minLength",
461
+ "@maxLength",
462
+ "@contentMediaType",
463
+ // number
464
+ "@type",
465
+ "@minimum",
466
+ "@maximum",
467
+ "@exclusiveMinimum",
468
+ "@exclusiveMaximum",
469
+ "@multipleOf",
470
+ // array
471
+ "@items",
472
+ "@minItems",
473
+ "@maxItems",
474
+ "@uniqueItems",
475
+ ];