typia 4.2.0 → 4.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,355 +1,355 @@
1
- import ts from "typescript";
2
-
3
- import { IMetadataTag } from "../metadata/IMetadataTag";
4
- import { Metadata } from "../metadata/Metadata";
5
-
6
- export namespace MetadataTagFactory {
7
- export const generate =
8
- (metadata: Metadata) =>
9
- (tagList: ts.JSDocTagInfo[]) =>
10
- (identifier: () => string): IMetadataTag[] => {
11
- const output: IMetadataTag[] = [];
12
- for (const tag of tagList) {
13
- const elem: IMetadataTag | null = parse(
14
- identifier,
15
- metadata,
16
- tag,
17
- output,
18
- );
19
- if (elem !== null) output.push(elem);
20
- }
21
- return output;
22
- };
23
-
24
- const parse = (
25
- identifier: () => string,
26
- metadata: Metadata,
27
- tag: ts.JSDocTagInfo,
28
- output: IMetadataTag[],
29
- ): IMetadataTag | null => {
30
- const closure = _PARSER[tag.name];
31
- if (closure === undefined) return null;
32
-
33
- const text = (tag.text || [])[0]?.text;
34
- if (text === undefined)
35
- throw new Error(`${LABEL}: no tag value on ${identifier()}`);
36
-
37
- return closure(identifier, metadata, text, output);
38
- };
39
-
40
- /**
41
- * @internal
42
- */
43
- export const _PARSER: Record<
44
- string,
45
- (
46
- identifier: () => string,
47
- metadata: Metadata,
48
- text: string,
49
- output: IMetadataTag[],
50
- ) => IMetadataTag | null
51
- > = {
52
- /* -----------------------------------------------------------
53
- ARRAY
54
- ----------------------------------------------------------- */
55
- items: (identifier, metadata, text, output) => {
56
- validate(identifier, metadata, output, "items", "array", [
57
- "minItems",
58
- ]);
59
- return {
60
- kind: "items",
61
- value: parse_number(identifier, text),
62
- };
63
- },
64
- minItems: (identifier, metadata, text, output) => {
65
- validate(identifier, metadata, output, "minItems", "array", [
66
- "items",
67
- ]);
68
- return {
69
- kind: "minItems",
70
- value: parse_number(identifier, text),
71
- };
72
- },
73
- maxItems: (identifier, metadata, text, output) => {
74
- validate(identifier, metadata, output, "maxItems", "array", [
75
- "items",
76
- ]);
77
- return {
78
- kind: "maxItems",
79
- value: parse_number(identifier, text),
80
- };
81
- },
82
-
83
- /* -----------------------------------------------------------
84
- NUMBER
85
- ----------------------------------------------------------- */
86
- type: (_identifier, metadata, text, _output) => {
87
- return has_atomic("number")(new Set())(metadata) &&
88
- (text === "int" || text === "uint")
89
- ? { kind: "type", value: text }
90
- : text === "{int}" || text === "{uint}"
91
- ? { kind: "type", value: text.slice(1, -1) as "int" | "uint" }
92
- : null;
93
- },
94
- minimum: (identifier, metadata, text, output) => {
95
- validate(identifier, metadata, output, "minimum", "number", [
96
- "exclusiveMinimum",
97
- ]);
98
- return {
99
- kind: "minimum",
100
- value: parse_number(identifier, text),
101
- };
102
- },
103
- maximum: (identifier, metadata, text, output) => {
104
- validate(identifier, metadata, output, "maximum", "number", [
105
- "exclusiveMaximum",
106
- ]);
107
- return {
108
- kind: "maximum",
109
- value: parse_number(identifier, text),
110
- };
111
- },
112
- exclusiveMinimum: (identifier, metadata, text, output) => {
113
- validate(
114
- identifier,
115
- metadata,
116
- output,
117
- "exclusiveMinimum",
118
- "number",
119
- ["minimum"],
120
- );
121
- return {
122
- kind: "exclusiveMinimum",
123
- value: parse_number(identifier, text),
124
- };
125
- },
126
- exclusiveMaximum: (identifier, metadata, text, output) => {
127
- validate(
128
- identifier,
129
- metadata,
130
- output,
131
- "exclusiveMaximum",
132
- "number",
133
- ["maximum"],
134
- );
135
- return {
136
- kind: "exclusiveMaximum",
137
- value: parse_number(identifier, text),
138
- };
139
- },
140
- multipleOf: (identifier, metadata, text, output) => {
141
- validate(identifier, metadata, output, "multipleOf", "number", [
142
- "step",
143
- ]);
144
- return {
145
- kind: "multipleOf",
146
- value: parse_number(identifier, text),
147
- };
148
- },
149
- step: (identifier, metadata, text, output) => {
150
- validate(identifier, metadata, output, "step", "number", [
151
- "multipleOf",
152
- ]);
153
-
154
- const minimum: boolean = output.some(
155
- (tag) =>
156
- tag.kind === "minimum" || tag.kind === "exclusiveMinimum",
157
- );
158
- if (minimum === undefined)
159
- throw new Error(
160
- `${LABEL}: step requires minimum or exclusiveMinimum tag on "${identifier()}".`,
161
- );
162
-
163
- return {
164
- kind: "step",
165
- value: parse_number(identifier, text),
166
- };
167
- },
168
-
169
- /* -----------------------------------------------------------
170
- STRING
171
- ----------------------------------------------------------- */
172
- // Ignore arbitrary @format values in the internal metadata,
173
- // these are currently only supported on the typia.application() API.
174
- format: (identifier, metadata, str, output) => {
175
- const value: IMetadataTag.IFormat["value"] | undefined =
176
- FORMATS.get(str);
177
- validate(
178
- identifier,
179
- metadata,
180
- output,
181
- "format",
182
- value === "date" || value === "datetime" ? "Date" : "string",
183
- ["pattern"],
184
- );
185
- if (value === undefined) return null;
186
- return {
187
- kind: "format",
188
- value,
189
- };
190
- },
191
- pattern: (identifier, metadata, value, output) => {
192
- validate(identifier, metadata, output, "pattern", "string", [
193
- "format",
194
- ]);
195
- return {
196
- kind: "pattern",
197
- value,
198
- };
199
- },
200
- length: (identifier, metadata, text, output) => {
201
- validate(identifier, metadata, output, "length", "string", [
202
- "minLength",
203
- "maxLength",
204
- ]);
205
- return {
206
- kind: "length",
207
- value: parse_number(identifier, text),
208
- };
209
- },
210
- minLength: (identifier, metadata, text, output) => {
211
- validate(identifier, metadata, output, "minLength", "string", [
212
- "length",
213
- ]);
214
- return {
215
- kind: "minLength",
216
- value: parse_number(identifier, text),
217
- };
218
- },
219
- maxLength: (identifier, metadata, text, output) => {
220
- validate(identifier, metadata, output, "maxLength", "string", [
221
- "length",
222
- ]);
223
- return {
224
- kind: "maxLength",
225
- value: parse_number(identifier, text),
226
- };
227
- },
228
- };
229
- }
230
-
231
- const parse_number = (identifier: () => string, str: string): number => {
232
- const value: number = Number(str);
233
- if (isNaN(value) === true)
234
- throw new Error(`${LABEL}: invalid number on "${identifier()}".`);
235
- return value;
236
- };
237
-
238
- const LABEL = "Error on typia.MetadataTagFactory.generate()";
239
- const FORMATS: Map<string, IMetadataTag.IFormat["value"]> = new Map([
240
- ["uuid", "uuid"],
241
- ["email", "email"],
242
- ["url", "url"],
243
- ["ipv4", "ipv4"],
244
- ["ipv6", "ipv6"],
245
- ["date", "date"],
246
- ["datetime", "datetime"],
247
- ["date-time", "datetime"],
248
- ["dateTime", "datetime"],
249
- ]);
250
-
251
- const WRONG_TYPE = (
252
- tag: string,
253
- type: "string" | "number" | "array",
254
- identifier: () => string,
255
- ) => `${LABEL}: ${tag} requires ${type} type, but no "${identifier()}".`;
256
-
257
- const validate = (
258
- identifier: () => string,
259
- metadata: Metadata,
260
- output: IMetadataTag[],
261
- kind: IMetadataTag["kind"],
262
- type: "array" | "string" | "number" | "Date",
263
- neighbors: IMetadataTag["kind"][],
264
- ): void => {
265
- // TYPE CHECKING
266
- if (type === "array") {
267
- if (has_array(new Set())(metadata) === false)
268
- throw new Error(WRONG_TYPE(kind, "array", identifier));
269
- } else if (type === "Date") {
270
- if (
271
- has_native("Date")(new Set())(metadata) === false &&
272
- has_atomic("string")(new Set())(metadata) === false
273
- )
274
- throw new Error(WRONG_TYPE(kind, "string", identifier));
275
- } else if (has_atomic(type)(new Set())(metadata) === false)
276
- throw new Error(WRONG_TYPE(kind, type, identifier));
277
-
278
- // DUPLICATED TAG
279
- if (output.some((tag) => tag.kind === kind))
280
- throw new Error(
281
- `${LABEL}: duplicated ${kind} tags on "${identifier()}".`,
282
- );
283
-
284
- // NEIGHBOR TAG
285
- for (const name of neighbors)
286
- if (output.some((tag) => tag.kind === name))
287
- throw new Error(
288
- `${LABEL}: ${kind} and ${name} tags on "${identifier()}".`,
289
- );
290
- };
291
-
292
- // @todo: must block repeated array and tuple type
293
- const has_atomic =
294
- (type: "string" | "number") =>
295
- (visited: Set<Metadata>) =>
296
- (metadata: Metadata): boolean => {
297
- if (visited.has(metadata)) return false;
298
- visited.add(metadata);
299
- return (
300
- metadata.atomics.find(
301
- type === "number"
302
- ? (atom: string) => atom === type || atom === "bigint"
303
- : (atom: string) => atom === type,
304
- ) !== undefined ||
305
- metadata.arrays.some((array) =>
306
- has_atomic(type)(visited)(array.value),
307
- ) ||
308
- metadata.tuples.some((tuple) =>
309
- tuple.elements.some(has_atomic(type)(visited)),
310
- ) ||
311
- metadata.aliases.some((alias) =>
312
- has_atomic(type)(visited)(alias.value),
313
- ) ||
314
- (metadata.resolved !== null &&
315
- has_atomic(type)(visited)(metadata.resolved.returns))
316
- );
317
- };
318
-
319
- const has_native =
320
- (type: string) =>
321
- (visited: Set<Metadata>) =>
322
- (metadata: Metadata): boolean => {
323
- if (visited.has(metadata)) return false;
324
- visited.add(metadata);
325
- return (
326
- metadata.natives.find((native) => native === type) !== undefined ||
327
- metadata.arrays.some((child) =>
328
- has_native(type)(visited)(child.value),
329
- ) ||
330
- metadata.tuples.some((tuple) =>
331
- tuple.elements.some(has_native(type)(visited)),
332
- ) ||
333
- metadata.aliases.some((alias) =>
334
- has_native(type)(visited)(alias.value),
335
- ) ||
336
- (metadata.resolved !== null &&
337
- has_native(type)(visited)(metadata.resolved.returns))
338
- );
339
- };
340
-
341
- const has_array =
342
- (visited: Set<Metadata>) =>
343
- (metadata: Metadata): boolean => {
344
- if (visited.has(metadata)) return false;
345
- visited.add(metadata);
346
- return (
347
- metadata.arrays.length !== 0 ||
348
- metadata.tuples.some((tuple) =>
349
- tuple.elements.some(has_array(visited)),
350
- ) ||
351
- metadata.aliases.some((alias) => has_array(visited)(alias.value)) ||
352
- (metadata.resolved !== null &&
353
- has_array(visited)(metadata.resolved.returns))
354
- );
355
- };
1
+ import ts from "typescript";
2
+
3
+ import { IMetadataTag } from "../metadata/IMetadataTag";
4
+ import { Metadata } from "../metadata/Metadata";
5
+
6
+ export namespace MetadataTagFactory {
7
+ export const generate =
8
+ (metadata: Metadata) =>
9
+ (tagList: ts.JSDocTagInfo[]) =>
10
+ (identifier: () => string): IMetadataTag[] => {
11
+ const output: IMetadataTag[] = [];
12
+ for (const tag of tagList) {
13
+ const elem: IMetadataTag | null = parse(
14
+ identifier,
15
+ metadata,
16
+ tag,
17
+ output,
18
+ );
19
+ if (elem !== null) output.push(elem);
20
+ }
21
+ return output;
22
+ };
23
+
24
+ const parse = (
25
+ identifier: () => string,
26
+ metadata: Metadata,
27
+ tag: ts.JSDocTagInfo,
28
+ output: IMetadataTag[],
29
+ ): IMetadataTag | null => {
30
+ const closure = _PARSER[tag.name];
31
+ if (closure === undefined) return null;
32
+
33
+ const text = (tag.text || [])[0]?.text;
34
+ if (text === undefined)
35
+ throw new Error(`${LABEL}: no tag value on ${identifier()}`);
36
+
37
+ return closure(identifier, metadata, text, output);
38
+ };
39
+
40
+ /**
41
+ * @internal
42
+ */
43
+ export const _PARSER: Record<
44
+ string,
45
+ (
46
+ identifier: () => string,
47
+ metadata: Metadata,
48
+ text: string,
49
+ output: IMetadataTag[],
50
+ ) => IMetadataTag | null
51
+ > = {
52
+ /* -----------------------------------------------------------
53
+ ARRAY
54
+ ----------------------------------------------------------- */
55
+ items: (identifier, metadata, text, output) => {
56
+ validate(identifier, metadata, output, "items", "array", [
57
+ "minItems",
58
+ ]);
59
+ return {
60
+ kind: "items",
61
+ value: parse_number(identifier, text),
62
+ };
63
+ },
64
+ minItems: (identifier, metadata, text, output) => {
65
+ validate(identifier, metadata, output, "minItems", "array", [
66
+ "items",
67
+ ]);
68
+ return {
69
+ kind: "minItems",
70
+ value: parse_number(identifier, text),
71
+ };
72
+ },
73
+ maxItems: (identifier, metadata, text, output) => {
74
+ validate(identifier, metadata, output, "maxItems", "array", [
75
+ "items",
76
+ ]);
77
+ return {
78
+ kind: "maxItems",
79
+ value: parse_number(identifier, text),
80
+ };
81
+ },
82
+
83
+ /* -----------------------------------------------------------
84
+ NUMBER
85
+ ----------------------------------------------------------- */
86
+ type: (_identifier, metadata, text, _output) => {
87
+ return has_atomic("number")(new Set())(metadata) &&
88
+ (text === "int" || text === "uint")
89
+ ? { kind: "type", value: text }
90
+ : text === "{int}" || text === "{uint}"
91
+ ? { kind: "type", value: text.slice(1, -1) as "int" | "uint" }
92
+ : null;
93
+ },
94
+ minimum: (identifier, metadata, text, output) => {
95
+ validate(identifier, metadata, output, "minimum", "number", [
96
+ "exclusiveMinimum",
97
+ ]);
98
+ return {
99
+ kind: "minimum",
100
+ value: parse_number(identifier, text),
101
+ };
102
+ },
103
+ maximum: (identifier, metadata, text, output) => {
104
+ validate(identifier, metadata, output, "maximum", "number", [
105
+ "exclusiveMaximum",
106
+ ]);
107
+ return {
108
+ kind: "maximum",
109
+ value: parse_number(identifier, text),
110
+ };
111
+ },
112
+ exclusiveMinimum: (identifier, metadata, text, output) => {
113
+ validate(
114
+ identifier,
115
+ metadata,
116
+ output,
117
+ "exclusiveMinimum",
118
+ "number",
119
+ ["minimum"],
120
+ );
121
+ return {
122
+ kind: "exclusiveMinimum",
123
+ value: parse_number(identifier, text),
124
+ };
125
+ },
126
+ exclusiveMaximum: (identifier, metadata, text, output) => {
127
+ validate(
128
+ identifier,
129
+ metadata,
130
+ output,
131
+ "exclusiveMaximum",
132
+ "number",
133
+ ["maximum"],
134
+ );
135
+ return {
136
+ kind: "exclusiveMaximum",
137
+ value: parse_number(identifier, text),
138
+ };
139
+ },
140
+ multipleOf: (identifier, metadata, text, output) => {
141
+ validate(identifier, metadata, output, "multipleOf", "number", [
142
+ "step",
143
+ ]);
144
+ return {
145
+ kind: "multipleOf",
146
+ value: parse_number(identifier, text),
147
+ };
148
+ },
149
+ step: (identifier, metadata, text, output) => {
150
+ validate(identifier, metadata, output, "step", "number", [
151
+ "multipleOf",
152
+ ]);
153
+
154
+ const minimum: boolean = output.some(
155
+ (tag) =>
156
+ tag.kind === "minimum" || tag.kind === "exclusiveMinimum",
157
+ );
158
+ if (minimum === undefined)
159
+ throw new Error(
160
+ `${LABEL}: step requires minimum or exclusiveMinimum tag on "${identifier()}".`,
161
+ );
162
+
163
+ return {
164
+ kind: "step",
165
+ value: parse_number(identifier, text),
166
+ };
167
+ },
168
+
169
+ /* -----------------------------------------------------------
170
+ STRING
171
+ ----------------------------------------------------------- */
172
+ // Ignore arbitrary @format values in the internal metadata,
173
+ // these are currently only supported on the typia.application() API.
174
+ format: (identifier, metadata, str, output) => {
175
+ const value: IMetadataTag.IFormat["value"] | undefined =
176
+ FORMATS.get(str);
177
+ validate(
178
+ identifier,
179
+ metadata,
180
+ output,
181
+ "format",
182
+ value === "date" || value === "datetime" ? "Date" : "string",
183
+ ["pattern"],
184
+ );
185
+ if (value === undefined) return null;
186
+ return {
187
+ kind: "format",
188
+ value,
189
+ };
190
+ },
191
+ pattern: (identifier, metadata, value, output) => {
192
+ validate(identifier, metadata, output, "pattern", "string", [
193
+ "format",
194
+ ]);
195
+ return {
196
+ kind: "pattern",
197
+ value,
198
+ };
199
+ },
200
+ length: (identifier, metadata, text, output) => {
201
+ validate(identifier, metadata, output, "length", "string", [
202
+ "minLength",
203
+ "maxLength",
204
+ ]);
205
+ return {
206
+ kind: "length",
207
+ value: parse_number(identifier, text),
208
+ };
209
+ },
210
+ minLength: (identifier, metadata, text, output) => {
211
+ validate(identifier, metadata, output, "minLength", "string", [
212
+ "length",
213
+ ]);
214
+ return {
215
+ kind: "minLength",
216
+ value: parse_number(identifier, text),
217
+ };
218
+ },
219
+ maxLength: (identifier, metadata, text, output) => {
220
+ validate(identifier, metadata, output, "maxLength", "string", [
221
+ "length",
222
+ ]);
223
+ return {
224
+ kind: "maxLength",
225
+ value: parse_number(identifier, text),
226
+ };
227
+ },
228
+ };
229
+ }
230
+
231
+ const parse_number = (identifier: () => string, str: string): number => {
232
+ const value: number = Number(str);
233
+ if (isNaN(value) === true)
234
+ throw new Error(`${LABEL}: invalid number on "${identifier()}".`);
235
+ return value;
236
+ };
237
+
238
+ const LABEL = "Error on typia.MetadataTagFactory.generate()";
239
+ const FORMATS: Map<string, IMetadataTag.IFormat["value"]> = new Map([
240
+ ["uuid", "uuid"],
241
+ ["email", "email"],
242
+ ["url", "url"],
243
+ ["ipv4", "ipv4"],
244
+ ["ipv6", "ipv6"],
245
+ ["date", "date"],
246
+ ["datetime", "datetime"],
247
+ ["date-time", "datetime"],
248
+ ["dateTime", "datetime"],
249
+ ]);
250
+
251
+ const WRONG_TYPE = (
252
+ tag: string,
253
+ type: "string" | "number" | "array",
254
+ identifier: () => string,
255
+ ) => `${LABEL}: ${tag} requires ${type} type, but no "${identifier()}".`;
256
+
257
+ const validate = (
258
+ identifier: () => string,
259
+ metadata: Metadata,
260
+ output: IMetadataTag[],
261
+ kind: IMetadataTag["kind"],
262
+ type: "array" | "string" | "number" | "Date",
263
+ neighbors: IMetadataTag["kind"][],
264
+ ): void => {
265
+ // TYPE CHECKING
266
+ if (type === "array") {
267
+ if (has_array(new Set())(metadata) === false)
268
+ throw new Error(WRONG_TYPE(kind, "array", identifier));
269
+ } else if (type === "Date") {
270
+ if (
271
+ has_native("Date")(new Set())(metadata) === false &&
272
+ has_atomic("string")(new Set())(metadata) === false
273
+ )
274
+ throw new Error(WRONG_TYPE(kind, "string", identifier));
275
+ } else if (has_atomic(type)(new Set())(metadata) === false)
276
+ throw new Error(WRONG_TYPE(kind, type, identifier));
277
+
278
+ // DUPLICATED TAG
279
+ if (output.some((tag) => tag.kind === kind))
280
+ throw new Error(
281
+ `${LABEL}: duplicated ${kind} tags on "${identifier()}".`,
282
+ );
283
+
284
+ // NEIGHBOR TAG
285
+ for (const name of neighbors)
286
+ if (output.some((tag) => tag.kind === name))
287
+ throw new Error(
288
+ `${LABEL}: ${kind} and ${name} tags on "${identifier()}".`,
289
+ );
290
+ };
291
+
292
+ // @todo: must block repeated array and tuple type
293
+ const has_atomic =
294
+ (type: "string" | "number") =>
295
+ (visited: Set<Metadata>) =>
296
+ (metadata: Metadata): boolean => {
297
+ if (visited.has(metadata)) return false;
298
+ visited.add(metadata);
299
+ return (
300
+ metadata.atomics.find(
301
+ type === "number"
302
+ ? (atom: string) => atom === type || atom === "bigint"
303
+ : (atom: string) => atom === type,
304
+ ) !== undefined ||
305
+ metadata.arrays.some((array) =>
306
+ has_atomic(type)(visited)(array.value),
307
+ ) ||
308
+ metadata.tuples.some((tuple) =>
309
+ tuple.elements.some(has_atomic(type)(visited)),
310
+ ) ||
311
+ metadata.aliases.some((alias) =>
312
+ has_atomic(type)(visited)(alias.value),
313
+ ) ||
314
+ (metadata.resolved !== null &&
315
+ has_atomic(type)(visited)(metadata.resolved.returns))
316
+ );
317
+ };
318
+
319
+ const has_native =
320
+ (type: string) =>
321
+ (visited: Set<Metadata>) =>
322
+ (metadata: Metadata): boolean => {
323
+ if (visited.has(metadata)) return false;
324
+ visited.add(metadata);
325
+ return (
326
+ metadata.natives.find((native) => native === type) !== undefined ||
327
+ metadata.arrays.some((child) =>
328
+ has_native(type)(visited)(child.value),
329
+ ) ||
330
+ metadata.tuples.some((tuple) =>
331
+ tuple.elements.some(has_native(type)(visited)),
332
+ ) ||
333
+ metadata.aliases.some((alias) =>
334
+ has_native(type)(visited)(alias.value),
335
+ ) ||
336
+ (metadata.resolved !== null &&
337
+ has_native(type)(visited)(metadata.resolved.returns))
338
+ );
339
+ };
340
+
341
+ const has_array =
342
+ (visited: Set<Metadata>) =>
343
+ (metadata: Metadata): boolean => {
344
+ if (visited.has(metadata)) return false;
345
+ visited.add(metadata);
346
+ return (
347
+ metadata.arrays.length !== 0 ||
348
+ metadata.tuples.some((tuple) =>
349
+ tuple.elements.some(has_array(visited)),
350
+ ) ||
351
+ metadata.aliases.some((alias) => has_array(visited)(alias.value)) ||
352
+ (metadata.resolved !== null &&
353
+ has_array(visited)(metadata.resolved.returns))
354
+ );
355
+ };