@wp-typia/project-tools 0.15.1 → 0.15.2
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/runtime/json-utils.d.ts +5 -8
- package/dist/runtime/json-utils.js +5 -10
- package/dist/runtime/metadata-analysis.d.ts +7 -11
- package/dist/runtime/metadata-analysis.js +7 -285
- package/dist/runtime/metadata-model.d.ts +7 -84
- package/dist/runtime/metadata-model.js +7 -59
- package/dist/runtime/metadata-parser.d.ts +5 -51
- package/dist/runtime/metadata-parser.js +5 -792
- package/dist/runtime/metadata-php-render.d.ts +5 -27
- package/dist/runtime/metadata-php-render.js +5 -547
- package/dist/runtime/metadata-projection.d.ts +7 -7
- package/dist/runtime/metadata-projection.js +7 -233
- package/dist/runtime/object-utils.d.ts +1 -1
- package/dist/runtime/object-utils.js +3 -6
- package/package.json +8 -8
|
@@ -1,794 +1,7 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import ts from "typescript";
|
|
4
|
-
import { createAnalysisContext, } from "./metadata-analysis.js";
|
|
5
|
-
import { cloneJsonValue } from "./json-utils.js";
|
|
6
|
-
import { baseNode, cloneProperties, defaultAttributeConstraints, withRequired, } from "./metadata-model.js";
|
|
7
|
-
const SUPPORTED_TAGS = new Set([
|
|
8
|
-
"Default",
|
|
9
|
-
"ExclusiveMaximum",
|
|
10
|
-
"ExclusiveMinimum",
|
|
11
|
-
"Format",
|
|
12
|
-
"MaxLength",
|
|
13
|
-
"MaxItems",
|
|
14
|
-
"Maximum",
|
|
15
|
-
"MinLength",
|
|
16
|
-
"MinItems",
|
|
17
|
-
"Minimum",
|
|
18
|
-
"MultipleOf",
|
|
19
|
-
"Pattern",
|
|
20
|
-
"Selector",
|
|
21
|
-
"Source",
|
|
22
|
-
"Type",
|
|
23
|
-
]);
|
|
24
1
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* @returns The resolved project root plus the parsed root attribute node for
|
|
30
|
-
* the requested source type.
|
|
2
|
+
* Re-exports metadata parser utilities from `@wp-typia/block-runtime`.
|
|
3
|
+
* This adapter keeps the public project-tools runtime path stable while the
|
|
4
|
+
* implementation is consolidated in block-runtime.
|
|
5
|
+
* @module
|
|
31
6
|
*/
|
|
32
|
-
export
|
|
33
|
-
const projectRoot = path.resolve(options.projectRoot ?? process.cwd());
|
|
34
|
-
const rootNodes = analyzeSourceTypes({
|
|
35
|
-
projectRoot,
|
|
36
|
-
typesFile: options.typesFile,
|
|
37
|
-
}, [options.sourceTypeName]);
|
|
38
|
-
return {
|
|
39
|
-
projectRoot,
|
|
40
|
-
rootNode: rootNodes[options.sourceTypeName],
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Analyze multiple named source types from a TypeScript module.
|
|
45
|
-
*
|
|
46
|
-
* @param options Metadata analysis options including the optional project root
|
|
47
|
-
* and the relative types file path to parse.
|
|
48
|
-
* @param sourceTypeNames Exported type or interface names to resolve from the
|
|
49
|
-
* configured types file.
|
|
50
|
-
* @returns A record keyed by source type name with parsed attribute-node trees
|
|
51
|
-
* for each requested type.
|
|
52
|
-
*/
|
|
53
|
-
export function analyzeSourceTypes(options, sourceTypeNames) {
|
|
54
|
-
const projectRoot = path.resolve(options.projectRoot ?? process.cwd());
|
|
55
|
-
const typesFilePath = path.resolve(projectRoot, options.typesFile);
|
|
56
|
-
const ctx = createAnalysisContext(projectRoot, typesFilePath);
|
|
57
|
-
const sourceFile = ctx.program.getSourceFile(typesFilePath);
|
|
58
|
-
if (sourceFile === undefined) {
|
|
59
|
-
throw new Error(`Unable to load types file: ${typesFilePath}`);
|
|
60
|
-
}
|
|
61
|
-
return Object.fromEntries(sourceTypeNames.map((sourceTypeName) => {
|
|
62
|
-
const declaration = findNamedDeclaration(sourceFile, sourceTypeName);
|
|
63
|
-
if (declaration === undefined) {
|
|
64
|
-
throw new Error(`Unable to find source type "${sourceTypeName}" in ${path.relative(projectRoot, typesFilePath)}`);
|
|
65
|
-
}
|
|
66
|
-
return [
|
|
67
|
-
sourceTypeName,
|
|
68
|
-
parseNamedDeclaration(declaration, ctx, sourceTypeName, true),
|
|
69
|
-
];
|
|
70
|
-
}));
|
|
71
|
-
}
|
|
72
|
-
function findNamedDeclaration(sourceFile, name) {
|
|
73
|
-
for (const statement of sourceFile.statements) {
|
|
74
|
-
if ((ts.isInterfaceDeclaration(statement) ||
|
|
75
|
-
ts.isTypeAliasDeclaration(statement)) &&
|
|
76
|
-
statement.name.text === name) {
|
|
77
|
-
return statement;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return undefined;
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Parse an interface or type alias declaration into one attribute-node tree.
|
|
84
|
-
*
|
|
85
|
-
* @param declaration TypeScript declaration node to parse.
|
|
86
|
-
* @param ctx Shared analysis context used for type resolution and recursion
|
|
87
|
-
* detection.
|
|
88
|
-
* @param pathLabel Human-readable path label for diagnostics.
|
|
89
|
-
* @param required Whether the resulting node should be marked as required.
|
|
90
|
-
* @returns The parsed attribute-node representation for the declaration.
|
|
91
|
-
*/
|
|
92
|
-
export function parseNamedDeclaration(declaration, ctx, pathLabel, required) {
|
|
93
|
-
const recursionKey = `${declaration.getSourceFile().fileName}:${declaration.name.text}`;
|
|
94
|
-
if (ctx.recursionGuard.has(recursionKey)) {
|
|
95
|
-
throw new Error(`Recursive types are not supported: ${pathLabel}`);
|
|
96
|
-
}
|
|
97
|
-
ctx.recursionGuard.add(recursionKey);
|
|
98
|
-
try {
|
|
99
|
-
if (ts.isInterfaceDeclaration(declaration)) {
|
|
100
|
-
return parseInterfaceDeclaration(declaration, ctx, pathLabel, required);
|
|
101
|
-
}
|
|
102
|
-
return withRequired(parseTypeNode(declaration.type, ctx, pathLabel), required);
|
|
103
|
-
}
|
|
104
|
-
finally {
|
|
105
|
-
ctx.recursionGuard.delete(recursionKey);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
function parseInterfaceDeclaration(declaration, ctx, pathLabel, required) {
|
|
109
|
-
const properties = {};
|
|
110
|
-
for (const heritageClause of declaration.heritageClauses ?? []) {
|
|
111
|
-
if (heritageClause.token !== ts.SyntaxKind.ExtendsKeyword) {
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
for (const baseType of heritageClause.types) {
|
|
115
|
-
const baseNode = parseTypeReference(baseType, ctx, `${pathLabel}<extends>`);
|
|
116
|
-
if (baseNode.kind !== "object" || baseNode.properties === undefined) {
|
|
117
|
-
throw new Error(`Only object-like interface extensions are supported: ${pathLabel}`);
|
|
118
|
-
}
|
|
119
|
-
Object.assign(properties, cloneProperties(baseNode.properties));
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
for (const member of declaration.members) {
|
|
123
|
-
if (!ts.isPropertySignature(member) || member.type === undefined) {
|
|
124
|
-
throw new Error(`Unsupported member in ${pathLabel}; only typed properties are supported`);
|
|
125
|
-
}
|
|
126
|
-
const propertyName = getPropertyName(member.name);
|
|
127
|
-
properties[propertyName] = withRequired(parseTypeNode(member.type, ctx, `${pathLabel}.${propertyName}`), member.questionToken === undefined);
|
|
128
|
-
}
|
|
129
|
-
return {
|
|
130
|
-
constraints: defaultAttributeConstraints(),
|
|
131
|
-
enumValues: null,
|
|
132
|
-
kind: "object",
|
|
133
|
-
path: pathLabel,
|
|
134
|
-
properties,
|
|
135
|
-
required,
|
|
136
|
-
union: null,
|
|
137
|
-
wp: {
|
|
138
|
-
selector: null,
|
|
139
|
-
source: null,
|
|
140
|
-
},
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* Parse one TypeScript type node into the internal metadata model.
|
|
145
|
-
*
|
|
146
|
-
* @param node TypeScript AST node describing the source type shape.
|
|
147
|
-
* @param ctx Shared analysis context used for symbol and type resolution.
|
|
148
|
-
* @param pathLabel Human-readable path label used in parse errors and warnings.
|
|
149
|
-
* @returns The parsed attribute-node representation of the provided type node.
|
|
150
|
-
*/
|
|
151
|
-
export function parseTypeNode(node, ctx, pathLabel) {
|
|
152
|
-
if (ts.isParenthesizedTypeNode(node)) {
|
|
153
|
-
return parseTypeNode(node.type, ctx, pathLabel);
|
|
154
|
-
}
|
|
155
|
-
if (ts.isIndexedAccessTypeNode(node)) {
|
|
156
|
-
return parseIndexedAccessType(node, ctx, pathLabel);
|
|
157
|
-
}
|
|
158
|
-
if (ts.isIntersectionTypeNode(node)) {
|
|
159
|
-
return parseIntersectionType(node, ctx, pathLabel);
|
|
160
|
-
}
|
|
161
|
-
if (ts.isUnionTypeNode(node)) {
|
|
162
|
-
return parseUnionType(node, ctx, pathLabel);
|
|
163
|
-
}
|
|
164
|
-
if (ts.isTypeLiteralNode(node)) {
|
|
165
|
-
return parseTypeLiteral(node, ctx, pathLabel);
|
|
166
|
-
}
|
|
167
|
-
if (ts.isArrayTypeNode(node)) {
|
|
168
|
-
return {
|
|
169
|
-
constraints: defaultAttributeConstraints(),
|
|
170
|
-
enumValues: null,
|
|
171
|
-
items: withRequired(parseTypeNode(node.elementType, ctx, `${pathLabel}[]`), true),
|
|
172
|
-
kind: "array",
|
|
173
|
-
path: pathLabel,
|
|
174
|
-
required: true,
|
|
175
|
-
union: null,
|
|
176
|
-
wp: {
|
|
177
|
-
selector: null,
|
|
178
|
-
source: null,
|
|
179
|
-
},
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
if (ts.isLiteralTypeNode(node)) {
|
|
183
|
-
return parseLiteralType(node, pathLabel);
|
|
184
|
-
}
|
|
185
|
-
if (ts.isTypeReferenceNode(node)) {
|
|
186
|
-
return parseTypeReference(node, ctx, pathLabel);
|
|
187
|
-
}
|
|
188
|
-
if (node.kind === ts.SyntaxKind.StringKeyword) {
|
|
189
|
-
return baseNode("string", pathLabel);
|
|
190
|
-
}
|
|
191
|
-
if (node.kind === ts.SyntaxKind.NumberKeyword ||
|
|
192
|
-
node.kind === ts.SyntaxKind.BigIntKeyword) {
|
|
193
|
-
return baseNode("number", pathLabel);
|
|
194
|
-
}
|
|
195
|
-
if (node.kind === ts.SyntaxKind.BooleanKeyword) {
|
|
196
|
-
return baseNode("boolean", pathLabel);
|
|
197
|
-
}
|
|
198
|
-
throw new Error(`Unsupported type node at ${pathLabel}: ${node.getText()}`);
|
|
199
|
-
}
|
|
200
|
-
function parseIntersectionType(node, ctx, pathLabel) {
|
|
201
|
-
const tagNodes = [];
|
|
202
|
-
const valueNodes = [];
|
|
203
|
-
for (const typeNode of node.types) {
|
|
204
|
-
if (ts.isTypeReferenceNode(typeNode) &&
|
|
205
|
-
getSupportedTagName(typeNode) !== null) {
|
|
206
|
-
tagNodes.push(typeNode);
|
|
207
|
-
}
|
|
208
|
-
else {
|
|
209
|
-
valueNodes.push(typeNode);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
if (valueNodes.length === 0) {
|
|
213
|
-
throw new Error(`Intersection at ${pathLabel} does not contain a value type`);
|
|
214
|
-
}
|
|
215
|
-
const parsedNodes = valueNodes.map((valueNode) => parseTypeNode(valueNode, ctx, pathLabel));
|
|
216
|
-
const parsed = parsedNodes.length === 1
|
|
217
|
-
? parsedNodes[0]
|
|
218
|
-
: mergePrimitiveIntersection(parsedNodes, pathLabel);
|
|
219
|
-
for (const tagNode of tagNodes) {
|
|
220
|
-
applyTag(parsed, tagNode, pathLabel);
|
|
221
|
-
}
|
|
222
|
-
return parsed;
|
|
223
|
-
}
|
|
224
|
-
function parseIndexedAccessType(node, ctx, pathLabel) {
|
|
225
|
-
const keyValue = extractLiteralValue(node.indexType);
|
|
226
|
-
if (typeof keyValue !== "string" && typeof keyValue !== "number") {
|
|
227
|
-
throw new Error(`Indexed access requires a string or number literal key at ${pathLabel}: ${node.indexType.getText()}`);
|
|
228
|
-
}
|
|
229
|
-
const propertyKey = String(keyValue);
|
|
230
|
-
const propertyDeclaration = resolveIndexedAccessPropertyDeclaration(node.objectType, propertyKey, ctx, pathLabel);
|
|
231
|
-
if (propertyDeclaration.type === undefined) {
|
|
232
|
-
throw new Error(`Indexed access property "${propertyKey}" is missing an explicit type at ${pathLabel}`);
|
|
233
|
-
}
|
|
234
|
-
return withRequired(parseTypeNode(propertyDeclaration.type, ctx, pathLabel), propertyDeclaration.questionToken === undefined);
|
|
235
|
-
}
|
|
236
|
-
function parseUnionType(node, ctx, pathLabel) {
|
|
237
|
-
const literalValues = node.types
|
|
238
|
-
.map((typeNode) => extractLiteralValue(typeNode))
|
|
239
|
-
.filter((value) => value !== undefined);
|
|
240
|
-
if (literalValues.length === node.types.length && literalValues.length > 0) {
|
|
241
|
-
const uniqueKinds = new Set(literalValues.map((value) => typeof value));
|
|
242
|
-
if (uniqueKinds.size !== 1) {
|
|
243
|
-
throw new Error(`Mixed primitive enums are not supported at ${pathLabel}`);
|
|
244
|
-
}
|
|
245
|
-
const kind = [...uniqueKinds][0];
|
|
246
|
-
return {
|
|
247
|
-
constraints: defaultAttributeConstraints(),
|
|
248
|
-
enumValues: literalValues,
|
|
249
|
-
kind,
|
|
250
|
-
path: pathLabel,
|
|
251
|
-
required: true,
|
|
252
|
-
union: null,
|
|
253
|
-
wp: {
|
|
254
|
-
selector: null,
|
|
255
|
-
source: null,
|
|
256
|
-
},
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
const withoutUndefined = node.types.filter((typeNode) => typeNode.kind !== ts.SyntaxKind.UndefinedKeyword &&
|
|
260
|
-
typeNode.kind !== ts.SyntaxKind.NullKeyword);
|
|
261
|
-
if (withoutUndefined.length === 1) {
|
|
262
|
-
return parseTypeNode(withoutUndefined[0], ctx, pathLabel);
|
|
263
|
-
}
|
|
264
|
-
if (withoutUndefined.length > 1) {
|
|
265
|
-
return parseDiscriminatedUnion(withoutUndefined, ctx, pathLabel);
|
|
266
|
-
}
|
|
267
|
-
throw new Error(`Unsupported union type at ${pathLabel}: ${node.getText()}`);
|
|
268
|
-
}
|
|
269
|
-
function parseDiscriminatedUnion(typeNodes, ctx, pathLabel) {
|
|
270
|
-
const branchNodes = typeNodes.map((typeNode, index) => ({
|
|
271
|
-
node: parseTypeNode(typeNode, ctx, `${pathLabel}<branch:${index}>`),
|
|
272
|
-
source: typeNode,
|
|
273
|
-
}));
|
|
274
|
-
for (const branch of branchNodes) {
|
|
275
|
-
if (branch.node.kind !== "object" || branch.node.properties === undefined) {
|
|
276
|
-
throw new Error(`Unsupported union type at ${pathLabel}; only discriminated object unions are supported`);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
const discriminator = findDiscriminatorKey(branchNodes.map((branch) => branch.node), pathLabel);
|
|
280
|
-
const branches = {};
|
|
281
|
-
for (const branch of branchNodes) {
|
|
282
|
-
const discriminatorNode = branch.node.properties?.[discriminator];
|
|
283
|
-
const discriminatorValue = discriminatorNode?.enumValues?.[0];
|
|
284
|
-
if (typeof discriminatorValue !== "string") {
|
|
285
|
-
throw new Error(`Discriminated union at ${pathLabel} must use string literal discriminator values`);
|
|
286
|
-
}
|
|
287
|
-
if (branches[discriminatorValue] !== undefined) {
|
|
288
|
-
throw new Error(`Discriminated union at ${pathLabel} has duplicate discriminator value "${discriminatorValue}"`);
|
|
289
|
-
}
|
|
290
|
-
branches[discriminatorValue] = withRequired(branch.node, true);
|
|
291
|
-
}
|
|
292
|
-
return {
|
|
293
|
-
constraints: defaultAttributeConstraints(),
|
|
294
|
-
enumValues: null,
|
|
295
|
-
kind: "union",
|
|
296
|
-
path: pathLabel,
|
|
297
|
-
required: true,
|
|
298
|
-
union: {
|
|
299
|
-
branches,
|
|
300
|
-
discriminator,
|
|
301
|
-
},
|
|
302
|
-
wp: {
|
|
303
|
-
selector: null,
|
|
304
|
-
source: null,
|
|
305
|
-
},
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
function findDiscriminatorKey(branches, pathLabel) {
|
|
309
|
-
const candidateKeys = new Set(Object.keys(branches[0].properties ?? {}));
|
|
310
|
-
for (const branch of branches.slice(1)) {
|
|
311
|
-
for (const key of [...candidateKeys]) {
|
|
312
|
-
if (!(branch.properties && key in branch.properties)) {
|
|
313
|
-
candidateKeys.delete(key);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
const discriminatorCandidates = [...candidateKeys].filter((key) => branches.every((branch) => isDiscriminatorProperty(branch.properties?.[key])));
|
|
318
|
-
if (discriminatorCandidates.length !== 1) {
|
|
319
|
-
throw new Error(`Unsupported union type at ${pathLabel}; expected exactly one shared discriminator property`);
|
|
320
|
-
}
|
|
321
|
-
return discriminatorCandidates[0];
|
|
322
|
-
}
|
|
323
|
-
function isDiscriminatorProperty(node) {
|
|
324
|
-
return Boolean(node &&
|
|
325
|
-
node.required &&
|
|
326
|
-
node.kind === "string" &&
|
|
327
|
-
node.enumValues !== null &&
|
|
328
|
-
node.enumValues.length === 1 &&
|
|
329
|
-
typeof node.enumValues[0] === "string");
|
|
330
|
-
}
|
|
331
|
-
function parseTypeLiteral(node, ctx, pathLabel) {
|
|
332
|
-
const properties = {};
|
|
333
|
-
for (const member of node.members) {
|
|
334
|
-
if (!ts.isPropertySignature(member) || member.type === undefined) {
|
|
335
|
-
throw new Error(`Unsupported inline object member at ${pathLabel}`);
|
|
336
|
-
}
|
|
337
|
-
const propertyName = getPropertyName(member.name);
|
|
338
|
-
properties[propertyName] = withRequired(parseTypeNode(member.type, ctx, `${pathLabel}.${propertyName}`), member.questionToken === undefined);
|
|
339
|
-
}
|
|
340
|
-
return {
|
|
341
|
-
constraints: defaultAttributeConstraints(),
|
|
342
|
-
enumValues: null,
|
|
343
|
-
kind: "object",
|
|
344
|
-
path: pathLabel,
|
|
345
|
-
properties,
|
|
346
|
-
required: true,
|
|
347
|
-
union: null,
|
|
348
|
-
wp: {
|
|
349
|
-
selector: null,
|
|
350
|
-
source: null,
|
|
351
|
-
},
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
function parseLiteralType(node, pathLabel) {
|
|
355
|
-
const literal = extractLiteralValue(node);
|
|
356
|
-
if (literal === undefined) {
|
|
357
|
-
throw new Error(`Unsupported literal type at ${pathLabel}: ${node.getText()}`);
|
|
358
|
-
}
|
|
359
|
-
return {
|
|
360
|
-
constraints: defaultAttributeConstraints(),
|
|
361
|
-
enumValues: [literal],
|
|
362
|
-
kind: typeof literal,
|
|
363
|
-
path: pathLabel,
|
|
364
|
-
required: true,
|
|
365
|
-
union: null,
|
|
366
|
-
wp: {
|
|
367
|
-
selector: null,
|
|
368
|
-
source: null,
|
|
369
|
-
},
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
function parseTypeReference(node, ctx, pathLabel) {
|
|
373
|
-
const typeName = getReferenceName(node);
|
|
374
|
-
const typeArguments = node.typeArguments ?? [];
|
|
375
|
-
if (typeName === "Array" || typeName === "ReadonlyArray") {
|
|
376
|
-
const [itemNode] = typeArguments;
|
|
377
|
-
if (itemNode === undefined) {
|
|
378
|
-
throw new Error(`Array type is missing an item type at ${pathLabel}`);
|
|
379
|
-
}
|
|
380
|
-
return {
|
|
381
|
-
constraints: defaultAttributeConstraints(),
|
|
382
|
-
enumValues: null,
|
|
383
|
-
items: withRequired(parseTypeNode(itemNode, ctx, `${pathLabel}[]`), true),
|
|
384
|
-
kind: "array",
|
|
385
|
-
path: pathLabel,
|
|
386
|
-
required: true,
|
|
387
|
-
union: null,
|
|
388
|
-
wp: {
|
|
389
|
-
selector: null,
|
|
390
|
-
source: null,
|
|
391
|
-
},
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
if (typeArguments.length > 0) {
|
|
395
|
-
throw new Error(`Generic type references are not supported at ${pathLabel}: ${typeName}`);
|
|
396
|
-
}
|
|
397
|
-
const symbol = resolveSymbol(node, ctx.checker);
|
|
398
|
-
if (symbol === undefined) {
|
|
399
|
-
throw new Error(`Unable to resolve type reference "${typeName}" at ${pathLabel}`);
|
|
400
|
-
}
|
|
401
|
-
const declaration = symbol.declarations?.find((candidate) => ts.isInterfaceDeclaration(candidate) ||
|
|
402
|
-
ts.isTypeAliasDeclaration(candidate) ||
|
|
403
|
-
ts.isEnumDeclaration(candidate) ||
|
|
404
|
-
ts.isClassDeclaration(candidate));
|
|
405
|
-
if (declaration === undefined) {
|
|
406
|
-
throw new Error(`Unsupported referenced type "${typeName}" at ${pathLabel}`);
|
|
407
|
-
}
|
|
408
|
-
if (!isSerializableExternalDeclaration(declaration, ctx)) {
|
|
409
|
-
throw new Error(`External or non-serializable referenced type "${typeName}" is not supported at ${pathLabel}`);
|
|
410
|
-
}
|
|
411
|
-
if (ts.isClassDeclaration(declaration) || ts.isEnumDeclaration(declaration)) {
|
|
412
|
-
throw new Error(`Class and enum references are not supported at ${pathLabel}`);
|
|
413
|
-
}
|
|
414
|
-
if ((declaration.typeParameters?.length ?? 0) > 0) {
|
|
415
|
-
throw new Error(`Generic type declarations are not supported at ${pathLabel}: ${typeName}`);
|
|
416
|
-
}
|
|
417
|
-
return parseNamedDeclaration(declaration, ctx, pathLabel, true);
|
|
418
|
-
}
|
|
419
|
-
function resolveIndexedAccessPropertyDeclaration(objectTypeNode, propertyKey, ctx, pathLabel) {
|
|
420
|
-
const objectType = ctx.checker.getTypeFromTypeNode(objectTypeNode);
|
|
421
|
-
const propertySymbol = ctx.checker
|
|
422
|
-
.getPropertiesOfType(objectType)
|
|
423
|
-
.find((candidate) => candidate.name === propertyKey);
|
|
424
|
-
if (propertySymbol === undefined) {
|
|
425
|
-
throw new Error(`Indexed access could not resolve property "${propertyKey}" at ${pathLabel}`);
|
|
426
|
-
}
|
|
427
|
-
const valueDeclaration = propertySymbol.valueDeclaration;
|
|
428
|
-
const declaration = valueDeclaration !== undefined &&
|
|
429
|
-
(ts.isPropertySignature(valueDeclaration) ||
|
|
430
|
-
ts.isPropertyDeclaration(valueDeclaration))
|
|
431
|
-
? valueDeclaration
|
|
432
|
-
: propertySymbol.declarations?.find((candidate) => ts.isPropertySignature(candidate) ||
|
|
433
|
-
ts.isPropertyDeclaration(candidate));
|
|
434
|
-
if (declaration === undefined) {
|
|
435
|
-
throw new Error(`Indexed access property "${propertyKey}" does not resolve to a typed property at ${pathLabel}`);
|
|
436
|
-
}
|
|
437
|
-
if (!isSerializableExternalDeclaration(declaration, ctx)) {
|
|
438
|
-
throw new Error(`External or non-serializable indexed access property "${propertyKey}" is not supported at ${pathLabel}`);
|
|
439
|
-
}
|
|
440
|
-
return declaration;
|
|
441
|
-
}
|
|
442
|
-
function mergePrimitiveIntersection(nodes, pathLabel) {
|
|
443
|
-
const [first, ...rest] = nodes;
|
|
444
|
-
if (!isPrimitiveCompatibleNode(first)) {
|
|
445
|
-
throw new Error(`Unsupported intersection at ${pathLabel}; only primitive-compatible intersections are supported`);
|
|
446
|
-
}
|
|
447
|
-
let merged = withRequired(first, first.required);
|
|
448
|
-
for (const node of rest) {
|
|
449
|
-
if (!isPrimitiveCompatibleNode(node) || node.kind !== merged.kind) {
|
|
450
|
-
throw new Error(`Unsupported intersection at ${pathLabel}; only a single primitive kind plus typia tags is supported`);
|
|
451
|
-
}
|
|
452
|
-
merged = {
|
|
453
|
-
...merged,
|
|
454
|
-
constraints: mergeConstraints(merged.constraints, node.constraints, pathLabel),
|
|
455
|
-
defaultValue: mergeDefaultValue(merged.defaultValue, node.defaultValue, pathLabel),
|
|
456
|
-
enumValues: intersectEnumValues(merged.enumValues, node.enumValues, pathLabel),
|
|
457
|
-
required: merged.required && node.required,
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
return merged;
|
|
461
|
-
}
|
|
462
|
-
function isPrimitiveCompatibleNode(node) {
|
|
463
|
-
return ((node.kind === "string" ||
|
|
464
|
-
node.kind === "number" ||
|
|
465
|
-
node.kind === "boolean") &&
|
|
466
|
-
node.items === undefined &&
|
|
467
|
-
node.properties === undefined &&
|
|
468
|
-
node.union === null);
|
|
469
|
-
}
|
|
470
|
-
function mergeConstraints(left, right, pathLabel) {
|
|
471
|
-
return {
|
|
472
|
-
exclusiveMaximum: mergeMaximumLike(left.exclusiveMaximum, right.exclusiveMaximum),
|
|
473
|
-
exclusiveMinimum: mergeMinimumLike(left.exclusiveMinimum, right.exclusiveMinimum),
|
|
474
|
-
format: mergeExactLike(left.format, right.format, pathLabel, "format"),
|
|
475
|
-
maxLength: mergeMaximumLike(left.maxLength, right.maxLength),
|
|
476
|
-
maxItems: mergeMaximumLike(left.maxItems, right.maxItems),
|
|
477
|
-
maximum: mergeMaximumLike(left.maximum, right.maximum),
|
|
478
|
-
minLength: mergeMinimumLike(left.minLength, right.minLength),
|
|
479
|
-
minItems: mergeMinimumLike(left.minItems, right.minItems),
|
|
480
|
-
minimum: mergeMinimumLike(left.minimum, right.minimum),
|
|
481
|
-
multipleOf: mergeExactLike(left.multipleOf, right.multipleOf, pathLabel, "multipleOf"),
|
|
482
|
-
pattern: mergeExactLike(left.pattern, right.pattern, pathLabel, "pattern"),
|
|
483
|
-
typeTag: mergeExactLike(left.typeTag, right.typeTag, pathLabel, "typeTag"),
|
|
484
|
-
};
|
|
485
|
-
}
|
|
486
|
-
function mergeMinimumLike(left, right) {
|
|
487
|
-
if (left === null) {
|
|
488
|
-
return right;
|
|
489
|
-
}
|
|
490
|
-
if (right === null) {
|
|
491
|
-
return left;
|
|
492
|
-
}
|
|
493
|
-
return Math.max(left, right);
|
|
494
|
-
}
|
|
495
|
-
function mergeMaximumLike(left, right) {
|
|
496
|
-
if (left === null) {
|
|
497
|
-
return right;
|
|
498
|
-
}
|
|
499
|
-
if (right === null) {
|
|
500
|
-
return left;
|
|
501
|
-
}
|
|
502
|
-
return Math.min(left, right);
|
|
503
|
-
}
|
|
504
|
-
function mergeExactLike(left, right, pathLabel, label) {
|
|
505
|
-
if (left === null) {
|
|
506
|
-
return right;
|
|
507
|
-
}
|
|
508
|
-
if (right === null) {
|
|
509
|
-
return left;
|
|
510
|
-
}
|
|
511
|
-
if (left !== right) {
|
|
512
|
-
throw new Error(`Conflicting ${label} constraints in intersection at ${pathLabel}: ${left} vs ${right}`);
|
|
513
|
-
}
|
|
514
|
-
return left;
|
|
515
|
-
}
|
|
516
|
-
function mergeDefaultValue(left, right, pathLabel) {
|
|
517
|
-
if (left === undefined) {
|
|
518
|
-
return right;
|
|
519
|
-
}
|
|
520
|
-
if (right === undefined) {
|
|
521
|
-
return left;
|
|
522
|
-
}
|
|
523
|
-
if (!jsonValuesEqual(left, right)) {
|
|
524
|
-
throw new Error(`Conflicting default values in intersection at ${pathLabel}`);
|
|
525
|
-
}
|
|
526
|
-
return cloneJsonValue(left);
|
|
527
|
-
}
|
|
528
|
-
function jsonValuesEqual(left, right) {
|
|
529
|
-
if (left === right) {
|
|
530
|
-
return true;
|
|
531
|
-
}
|
|
532
|
-
if (left === null ||
|
|
533
|
-
right === null ||
|
|
534
|
-
typeof left !== "object" ||
|
|
535
|
-
typeof right !== "object") {
|
|
536
|
-
return false;
|
|
537
|
-
}
|
|
538
|
-
if (Array.isArray(left) || Array.isArray(right)) {
|
|
539
|
-
if (!Array.isArray(left) || !Array.isArray(right)) {
|
|
540
|
-
return false;
|
|
541
|
-
}
|
|
542
|
-
if (left.length !== right.length) {
|
|
543
|
-
return false;
|
|
544
|
-
}
|
|
545
|
-
return left.every((value, index) => jsonValuesEqual(value, right[index]));
|
|
546
|
-
}
|
|
547
|
-
const leftEntries = Object.entries(left);
|
|
548
|
-
const rightEntries = Object.entries(right);
|
|
549
|
-
if (leftEntries.length !== rightEntries.length) {
|
|
550
|
-
return false;
|
|
551
|
-
}
|
|
552
|
-
return leftEntries.every(([key, value]) => Object.prototype.hasOwnProperty.call(right, key) &&
|
|
553
|
-
jsonValuesEqual(value, right[key]));
|
|
554
|
-
}
|
|
555
|
-
function intersectEnumValues(left, right, pathLabel) {
|
|
556
|
-
if (left === null) {
|
|
557
|
-
return right ? [...right] : null;
|
|
558
|
-
}
|
|
559
|
-
if (right === null) {
|
|
560
|
-
return [...left];
|
|
561
|
-
}
|
|
562
|
-
const allowed = new Set(right);
|
|
563
|
-
const intersection = left.filter((value) => allowed.has(value));
|
|
564
|
-
if (intersection.length === 0) {
|
|
565
|
-
throw new Error(`Intersection at ${pathLabel} resolves to an empty enum`);
|
|
566
|
-
}
|
|
567
|
-
return intersection;
|
|
568
|
-
}
|
|
569
|
-
function applyTag(node, tagNode, pathLabel) {
|
|
570
|
-
const tagName = getSupportedTagName(tagNode);
|
|
571
|
-
if (tagName === null) {
|
|
572
|
-
return;
|
|
573
|
-
}
|
|
574
|
-
const [arg] = tagNode.typeArguments ?? [];
|
|
575
|
-
if (arg === undefined) {
|
|
576
|
-
throw new Error(`Tag "${tagName}" is missing its generic argument at ${pathLabel}`);
|
|
577
|
-
}
|
|
578
|
-
switch (tagName) {
|
|
579
|
-
case "Default": {
|
|
580
|
-
const value = parseDefaultValue(arg, pathLabel);
|
|
581
|
-
if (value === undefined) {
|
|
582
|
-
throw new Error(`Unsupported Default value at ${pathLabel}: ${arg.getText()}`);
|
|
583
|
-
}
|
|
584
|
-
node.defaultValue = value;
|
|
585
|
-
return;
|
|
586
|
-
}
|
|
587
|
-
case "Format":
|
|
588
|
-
node.constraints.format = parseStringLikeArgument(arg, tagName, pathLabel);
|
|
589
|
-
return;
|
|
590
|
-
case "Pattern":
|
|
591
|
-
node.constraints.pattern = parseStringLikeArgument(arg, tagName, pathLabel);
|
|
592
|
-
return;
|
|
593
|
-
case "Selector":
|
|
594
|
-
node.wp.selector = parseStringLikeArgument(arg, tagName, pathLabel);
|
|
595
|
-
return;
|
|
596
|
-
case "Source":
|
|
597
|
-
node.wp.source = parseWordPressAttributeSource(arg, pathLabel);
|
|
598
|
-
return;
|
|
599
|
-
case "Type":
|
|
600
|
-
node.constraints.typeTag = parseStringLikeArgument(arg, tagName, pathLabel);
|
|
601
|
-
return;
|
|
602
|
-
case "MinLength":
|
|
603
|
-
node.constraints.minLength = parseNumericArgument(arg, tagName, pathLabel);
|
|
604
|
-
return;
|
|
605
|
-
case "MaxLength":
|
|
606
|
-
node.constraints.maxLength = parseNumericArgument(arg, tagName, pathLabel);
|
|
607
|
-
return;
|
|
608
|
-
case "MinItems":
|
|
609
|
-
node.constraints.minItems = parseNumericArgument(arg, tagName, pathLabel);
|
|
610
|
-
return;
|
|
611
|
-
case "MaxItems":
|
|
612
|
-
node.constraints.maxItems = parseNumericArgument(arg, tagName, pathLabel);
|
|
613
|
-
return;
|
|
614
|
-
case "Minimum":
|
|
615
|
-
node.constraints.minimum = parseNumericArgument(arg, tagName, pathLabel);
|
|
616
|
-
return;
|
|
617
|
-
case "Maximum":
|
|
618
|
-
node.constraints.maximum = parseNumericArgument(arg, tagName, pathLabel);
|
|
619
|
-
return;
|
|
620
|
-
case "ExclusiveMinimum":
|
|
621
|
-
node.constraints.exclusiveMinimum = parseNumericArgument(arg, tagName, pathLabel);
|
|
622
|
-
return;
|
|
623
|
-
case "ExclusiveMaximum":
|
|
624
|
-
node.constraints.exclusiveMaximum = parseNumericArgument(arg, tagName, pathLabel);
|
|
625
|
-
return;
|
|
626
|
-
case "MultipleOf":
|
|
627
|
-
node.constraints.multipleOf = parseNumericArgument(arg, tagName, pathLabel);
|
|
628
|
-
return;
|
|
629
|
-
default:
|
|
630
|
-
return;
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
function parseDefaultValue(node, pathLabel) {
|
|
634
|
-
if (ts.isParenthesizedTypeNode(node)) {
|
|
635
|
-
return parseDefaultValue(node.type, pathLabel);
|
|
636
|
-
}
|
|
637
|
-
if (ts.isLiteralTypeNode(node)) {
|
|
638
|
-
const literal = extractLiteralValue(node);
|
|
639
|
-
return literal === undefined ? undefined : literal;
|
|
640
|
-
}
|
|
641
|
-
if (ts.isTypeLiteralNode(node)) {
|
|
642
|
-
const objectValue = {};
|
|
643
|
-
for (const member of node.members) {
|
|
644
|
-
if (!ts.isPropertySignature(member) || member.type === undefined) {
|
|
645
|
-
throw new Error(`Unsupported object Default value at ${pathLabel}`);
|
|
646
|
-
}
|
|
647
|
-
const propertyName = getPropertyName(member.name);
|
|
648
|
-
const value = parseDefaultValue(member.type, `${pathLabel}.${propertyName}`);
|
|
649
|
-
if (value === undefined) {
|
|
650
|
-
throw new Error(`Unsupported object Default value at ${pathLabel}.${propertyName}`);
|
|
651
|
-
}
|
|
652
|
-
objectValue[propertyName] = value;
|
|
653
|
-
}
|
|
654
|
-
return objectValue;
|
|
655
|
-
}
|
|
656
|
-
if (ts.isTupleTypeNode(node)) {
|
|
657
|
-
return node.elements.map((element, index) => {
|
|
658
|
-
const value = parseDefaultValue(element, `${pathLabel}[${index}]`);
|
|
659
|
-
if (value === undefined) {
|
|
660
|
-
throw new Error(`Unsupported array Default value at ${pathLabel}[${index}]`);
|
|
661
|
-
}
|
|
662
|
-
return value;
|
|
663
|
-
});
|
|
664
|
-
}
|
|
665
|
-
if (node.kind === ts.SyntaxKind.NullKeyword) {
|
|
666
|
-
return null;
|
|
667
|
-
}
|
|
668
|
-
return undefined;
|
|
669
|
-
}
|
|
670
|
-
function parseNumericArgument(node, tagName, pathLabel) {
|
|
671
|
-
const value = extractLiteralValue(node);
|
|
672
|
-
if (typeof value !== "number") {
|
|
673
|
-
throw new Error(`Tag "${tagName}" expects a numeric literal at ${pathLabel}`);
|
|
674
|
-
}
|
|
675
|
-
return value;
|
|
676
|
-
}
|
|
677
|
-
function parseStringLikeArgument(node, tagName, pathLabel) {
|
|
678
|
-
const value = extractLiteralValue(node);
|
|
679
|
-
if (typeof value !== "string") {
|
|
680
|
-
throw new Error(`Tag "${tagName}" expects a string literal at ${pathLabel}`);
|
|
681
|
-
}
|
|
682
|
-
return value;
|
|
683
|
-
}
|
|
684
|
-
function parseWordPressAttributeSource(node, pathLabel) {
|
|
685
|
-
const value = parseStringLikeArgument(node, "Source", pathLabel);
|
|
686
|
-
if (value === "html" || value === "text" || value === "rich-text") {
|
|
687
|
-
return value;
|
|
688
|
-
}
|
|
689
|
-
throw new Error(`Tag "Source" only supports "html", "text", or "rich-text" at ${pathLabel}`);
|
|
690
|
-
}
|
|
691
|
-
function extractLiteralValue(node) {
|
|
692
|
-
if (ts.isParenthesizedTypeNode(node)) {
|
|
693
|
-
return extractLiteralValue(node.type);
|
|
694
|
-
}
|
|
695
|
-
if (ts.isLiteralTypeNode(node)) {
|
|
696
|
-
return extractLiteralValue(node.literal);
|
|
697
|
-
}
|
|
698
|
-
if (ts.isPrefixUnaryExpression(node) &&
|
|
699
|
-
node.operator === ts.SyntaxKind.MinusToken &&
|
|
700
|
-
ts.isNumericLiteral(node.operand)) {
|
|
701
|
-
return -Number(node.operand.text);
|
|
702
|
-
}
|
|
703
|
-
if (node.kind === ts.SyntaxKind.TrueKeyword) {
|
|
704
|
-
return true;
|
|
705
|
-
}
|
|
706
|
-
if (node.kind === ts.SyntaxKind.FalseKeyword) {
|
|
707
|
-
return false;
|
|
708
|
-
}
|
|
709
|
-
if (ts.isStringLiteral(node)) {
|
|
710
|
-
return node.text;
|
|
711
|
-
}
|
|
712
|
-
if (ts.isNumericLiteral(node)) {
|
|
713
|
-
return Number(node.text);
|
|
714
|
-
}
|
|
715
|
-
return undefined;
|
|
716
|
-
}
|
|
717
|
-
function getPropertyName(name) {
|
|
718
|
-
if (ts.isIdentifier(name) ||
|
|
719
|
-
ts.isStringLiteral(name) ||
|
|
720
|
-
ts.isNumericLiteral(name)) {
|
|
721
|
-
return name.text;
|
|
722
|
-
}
|
|
723
|
-
throw new Error(`Unsupported property name: ${name.getText()}`);
|
|
724
|
-
}
|
|
725
|
-
function getSupportedTagName(node) {
|
|
726
|
-
const typeName = getEntityNameText(node.typeName);
|
|
727
|
-
const [, tagName] = typeName.split(".");
|
|
728
|
-
if (!typeName.startsWith("tags.") ||
|
|
729
|
-
tagName === undefined ||
|
|
730
|
-
!SUPPORTED_TAGS.has(tagName)) {
|
|
731
|
-
return null;
|
|
732
|
-
}
|
|
733
|
-
return tagName;
|
|
734
|
-
}
|
|
735
|
-
function getEntityNameText(name) {
|
|
736
|
-
if (ts.isIdentifier(name)) {
|
|
737
|
-
return name.text;
|
|
738
|
-
}
|
|
739
|
-
return `${getEntityNameText(name.left)}.${name.right.text}`;
|
|
740
|
-
}
|
|
741
|
-
function resolveSymbol(node, checker) {
|
|
742
|
-
const symbol = checker.getSymbolAtLocation(ts.isTypeReferenceNode(node) ? node.typeName : node.expression);
|
|
743
|
-
if (symbol === undefined) {
|
|
744
|
-
return undefined;
|
|
745
|
-
}
|
|
746
|
-
return symbol.flags & ts.SymbolFlags.Alias
|
|
747
|
-
? checker.getAliasedSymbol(symbol)
|
|
748
|
-
: symbol;
|
|
749
|
-
}
|
|
750
|
-
function getReferenceName(node) {
|
|
751
|
-
if (ts.isTypeReferenceNode(node)) {
|
|
752
|
-
return getEntityNameText(node.typeName);
|
|
753
|
-
}
|
|
754
|
-
return node.expression.getText();
|
|
755
|
-
}
|
|
756
|
-
function isProjectLocalDeclaration(declaration, projectRoot) {
|
|
757
|
-
const fileName = declaration.getSourceFile().fileName;
|
|
758
|
-
return (!fileName.includes("node_modules") &&
|
|
759
|
-
!path.relative(projectRoot, fileName).startsWith(".."));
|
|
760
|
-
}
|
|
761
|
-
function isSerializableExternalDeclaration(declaration, ctx) {
|
|
762
|
-
if (isProjectLocalDeclaration(declaration, ctx.projectRoot)) {
|
|
763
|
-
return true;
|
|
764
|
-
}
|
|
765
|
-
const packageName = getOwningPackageName(declaration.getSourceFile().fileName, ctx.packageNameCache);
|
|
766
|
-
return packageName !== null && ctx.allowedExternalPackages.has(packageName);
|
|
767
|
-
}
|
|
768
|
-
function getOwningPackageName(fileName, cache) {
|
|
769
|
-
let currentDir = path.dirname(fileName);
|
|
770
|
-
while (true) {
|
|
771
|
-
if (cache.has(currentDir)) {
|
|
772
|
-
return cache.get(currentDir) ?? null;
|
|
773
|
-
}
|
|
774
|
-
const packageJsonPath = path.join(currentDir, "package.json");
|
|
775
|
-
if (fs.existsSync(packageJsonPath)) {
|
|
776
|
-
try {
|
|
777
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
778
|
-
const packageName = typeof packageJson.name === "string" ? packageJson.name : null;
|
|
779
|
-
cache.set(currentDir, packageName);
|
|
780
|
-
return packageName;
|
|
781
|
-
}
|
|
782
|
-
catch {
|
|
783
|
-
cache.set(currentDir, null);
|
|
784
|
-
return null;
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
const parentDir = path.dirname(currentDir);
|
|
788
|
-
if (parentDir === currentDir) {
|
|
789
|
-
cache.set(currentDir, null);
|
|
790
|
-
return null;
|
|
791
|
-
}
|
|
792
|
-
currentDir = parentDir;
|
|
793
|
-
}
|
|
794
|
-
}
|
|
7
|
+
export * from "@wp-typia/block-runtime/metadata-parser";
|