@@ -888,6 +888,13 @@ export class JsonSchemaGenerator {
888888
889889 private getClassDefinition ( clazzType : ts . Type , definition : Definition ) : Definition {
890890 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+
891898 if ( this . args . typeOfKeyword && node . kind === ts . SyntaxKind . FunctionType ) {
892899 definition . typeof = "function" ;
893900 return definition ;
@@ -1048,6 +1055,8 @@ export class JsonSchemaGenerator {
10481055 return name ;
10491056 }
10501057
1058+ private recursiveTypeRef = new Map ( ) ;
1059+
10511060 private getTypeDefinition (
10521061 typ : ts . Type ,
10531062 asRef = this . args . ref ,
@@ -1084,9 +1093,11 @@ export class JsonSchemaGenerator {
10841093 // FIXME: We can't just compare the name of the symbol - it ignores the namespace
10851094 const isRawType =
10861095 ! 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 ) ) ;
10901101
10911102 // special case: an union where all child are string literals -> make an enum instead
10921103 let isStringEnum = false ;
@@ -1106,6 +1117,7 @@ export class JsonSchemaGenerator {
11061117 ) {
11071118 asRef = false ; // raw types and inline types cannot be reffed,
11081119 // unless we are handling a type alias
1120+ // or it is recursive type - see below
11091121 }
11101122 }
11111123
@@ -1116,15 +1128,16 @@ export class JsonSchemaGenerator {
11161128 reffedType ! . getFlags ( ) & ts . SymbolFlags . Alias ? this . tc . getAliasedSymbol ( reffedType ! ) : reffedType !
11171129 )
11181130 . 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 ) ;
11211133 const relativePath = path . relative ( process . cwd ( ) , sourceFile . fileName ) ;
11221134 fullTypeName = `${ typeName } .${ generateHashOfNode ( getCanonicalDeclaration ( reffedType ! ) , relativePath ) } ` ;
11231135 } else {
11241136 fullTypeName = this . makeTypeNameUnique ( typ , typeName ) ;
11251137 }
1126- } else if ( asRef ) {
1127- if ( this . args . uniqueNames ) {
1138+ } else {
1139+ // typ.symbol can be undefined
1140+ if ( this . args . uniqueNames && typ . symbol ) {
11281141 const sym = typ . symbol ;
11291142 const sourceFile = getSourceFile ( sym ) ;
11301143 const relativePath = path . relative ( process . cwd ( ) , sourceFile . fileName ) ;
@@ -1139,6 +1152,15 @@ export class JsonSchemaGenerator {
11391152 }
11401153 }
11411154
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+
11421164 if ( asRef ) {
11431165 // We don't return the full definition, but we put it into
11441166 // reffedDefinitions below.
@@ -1227,6 +1249,26 @@ export class JsonSchemaGenerator {
12271249 }
12281250 }
12291251
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+
12301272 if ( otherAnnotations [ "nullable" ] ) {
12311273 makeNullable ( returnedDefinition ) ;
12321274 }
0 commit comments