11import ts from "typescript" ;
2+ import type { Config } from "./Config.js" ;
23import { NoRootTypeError } from "./Error/NoRootTypeError.js" ;
3- import { Context , NodeParser } from "./NodeParser.js" ;
4- import { Definition } from "./Schema/Definition.js" ;
5- import { Schema } from "./Schema/Schema.js" ;
6- import { BaseType } from "./Type/BaseType.js" ;
4+ import { Context , type NodeParser } from "./NodeParser.js" ;
5+ import type { Definition } from "./Schema/Definition.js" ;
6+ import type { Schema } from "./Schema/Schema.js" ;
7+ import type { BaseType } from "./Type/BaseType.js" ;
78import { DefinitionType } from "./Type/DefinitionType.js" ;
8- import { TypeFormatter } from "./TypeFormatter.js" ;
9- import { StringMap } from "./Utils/StringMap.js" ;
10- import { localSymbolAtNode , symbolAtNode } from "./Utils/symbolAtNode.js" ;
11- import { removeUnreachable } from "./Utils/removeUnreachable.js" ;
12- import { Config } from "./Config.js" ;
9+ import type { TypeFormatter } from "./TypeFormatter.js" ;
10+ import type { StringMap } from "./Utils/StringMap.js" ;
1311import { hasJsDocTag } from "./Utils/hasJsDocTag.js" ;
12+ import { removeUnreachable } from "./Utils/removeUnreachable.js" ;
1413
1514export class SchemaGenerator {
1615 public constructor (
@@ -32,7 +31,10 @@ export class SchemaGenerator {
3231
3332 const rootTypeDefinition = rootTypes . length === 1 ? this . getRootTypeDefinition ( rootTypes [ 0 ] ) : undefined ;
3433 const definitions : StringMap < Definition > = { } ;
35- rootTypes . forEach ( ( rootType ) => this . appendRootChildDefinitions ( rootType , definitions ) ) ;
34+
35+ for ( const rootType of rootTypes ) {
36+ this . appendRootChildDefinitions ( rootType , definitions ) ;
37+ }
3638
3739 const reachableDefinitions = removeUnreachable ( rootTypeDefinition , definitions ) ;
3840
@@ -47,15 +49,15 @@ export class SchemaGenerator {
4749 protected getRootNodes ( fullName : string | undefined ) : ts . Node [ ] {
4850 if ( fullName && fullName !== "*" ) {
4951 return [ this . findNamedNode ( fullName ) ] ;
50- } else {
51- const rootFileNames = this . program . getRootFileNames ( ) ;
52- const rootSourceFiles = this . program
53- . getSourceFiles ( )
54- . filter ( ( sourceFile ) => rootFileNames . includes ( sourceFile . fileName ) ) ;
55- const rootNodes = new Map < string , ts . Node > ( ) ;
56- this . appendTypes ( rootSourceFiles , this . program . getTypeChecker ( ) , rootNodes ) ;
57- return [ ...rootNodes . values ( ) ] ;
5852 }
53+
54+ const rootFileNames = this . program . getRootFileNames ( ) ;
55+ const rootSourceFiles = this . program
56+ . getSourceFiles ( )
57+ . filter ( ( sourceFile ) => rootFileNames . includes ( sourceFile . fileName ) ) ;
58+ const rootNodes = new Map < string , ts . Node > ( ) ;
59+ this . appendTypes ( rootSourceFiles , this . program . getTypeChecker ( ) , rootNodes ) ;
60+ return [ ...rootNodes . values ( ) ] ;
5961 }
6062 protected findNamedNode ( fullName : string ) : ts . Node {
6163 const typeChecker = this . program . getTypeChecker ( ) ;
@@ -129,6 +131,7 @@ export class SchemaGenerator {
129131
130132 return { projectFiles, externalFiles } ;
131133 }
134+
132135 protected appendTypes (
133136 sourceFiles : readonly ts . SourceFile [ ] ,
134137 typeChecker : ts . TypeChecker ,
@@ -138,72 +141,131 @@ export class SchemaGenerator {
138141 this . inspectNode ( sourceFile , typeChecker , types ) ;
139142 }
140143 }
144+
141145 protected inspectNode ( node : ts . Node , typeChecker : ts . TypeChecker , allTypes : Map < string , ts . Node > ) : void {
142- switch ( node . kind ) {
143- case ts . SyntaxKind . VariableDeclaration : {
144- const variableDeclarationNode = node as ts . VariableDeclaration ;
145- if (
146- variableDeclarationNode . initializer ?. kind === ts . SyntaxKind . ArrowFunction ||
147- variableDeclarationNode . initializer ?. kind === ts . SyntaxKind . FunctionExpression
148- ) {
149- this . inspectNode ( variableDeclarationNode . initializer , typeChecker , allTypes ) ;
150- }
151- return ;
146+ if ( ts . isVariableDeclaration ( node ) ) {
147+ if (
148+ node . initializer ?. kind === ts . SyntaxKind . ArrowFunction ||
149+ node . initializer ?. kind === ts . SyntaxKind . FunctionExpression
150+ ) {
151+ this . inspectNode ( node . initializer , typeChecker , allTypes ) ;
152152 }
153- case ts . SyntaxKind . InterfaceDeclaration :
154- case ts . SyntaxKind . ClassDeclaration :
155- case ts . SyntaxKind . EnumDeclaration :
156- case ts . SyntaxKind . TypeAliasDeclaration :
157- if (
158- this . config ?. expose === "all" ||
159- ( this . isExportType ( node ) && ! this . isGenericType ( node as ts . TypeAliasDeclaration ) )
160- ) {
161- allTypes . set ( this . getFullName ( node , typeChecker ) , node ) ;
162- return ;
163- }
164- return ;
165- case ts . SyntaxKind . ConstructorType :
166- case ts . SyntaxKind . FunctionDeclaration :
167- case ts . SyntaxKind . FunctionExpression :
168- case ts . SyntaxKind . ArrowFunction :
153+
154+ return ;
155+ }
156+
157+ if (
158+ ts . isInterfaceDeclaration ( node ) ||
159+ ts . isClassDeclaration ( node ) ||
160+ ts . isEnumDeclaration ( node ) ||
161+ ts . isTypeAliasDeclaration ( node )
162+ ) {
163+ if (
164+ this . config ?. expose === "all" ||
165+ ( this . isExportType ( node ) && ! this . isGenericType ( node as ts . TypeAliasDeclaration ) )
166+ ) {
169167 allTypes . set ( this . getFullName ( node , typeChecker ) , node ) ;
170168 return ;
171- case ts . SyntaxKind . ExportSpecifier : {
172- const exportSpecifierNode = node as ts . ExportSpecifier ;
173- const symbol = typeChecker . getExportSpecifierLocalTargetSymbol ( exportSpecifierNode ) ;
174- if ( symbol ?. declarations ?. length === 1 ) {
175- const declaration = symbol . declarations [ 0 ] ;
176- if ( declaration . kind === ts . SyntaxKind . ImportSpecifier ) {
177- // Handling the `Foo` in `import { Foo } from "./lib"; export { Foo };`
178- const importSpecifierNode = declaration as ts . ImportSpecifier ;
179- const type = typeChecker . getTypeAtLocation ( importSpecifierNode ) ;
180- if ( type . symbol ?. declarations ?. length === 1 ) {
181- this . inspectNode ( type . symbol . declarations [ 0 ] , typeChecker , allTypes ) ;
182- }
183- } else {
184- // Handling the `Bar` in `export { Bar } from './lib';`
185- this . inspectNode ( declaration , typeChecker , allTypes ) ;
169+ }
170+ return ;
171+ }
172+
173+ if (
174+ ts . isFunctionDeclaration ( node ) ||
175+ ts . isFunctionExpression ( node ) ||
176+ ts . isArrowFunction ( node ) ||
177+ ts . isConstructorTypeNode ( node )
178+ ) {
179+ allTypes . set ( this . getFullName ( node , typeChecker ) , node ) ;
180+ return ;
181+ }
182+
183+ if ( ts . isExportSpecifier ( node ) ) {
184+ const symbol = typeChecker . getExportSpecifierLocalTargetSymbol ( node ) ;
185+
186+ if ( symbol ?. declarations ?. length === 1 ) {
187+ const declaration = symbol . declarations [ 0 ] ;
188+
189+ if ( ts . isImportSpecifier ( declaration ) ) {
190+ // Handling the `Foo` in `import { Foo } from "./lib"; export { Foo };`
191+ const type = typeChecker . getTypeAtLocation ( declaration ) ;
192+
193+ if ( type . symbol ?. declarations ?. length === 1 ) {
194+ this . inspectNode ( type . symbol . declarations [ 0 ] , typeChecker , allTypes ) ;
186195 }
196+ } else {
197+ // Handling the `Bar` in `export { Bar } from './lib';`
198+ this . inspectNode ( declaration , typeChecker , allTypes ) ;
187199 }
200+ }
201+
202+ return ;
203+ }
204+
205+ if ( ts . isExportDeclaration ( node ) ) {
206+ if ( ! ts . isExportDeclaration ( node ) ) {
188207 return ;
189208 }
190- default :
191- ts . forEachChild ( node , ( subnode ) => this . inspectNode ( subnode , typeChecker , allTypes ) ) ;
209+
210+ // export { variable } clauses
211+ if ( ! node . moduleSpecifier ) {
192212 return ;
213+ }
214+
215+ const symbol = typeChecker . getSymbolAtLocation ( node . moduleSpecifier ) ;
216+
217+ // should never hit this (maybe type error in user's code)
218+ if ( ! symbol || ! symbol . declarations ) {
219+ return ;
220+ }
221+
222+ // module augmentation can result in more than one source file
223+ for ( const source of symbol . declarations ) {
224+ const sourceSymbol = typeChecker . getSymbolAtLocation ( source ) ;
225+
226+ if ( ! sourceSymbol ) {
227+ return ;
228+ }
229+
230+ const moduleExports = typeChecker . getExportsOfModule ( sourceSymbol ) ;
231+
232+ for ( const moduleExport of moduleExports ) {
233+ const nodes =
234+ moduleExport . declarations ||
235+ ( ! ! moduleExport . valueDeclaration && [ moduleExport . valueDeclaration ] ) ;
236+
237+ // should never hit this (maybe type error in user's code)
238+ if ( ! nodes ) {
239+ return ;
240+ }
241+
242+ for ( const subnodes of nodes ) {
243+ this . inspectNode ( subnodes , typeChecker , allTypes ) ;
244+ }
245+ }
246+ }
247+
248+ return ;
193249 }
250+
251+ ts . forEachChild ( node , ( subnode ) => this . inspectNode ( subnode , typeChecker , allTypes ) ) ;
194252 }
195- protected isExportType ( node : ts . Node ) : boolean {
253+
254+ protected isExportType (
255+ node : ts . InterfaceDeclaration | ts . ClassDeclaration | ts . EnumDeclaration | ts . TypeAliasDeclaration ,
256+ ) : boolean {
196257 if ( this . config ?. jsDoc !== "none" && hasJsDocTag ( node , "internal" ) ) {
197258 return false ;
198259 }
199- const localSymbol = localSymbolAtNode ( node ) ;
200- return localSymbol ? " exportSymbol" in localSymbol : false ;
260+
261+ return ! ! node . localSymbol ?. exportSymbol ;
201262 }
263+
202264 protected isGenericType ( node : ts . TypeAliasDeclaration ) : boolean {
203265 return ! ! ( node . typeParameters && node . typeParameters . length > 0 ) ;
204266 }
205- protected getFullName ( node : ts . Node , typeChecker : ts . TypeChecker ) : string {
206- const symbol = symbolAtNode ( node ) ! ;
207- return typeChecker . getFullyQualifiedName ( symbol ) . replace ( / " .* " \. / , "" ) ;
267+
268+ protected getFullName ( node : ts . Declaration , typeChecker : ts . TypeChecker ) : string {
269+ return typeChecker . getFullyQualifiedName ( node . symbol ) . replace ( / " .* " \. / , "" ) ;
208270 }
209271}
0 commit comments