@player-tools/xlr-converters 0.0.2-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/index.cjs.js +756 -0
- package/dist/index.d.ts +86 -0
- package/dist/index.esm.js +746 -0
- package/package.json +35 -0
- package/src/index.ts +5 -0
- package/src/ts-to-xlr.ts +725 -0
- package/src/types.ts +15 -0
- package/src/xlr-to-ts.ts +511 -0
package/src/ts-to-xlr.ts
ADDED
|
@@ -0,0 +1,725 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import type {
|
|
3
|
+
NodeType,
|
|
4
|
+
FunctionTypeParameters,
|
|
5
|
+
TupleType,
|
|
6
|
+
NamedType,
|
|
7
|
+
ObjectType,
|
|
8
|
+
NamedTypeWithGenerics,
|
|
9
|
+
NodeTypeWithGenerics,
|
|
10
|
+
ObjectProperty,
|
|
11
|
+
AnyType,
|
|
12
|
+
ParamTypeNode,
|
|
13
|
+
ConditionalType,
|
|
14
|
+
} from '@player-tools/xlr';
|
|
15
|
+
import type { TopLevelDeclaration } from '@player-tools/xlr-utils';
|
|
16
|
+
import {
|
|
17
|
+
buildTemplateRegex,
|
|
18
|
+
decorateNode,
|
|
19
|
+
fillInGenerics,
|
|
20
|
+
getReferencedType,
|
|
21
|
+
getStringLiteralsFromUnion,
|
|
22
|
+
isExportedDeclaration,
|
|
23
|
+
isGenericInterfaceDeclaration,
|
|
24
|
+
isGenericNodeType,
|
|
25
|
+
isGenericTypeDeclaration,
|
|
26
|
+
isOptionalProperty,
|
|
27
|
+
isTypeReferenceGeneric,
|
|
28
|
+
tsStripOptionalType,
|
|
29
|
+
isNonNullable,
|
|
30
|
+
applyPartialOrRequiredToNodeType,
|
|
31
|
+
applyPickOrOmitToNodeType,
|
|
32
|
+
isTopLevelNode,
|
|
33
|
+
resolveConditional,
|
|
34
|
+
} from '@player-tools/xlr-utils';
|
|
35
|
+
import { ConversionError } from './types';
|
|
36
|
+
|
|
37
|
+
export interface TSConverterContext {
|
|
38
|
+
/** */
|
|
39
|
+
customPrimitives: Array<string>;
|
|
40
|
+
|
|
41
|
+
/** */
|
|
42
|
+
typeChecker: ts.TypeChecker;
|
|
43
|
+
|
|
44
|
+
/** */
|
|
45
|
+
throwError: (message: string) => never;
|
|
46
|
+
|
|
47
|
+
/** Cached conversion operations */
|
|
48
|
+
cache: {
|
|
49
|
+
/** Converted TS Nodes */
|
|
50
|
+
convertedNodes: Map<ts.TypeNode, NodeType | undefined>;
|
|
51
|
+
|
|
52
|
+
/** Processed Template Strings */
|
|
53
|
+
convertedTemplates: Map<ts.TemplateLiteralTypeNode, string>;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const AnyTypeNode: AnyType = {
|
|
58
|
+
type: 'any',
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/** Converts TS Nodes/Files to XLRs */
|
|
62
|
+
export class TsConverter {
|
|
63
|
+
private context: TSConverterContext;
|
|
64
|
+
|
|
65
|
+
constructor(typeChecker: ts.TypeChecker, customPrimitives?: Array<string>) {
|
|
66
|
+
this.context = {
|
|
67
|
+
customPrimitives: customPrimitives ?? [],
|
|
68
|
+
typeChecker,
|
|
69
|
+
throwError: (message: string) => {
|
|
70
|
+
throw new ConversionError(message);
|
|
71
|
+
},
|
|
72
|
+
cache: {
|
|
73
|
+
convertedNodes: new Map(),
|
|
74
|
+
convertedTemplates: new Map(),
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Converts all exported objects to a XLR representation */
|
|
80
|
+
public convertSourceFile(sourceFile: ts.SourceFile) {
|
|
81
|
+
const declarations = sourceFile.statements.filter(
|
|
82
|
+
(statement): statement is TopLevelDeclaration =>
|
|
83
|
+
ts.isTypeAliasDeclaration(statement) ||
|
|
84
|
+
ts.isInterfaceDeclaration(statement)
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const types = declarations
|
|
88
|
+
.filter((declaration) => isExportedDeclaration(declaration))
|
|
89
|
+
.map((statement) => this.convertDeclaration(statement) as NamedType)
|
|
90
|
+
.filter(<T>(v: T): v is NonNullable<T> => !!v);
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
data: { version: 1, types },
|
|
94
|
+
convertedTypes: types.map(({ name }) => name),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Converts a single type/interface declaration to XLRs */
|
|
99
|
+
public convertDeclaration(
|
|
100
|
+
node: TopLevelDeclaration
|
|
101
|
+
): NamedType<ObjectType> | NamedTypeWithGenerics<ObjectType> {
|
|
102
|
+
const sourceFile = node.parent as ts.SourceFile;
|
|
103
|
+
const { fileName } = sourceFile;
|
|
104
|
+
if (ts.isTypeAliasDeclaration(node)) {
|
|
105
|
+
let genericTokens;
|
|
106
|
+
if (isGenericTypeDeclaration(node)) {
|
|
107
|
+
genericTokens = this.generateGenerics(node.typeParameters);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
name: node.name.getText(),
|
|
112
|
+
source: fileName,
|
|
113
|
+
...(this.convertTsTypeNode(node.type) ?? AnyTypeNode),
|
|
114
|
+
...decorateNode(node),
|
|
115
|
+
genericTokens,
|
|
116
|
+
} as NamedTypeWithGenerics<ObjectType>;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (ts.isInterfaceDeclaration(node)) {
|
|
120
|
+
let genericTokens;
|
|
121
|
+
if (isGenericInterfaceDeclaration(node)) {
|
|
122
|
+
genericTokens = this.generateGenerics(node.typeParameters);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const baseObject = {
|
|
126
|
+
name: node.name.getText(),
|
|
127
|
+
type: 'object',
|
|
128
|
+
source: fileName,
|
|
129
|
+
...this.fromTsObjectMembers(node),
|
|
130
|
+
...decorateNode(node),
|
|
131
|
+
genericTokens,
|
|
132
|
+
};
|
|
133
|
+
// See if there are interfaces being implemented/extended
|
|
134
|
+
if (node.heritageClauses) {
|
|
135
|
+
return this.handleHeritageClauses(
|
|
136
|
+
node.heritageClauses,
|
|
137
|
+
baseObject as ObjectType,
|
|
138
|
+
this.context.typeChecker
|
|
139
|
+
) as NamedType<ObjectType>;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return baseObject as NamedType<ObjectType>;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
this.context.throwError(
|
|
146
|
+
`Error: type node is not an Interface or a Type, can't convert as Declaration`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Converts an arbitrary ts.TypeNode to XLRs */
|
|
151
|
+
public convertTsTypeNode(node: ts.TypeNode): NodeType | undefined {
|
|
152
|
+
if (this.context.cache.convertedNodes.has(node)) {
|
|
153
|
+
return this.context.cache.convertedNodes.get(node);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const convertedNode = this.tsNodeToType(node);
|
|
157
|
+
this.context.cache.convertedNodes.set(node, convertedNode);
|
|
158
|
+
return convertedNode;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private tsNodeToType(node: ts.TypeNode): NodeType | undefined {
|
|
162
|
+
if (ts.isUnionTypeNode(node)) {
|
|
163
|
+
return {
|
|
164
|
+
type: 'or',
|
|
165
|
+
or: node.types
|
|
166
|
+
.map((child) => this.convertTsTypeNode(child))
|
|
167
|
+
.filter(isNonNullable),
|
|
168
|
+
...decorateNode(node),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (ts.isIntersectionTypeNode(node)) {
|
|
173
|
+
return {
|
|
174
|
+
type: 'and',
|
|
175
|
+
and: node.types
|
|
176
|
+
.map((child) => this.convertTsTypeNode(child))
|
|
177
|
+
.filter(isNonNullable),
|
|
178
|
+
...decorateNode(node),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (ts.isParenthesizedTypeNode(node)) {
|
|
183
|
+
const children = [...node.getChildren()];
|
|
184
|
+
|
|
185
|
+
if (children[0]?.kind === ts.SyntaxKind.OpenParenToken) {
|
|
186
|
+
children.shift();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (
|
|
190
|
+
children[children.length - 1]?.kind === ts.SyntaxKind.CloseParenToken
|
|
191
|
+
) {
|
|
192
|
+
children.pop();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const element = children[0];
|
|
196
|
+
|
|
197
|
+
if (children.length !== 1 || !ts.isTypeNode(element)) {
|
|
198
|
+
this.context.throwError(
|
|
199
|
+
`Parenthesis type not understood. Length ${
|
|
200
|
+
children.length
|
|
201
|
+
}, Is Type Node: ${ts.SyntaxKind[element.kind]}`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return this.convertTsTypeNode(element);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (node.kind === ts.SyntaxKind.AnyKeyword) {
|
|
209
|
+
return { type: 'any', ...decorateNode(node) };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (node.kind === ts.SyntaxKind.UnknownKeyword) {
|
|
213
|
+
return { type: 'unknown', ...decorateNode(node) };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (node.kind === ts.SyntaxKind.StringKeyword) {
|
|
217
|
+
return { type: 'string', ...decorateNode(node) };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (node.kind === ts.SyntaxKind.NumberKeyword) {
|
|
221
|
+
return { type: 'number', ...decorateNode(node) };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (node.kind === ts.SyntaxKind.BooleanKeyword) {
|
|
225
|
+
return { type: 'boolean', ...decorateNode(node) };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (node.kind === ts.SyntaxKind.UndefinedKeyword) {
|
|
229
|
+
return { type: 'undefined', ...decorateNode(node) };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (node.kind === ts.SyntaxKind.NeverKeyword) {
|
|
233
|
+
return { type: 'never', ...decorateNode(node) };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (node.kind === ts.SyntaxKind.ObjectKeyword) {
|
|
237
|
+
return {
|
|
238
|
+
type: 'object',
|
|
239
|
+
properties: {},
|
|
240
|
+
additionalProperties: AnyTypeNode,
|
|
241
|
+
...decorateNode(node),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (ts.isTemplateLiteralTypeNode(node)) {
|
|
246
|
+
let format;
|
|
247
|
+
|
|
248
|
+
if (this.context.cache.convertedTemplates.has(node)) {
|
|
249
|
+
format = this.context.cache.convertedTemplates.get(node) as string;
|
|
250
|
+
} else {
|
|
251
|
+
format = buildTemplateRegex(node, this.context.typeChecker);
|
|
252
|
+
this.context.cache.convertedTemplates.set(node, format);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
type: 'template',
|
|
257
|
+
format,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (ts.isArrayTypeNode(node)) {
|
|
262
|
+
return {
|
|
263
|
+
type: 'array',
|
|
264
|
+
elementType: this.convertTsTypeNode(node.elementType) ?? AnyTypeNode,
|
|
265
|
+
...decorateNode(node),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (ts.isConditionalTypeNode(node)) {
|
|
270
|
+
const xlrNode = {
|
|
271
|
+
type: 'conditional',
|
|
272
|
+
check: {
|
|
273
|
+
left: this.convertTsTypeNode(node.checkType) as NodeType,
|
|
274
|
+
right: this.convertTsTypeNode(node.extendsType) as NodeType,
|
|
275
|
+
},
|
|
276
|
+
value: {
|
|
277
|
+
true: this.convertTsTypeNode(node.trueType) as NodeType,
|
|
278
|
+
false: this.convertTsTypeNode(node.falseType) as NodeType,
|
|
279
|
+
},
|
|
280
|
+
} as ConditionalType;
|
|
281
|
+
return resolveConditional(xlrNode);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (ts.isTypeReferenceNode(node)) {
|
|
285
|
+
return this.resolveRefNode(node);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (ts.isTupleTypeNode(node)) {
|
|
289
|
+
return {
|
|
290
|
+
type: 'tuple',
|
|
291
|
+
...this.fromTsTuple(node),
|
|
292
|
+
...decorateNode(node),
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (ts.isLiteralTypeNode(node)) {
|
|
297
|
+
if (ts.isNumericLiteral(node.literal)) {
|
|
298
|
+
return {
|
|
299
|
+
type: 'number',
|
|
300
|
+
const: Number(node.literal.text),
|
|
301
|
+
...decorateNode(node),
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (ts.isStringLiteral(node.literal)) {
|
|
306
|
+
return {
|
|
307
|
+
type: 'string',
|
|
308
|
+
const: node.literal.text,
|
|
309
|
+
...decorateNode(node),
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (node.literal.kind === ts.SyntaxKind.TrueKeyword) {
|
|
314
|
+
return {
|
|
315
|
+
type: 'boolean',
|
|
316
|
+
const: true,
|
|
317
|
+
...decorateNode(node),
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (node.literal.kind === ts.SyntaxKind.FalseKeyword) {
|
|
322
|
+
return {
|
|
323
|
+
type: 'boolean',
|
|
324
|
+
const: false,
|
|
325
|
+
...decorateNode(node),
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (node.literal.kind === ts.SyntaxKind.NullKeyword) {
|
|
330
|
+
return { type: 'null', ...decorateNode(node) };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (node.literal.kind === ts.SyntaxKind.PrefixUnaryExpression) {
|
|
334
|
+
this.context.throwError('Prefix unary expressions not supported');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
this.context.throwError('Literal type not understood');
|
|
338
|
+
} else if (ts.isTypeLiteralNode(node)) {
|
|
339
|
+
return {
|
|
340
|
+
type: 'object',
|
|
341
|
+
...this.fromTsObjectMembers(node),
|
|
342
|
+
...decorateNode(node),
|
|
343
|
+
};
|
|
344
|
+
} else if (ts.isFunctionTypeNode(node)) {
|
|
345
|
+
const parameters: Array<FunctionTypeParameters> = node.parameters.map(
|
|
346
|
+
(param) => {
|
|
347
|
+
let typeNode;
|
|
348
|
+
if (param.type) {
|
|
349
|
+
typeNode = this.convertTsTypeNode(param.type);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return {
|
|
353
|
+
name: param.name.getText(),
|
|
354
|
+
type: typeNode ?? AnyTypeNode,
|
|
355
|
+
optional: param.questionToken ? true : undefined,
|
|
356
|
+
default: param.initializer
|
|
357
|
+
? this.convertTsTypeNode(
|
|
358
|
+
param.initializer as unknown as ts.TypeNode
|
|
359
|
+
)
|
|
360
|
+
: undefined,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
const returnType =
|
|
366
|
+
node.type.kind === ts.SyntaxKind.VoidKeyword
|
|
367
|
+
? undefined
|
|
368
|
+
: this.convertTsTypeNode(node.type);
|
|
369
|
+
|
|
370
|
+
return {
|
|
371
|
+
type: 'function',
|
|
372
|
+
parameters,
|
|
373
|
+
returnType,
|
|
374
|
+
...decorateNode(node),
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Handle generics
|
|
379
|
+
else if (ts.isIndexedAccessTypeNode(node)) {
|
|
380
|
+
if (
|
|
381
|
+
ts.isTypeReferenceNode(node.objectType) &&
|
|
382
|
+
ts.isLiteralTypeNode(node.indexType)
|
|
383
|
+
) {
|
|
384
|
+
const baseObject = this.convertTsTypeNode(
|
|
385
|
+
node.objectType
|
|
386
|
+
) as ObjectType;
|
|
387
|
+
const accessor = node.indexType.literal.getText().replace(/["']/g, '');
|
|
388
|
+
if (Object.keys(baseObject.properties ?? {}).includes(accessor)) {
|
|
389
|
+
return baseObject.properties[accessor].node;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (baseObject.additionalProperties) {
|
|
393
|
+
return baseObject.additionalProperties;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return { type: 'null' };
|
|
398
|
+
} else {
|
|
399
|
+
this.context.throwError(`Unimplemented type ${ts.SyntaxKind[node.kind]}`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
private fromTsObjectMembers(
|
|
404
|
+
node: ts.InterfaceDeclaration | ts.TypeLiteralNode
|
|
405
|
+
): Pick<ObjectType, 'properties' | 'additionalProperties'> {
|
|
406
|
+
const ret: Pick<ObjectType, 'properties' | 'additionalProperties'> = {
|
|
407
|
+
properties: {},
|
|
408
|
+
additionalProperties: false,
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
node.members.forEach((member) => {
|
|
412
|
+
if (ts.isPropertySignature(member) && member.type) {
|
|
413
|
+
const name = member.name.getText();
|
|
414
|
+
ret.properties[name] = {
|
|
415
|
+
required: !isOptionalProperty(member),
|
|
416
|
+
node: {
|
|
417
|
+
...(this.convertTsTypeNode(member.type) ?? AnyTypeNode),
|
|
418
|
+
...decorateNode(member),
|
|
419
|
+
},
|
|
420
|
+
};
|
|
421
|
+
} else if (ts.isIndexSignatureDeclaration(member)) {
|
|
422
|
+
const param = member.parameters[0];
|
|
423
|
+
if (param.type?.kind !== ts.SyntaxKind.StringKeyword) {
|
|
424
|
+
this.context.throwError(
|
|
425
|
+
'Will not convert non-string index signature'
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
ret.additionalProperties =
|
|
430
|
+
this.convertTsTypeNode(member.type) ?? AnyTypeNode;
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
return ret;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
private fromTsTuple(
|
|
438
|
+
node: ts.TupleTypeNode
|
|
439
|
+
): Pick<TupleType, 'elementTypes' | 'additionalItems' | 'minItems'> {
|
|
440
|
+
if (node.elements.length === 0) {
|
|
441
|
+
return { elementTypes: [], additionalItems: false, minItems: 0 };
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const hasRest = ts.isRestTypeNode(node.elements[node.elements.length - 1]);
|
|
445
|
+
|
|
446
|
+
const [elements, rest] = hasRest
|
|
447
|
+
? [
|
|
448
|
+
node.elements.slice(0, node.elements.length - 1),
|
|
449
|
+
node.elements[node.elements.length - 1] as ts.RestTypeNode,
|
|
450
|
+
]
|
|
451
|
+
: [[...node.elements], undefined];
|
|
452
|
+
|
|
453
|
+
const elementTypes = elements.map(
|
|
454
|
+
(element) =>
|
|
455
|
+
this.convertTsTypeNode(tsStripOptionalType(element)) ?? AnyTypeNode
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
const additionalItems = rest
|
|
459
|
+
? this.convertTsTypeNode((rest.type as ts.ArrayTypeNode).elementType) ??
|
|
460
|
+
AnyTypeNode
|
|
461
|
+
: false;
|
|
462
|
+
|
|
463
|
+
const firstOptional = elements.findIndex((element) =>
|
|
464
|
+
ts.isOptionalTypeNode(element)
|
|
465
|
+
);
|
|
466
|
+
const minItems = firstOptional === -1 ? elements.length : firstOptional;
|
|
467
|
+
|
|
468
|
+
return {
|
|
469
|
+
elementTypes,
|
|
470
|
+
...(additionalItems && additionalItems.type === 'any'
|
|
471
|
+
? { additionalItems: AnyTypeNode }
|
|
472
|
+
: { additionalItems }),
|
|
473
|
+
minItems,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
private handleHeritageClauses(
|
|
478
|
+
clauses: ts.NodeArray<ts.HeritageClause>,
|
|
479
|
+
baseObject: ObjectType,
|
|
480
|
+
typeChecker: ts.TypeChecker
|
|
481
|
+
): ObjectType {
|
|
482
|
+
let newProperties: { [x: string]: ObjectProperty } = {};
|
|
483
|
+
let newAdditionalProperties: NodeType | false = false;
|
|
484
|
+
|
|
485
|
+
clauses.forEach((heritageClause) => {
|
|
486
|
+
const parent = heritageClause.types[0];
|
|
487
|
+
const parentType = typeChecker.getTypeAtLocation(parent);
|
|
488
|
+
const parentSymbol = parentType.symbol;
|
|
489
|
+
const parentDeclarations = parentSymbol?.declarations;
|
|
490
|
+
let parentInterface = parentDeclarations?.[0];
|
|
491
|
+
|
|
492
|
+
if (
|
|
493
|
+
parentInterface &&
|
|
494
|
+
ts.isTypeLiteralNode(parentInterface) &&
|
|
495
|
+
ts.isTypeAliasDeclaration(parentInterface.parent)
|
|
496
|
+
) {
|
|
497
|
+
// check for if the node is a type to get the actual type declaration
|
|
498
|
+
parentInterface = parentInterface.parent;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (parentInterface && isTopLevelNode(parentInterface)) {
|
|
502
|
+
const parentInterfaceType = this.convertDeclaration(parentInterface);
|
|
503
|
+
if (parentInterface.typeParameters && parent.typeArguments) {
|
|
504
|
+
const filledInInterface = this.solveGenerics(
|
|
505
|
+
parentInterfaceType as NodeTypeWithGenerics,
|
|
506
|
+
parentInterface.typeParameters,
|
|
507
|
+
parent.typeArguments
|
|
508
|
+
) as NamedType<ObjectType>;
|
|
509
|
+
newProperties = filledInInterface.properties;
|
|
510
|
+
newAdditionalProperties = filledInInterface.additionalProperties;
|
|
511
|
+
} else {
|
|
512
|
+
if (isGenericNodeType(baseObject)) {
|
|
513
|
+
baseObject.genericTokens.push(
|
|
514
|
+
...((parentInterfaceType as NodeTypeWithGenerics).genericTokens ??
|
|
515
|
+
[])
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
newProperties = parentInterfaceType.properties;
|
|
520
|
+
newAdditionalProperties = parentInterfaceType.additionalProperties;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
newAdditionalProperties =
|
|
525
|
+
baseObject.additionalProperties === false
|
|
526
|
+
? newAdditionalProperties
|
|
527
|
+
: false;
|
|
528
|
+
return {
|
|
529
|
+
...baseObject,
|
|
530
|
+
properties: { ...newProperties, ...baseObject.properties },
|
|
531
|
+
additionalProperties: newAdditionalProperties,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
private solveGenerics(
|
|
536
|
+
baseInterface: NodeTypeWithGenerics,
|
|
537
|
+
typeParameters: ts.NodeArray<ts.TypeParameterDeclaration>,
|
|
538
|
+
typeArguments?: ts.NodeArray<ts.TypeNode>
|
|
539
|
+
): NodeTypeWithGenerics | NodeType {
|
|
540
|
+
// map type args to generics
|
|
541
|
+
if (typeArguments && typeArguments.length === 0) return baseInterface;
|
|
542
|
+
const genericMap: Map<string, NodeType> = new Map();
|
|
543
|
+
typeParameters.forEach((tp, i) => {
|
|
544
|
+
let typeToProcess: ts.TypeNode;
|
|
545
|
+
if (typeArguments && i < typeArguments.length) {
|
|
546
|
+
typeToProcess = typeArguments[i];
|
|
547
|
+
} else if (tp.default) {
|
|
548
|
+
typeToProcess = tp.default;
|
|
549
|
+
} else {
|
|
550
|
+
// might need to do some error checking here if there is no type to fill in
|
|
551
|
+
typeToProcess = ts.factory.createKeywordTypeNode(
|
|
552
|
+
ts.SyntaxKind.AnyKeyword
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const processedNodeType = this.convertTsTypeNode(typeToProcess);
|
|
557
|
+
if (processedNodeType) {
|
|
558
|
+
genericMap.set(tp.name.getText(), processedNodeType);
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
return fillInGenerics(baseInterface, genericMap);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
private generateGenerics(
|
|
566
|
+
params: ts.NodeArray<ts.TypeParameterDeclaration> | undefined
|
|
567
|
+
): Array<ParamTypeNode> {
|
|
568
|
+
const genericArray: Array<ParamTypeNode> = [];
|
|
569
|
+
|
|
570
|
+
params?.forEach((param) => {
|
|
571
|
+
const serializedObject: ParamTypeNode = {
|
|
572
|
+
symbol: param.name.text,
|
|
573
|
+
};
|
|
574
|
+
if (param.constraint) {
|
|
575
|
+
// any case is unsafe but it can be either a Type or TypeNode and both are parsed fine
|
|
576
|
+
serializedObject.constraints = this.convertTsTypeNode(param.constraint);
|
|
577
|
+
} else {
|
|
578
|
+
serializedObject.constraints = AnyTypeNode;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
if (param.default) {
|
|
582
|
+
// any case is unsafe but it can be either a Type or TypeNode and both are parsed fine
|
|
583
|
+
serializedObject.default = this.convertTsTypeNode(param.default);
|
|
584
|
+
} else {
|
|
585
|
+
serializedObject.default = AnyTypeNode;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
genericArray.push(serializedObject);
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
return genericArray;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
private resolveRefNode(node: ts.TypeReferenceNode): NodeType {
|
|
595
|
+
let refName: string;
|
|
596
|
+
|
|
597
|
+
if (node.typeName.kind === ts.SyntaxKind.QualifiedName) {
|
|
598
|
+
refName = `${node.typeName.left.getText()}.${node.typeName.right.getText()}`;
|
|
599
|
+
} else {
|
|
600
|
+
refName = node.typeName.text;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// the use of a generic in an interface/type
|
|
604
|
+
if (isTypeReferenceGeneric(node, this.context.typeChecker)) {
|
|
605
|
+
if (ts.isIndexedAccessTypeNode(node.parent)) {
|
|
606
|
+
const genericSymbol = this.context.typeChecker.getSymbolAtLocation(
|
|
607
|
+
node.typeName
|
|
608
|
+
);
|
|
609
|
+
const typeParameters = this.generateGenerics(
|
|
610
|
+
genericSymbol?.declarations as unknown as ts.NodeArray<ts.TypeParameterDeclaration>
|
|
611
|
+
);
|
|
612
|
+
const typeParameter = typeParameters[0];
|
|
613
|
+
if (typeParameter) {
|
|
614
|
+
if (typeParameter.constraints) {
|
|
615
|
+
return typeParameter.constraints;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (typeParameter.default) {
|
|
619
|
+
return typeParameter.default;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
return AnyTypeNode;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
return { type: 'ref', ref: node.getText(), ...decorateNode(node) };
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if (refName === 'Array') {
|
|
630
|
+
const typeArgs = node.typeArguments as ts.NodeArray<ts.TypeNode>;
|
|
631
|
+
return {
|
|
632
|
+
type: 'array',
|
|
633
|
+
elementType: typeArgs
|
|
634
|
+
? this.convertTsTypeNode(typeArgs[0]) ?? AnyTypeNode
|
|
635
|
+
: AnyTypeNode,
|
|
636
|
+
...decorateNode(node),
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if (refName === 'Record') {
|
|
641
|
+
const indexType = node.typeArguments?.[0] as ts.TypeNode;
|
|
642
|
+
const valueType = node.typeArguments?.[1] as ts.TypeNode;
|
|
643
|
+
return {
|
|
644
|
+
type: 'record',
|
|
645
|
+
keyType: this.convertTsTypeNode(indexType) ?? AnyTypeNode,
|
|
646
|
+
valueType: this.convertTsTypeNode(valueType) ?? AnyTypeNode,
|
|
647
|
+
...decorateNode(node),
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
if (refName === 'Pick' || refName === 'Omit') {
|
|
652
|
+
const baseType = node.typeArguments?.[0] as ts.TypeNode;
|
|
653
|
+
const modifiers = node.typeArguments?.[1] as ts.TypeNode;
|
|
654
|
+
|
|
655
|
+
const baseObj = this.convertTsTypeNode(baseType) as NamedType<ObjectType>;
|
|
656
|
+
const modifierNames = getStringLiteralsFromUnion(modifiers);
|
|
657
|
+
|
|
658
|
+
return (
|
|
659
|
+
applyPickOrOmitToNodeType(baseObj, refName, modifierNames) ?? {
|
|
660
|
+
type: 'never',
|
|
661
|
+
}
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (refName === 'Partial' || refName === 'Required') {
|
|
666
|
+
const baseType = node.typeArguments?.[0] as ts.TypeNode;
|
|
667
|
+
const baseObj = this.convertTsTypeNode(baseType) as NodeType;
|
|
668
|
+
const modifier = refName !== 'Partial';
|
|
669
|
+
|
|
670
|
+
return applyPartialOrRequiredToNodeType(baseObj, modifier);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// catch all for all other type references
|
|
674
|
+
if (!this.context.customPrimitives.includes(refName)) {
|
|
675
|
+
const typeInfo = getReferencedType(node, this.context.typeChecker);
|
|
676
|
+
if (typeInfo) {
|
|
677
|
+
const genericType = this.convertDeclaration(typeInfo.declaration);
|
|
678
|
+
const genericParams = typeInfo.declaration.typeParameters;
|
|
679
|
+
const genericArgs = node.typeArguments;
|
|
680
|
+
if (genericType && genericParams && genericArgs) {
|
|
681
|
+
return this.solveGenerics(
|
|
682
|
+
genericType as NamedTypeWithGenerics,
|
|
683
|
+
genericParams,
|
|
684
|
+
genericArgs
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
if (genericType) {
|
|
689
|
+
return genericType;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
this.context.throwError(
|
|
694
|
+
`Can't find referenced type ${refName}, is it available in the current package or node_modules?`
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
const genericArgs: Array<NodeType | NamedType<ObjectType>> = [];
|
|
699
|
+
if (node.typeArguments) {
|
|
700
|
+
node.typeArguments.forEach((typeArg) => {
|
|
701
|
+
let convertedNode;
|
|
702
|
+
if (isTopLevelNode(typeArg)) {
|
|
703
|
+
convertedNode = this.convertDeclaration(typeArg);
|
|
704
|
+
} else {
|
|
705
|
+
convertedNode = this.convertTsTypeNode(typeArg);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
if (convertedNode) {
|
|
709
|
+
genericArgs.push(convertedNode);
|
|
710
|
+
} else {
|
|
711
|
+
this.context.throwError(
|
|
712
|
+
`Conversion Error: Couldn't convert type argument in type ${refName}`
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
return {
|
|
719
|
+
type: 'ref',
|
|
720
|
+
ref: node.getText(),
|
|
721
|
+
...decorateNode(node),
|
|
722
|
+
genericArguments: genericArgs.length > 0 ? genericArgs : undefined,
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
}
|