@@ -888,6 +888,13 @@ export class JsonSchemaGenerator {
888
888
889
889
private getClassDefinition ( clazzType : ts . Type , definition : Definition ) : Definition {
890
890
const node = clazzType . getSymbol ( ) ! . getDeclarations ( ) ! [ 0 ] ;
891
+
892
+ // Example: typeof globalThis may not have any declaration
893
+ if ( ! node ) {
894
+ definition . type = "object" ;
895
+ return definition ;
896
+ }
897
+
891
898
if ( this . args . typeOfKeyword && node . kind === ts . SyntaxKind . FunctionType ) {
892
899
definition . typeof = "function" ;
893
900
return definition ;
@@ -1048,6 +1055,8 @@ export class JsonSchemaGenerator {
1048
1055
return name ;
1049
1056
}
1050
1057
1058
+ private recursiveTypeRef = new Map ( ) ;
1059
+
1051
1060
private getTypeDefinition (
1052
1061
typ : ts . Type ,
1053
1062
asRef = this . args . ref ,
@@ -1084,9 +1093,11 @@ export class JsonSchemaGenerator {
1084
1093
// FIXME: We can't just compare the name of the symbol - it ignores the namespace
1085
1094
const isRawType =
1086
1095
! symbol ||
1087
- this . tc . getFullyQualifiedName ( symbol ) === "Date" ||
1088
- symbol . name === "integer" ||
1089
- this . tc . getIndexInfoOfType ( typ , ts . IndexKind . Number ) !== undefined ;
1096
+ // Window is incorrectly marked as rawType here for some reason
1097
+ ( this . tc . getFullyQualifiedName ( symbol ) !== "Window" &&
1098
+ ( this . tc . getFullyQualifiedName ( symbol ) === "Date" ||
1099
+ symbol . name === "integer" ||
1100
+ this . tc . getIndexInfoOfType ( typ , ts . IndexKind . Number ) !== undefined ) ) ;
1090
1101
1091
1102
// special case: an union where all child are string literals -> make an enum instead
1092
1103
let isStringEnum = false ;
@@ -1106,6 +1117,7 @@ export class JsonSchemaGenerator {
1106
1117
) {
1107
1118
asRef = false ; // raw types and inline types cannot be reffed,
1108
1119
// unless we are handling a type alias
1120
+ // or it is recursive type - see below
1109
1121
}
1110
1122
}
1111
1123
@@ -1116,15 +1128,16 @@ export class JsonSchemaGenerator {
1116
1128
reffedType ! . getFlags ( ) & ts . SymbolFlags . Alias ? this . tc . getAliasedSymbol ( reffedType ! ) : reffedType !
1117
1129
)
1118
1130
. replace ( REGEX_FILE_NAME_OR_SPACE , "" ) ;
1119
- if ( this . args . uniqueNames ) {
1120
- const sourceFile = getSourceFile ( reffedType ! ) ;
1131
+ if ( this . args . uniqueNames && reffedType ) {
1132
+ const sourceFile = getSourceFile ( reffedType ) ;
1121
1133
const relativePath = path . relative ( process . cwd ( ) , sourceFile . fileName ) ;
1122
1134
fullTypeName = `${ typeName } .${ generateHashOfNode ( getCanonicalDeclaration ( reffedType ! ) , relativePath ) } ` ;
1123
1135
} else {
1124
1136
fullTypeName = this . makeTypeNameUnique ( typ , typeName ) ;
1125
1137
}
1126
- } else if ( asRef ) {
1127
- if ( this . args . uniqueNames ) {
1138
+ } else {
1139
+ // typ.symbol can be undefined
1140
+ if ( this . args . uniqueNames && typ . symbol ) {
1128
1141
const sym = typ . symbol ;
1129
1142
const sourceFile = getSourceFile ( sym ) ;
1130
1143
const relativePath = path . relative ( process . cwd ( ) , sourceFile . fileName ) ;
@@ -1139,6 +1152,15 @@ export class JsonSchemaGenerator {
1139
1152
}
1140
1153
}
1141
1154
1155
+ // Handle recursive types
1156
+ if ( ! isRawType || ! ! typ . aliasSymbol ) {
1157
+ if ( this . recursiveTypeRef . has ( fullTypeName ) ) {
1158
+ asRef = true ;
1159
+ } else {
1160
+ this . recursiveTypeRef . set ( fullTypeName , definition ) ;
1161
+ }
1162
+ }
1163
+
1142
1164
if ( asRef ) {
1143
1165
// We don't return the full definition, but we put it into
1144
1166
// reffedDefinitions below.
@@ -1227,6 +1249,26 @@ export class JsonSchemaGenerator {
1227
1249
}
1228
1250
}
1229
1251
1252
+ if ( this . recursiveTypeRef . get ( fullTypeName ) === definition ) {
1253
+ this . recursiveTypeRef . delete ( fullTypeName ) ;
1254
+ // If the type was recursive (there is reffedDefinitions) - lets replace it to reference
1255
+ if ( this . reffedDefinitions [ fullTypeName ] ) {
1256
+ // Here we may want to filter out all type specific fields
1257
+ // and include fields like description etc
1258
+ const annotations = Object . entries ( returnedDefinition ) . reduce ( ( acc , [ key , value ] ) => {
1259
+ if ( validationKeywords [ key ] && typeof value !== undefined ) {
1260
+ acc [ key ] = value ;
1261
+ }
1262
+ return acc ;
1263
+ } , { } ) ;
1264
+
1265
+ returnedDefinition = {
1266
+ $ref : `${ this . args . id } #/definitions/` + fullTypeName ,
1267
+ ...annotations ,
1268
+ } ;
1269
+ }
1270
+ }
1271
+
1230
1272
if ( otherAnnotations [ "nullable" ] ) {
1231
1273
makeNullable ( returnedDefinition ) ;
1232
1274
}
0 commit comments