@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.
@@ -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
+ }