1- import { AttributeNode , ClassNode , CommentTrivia , SourceFileNode , TypeDeclarationNode , TypeReferenceNode } from "./ast" ;
1+ import {
2+ AttributeNode ,
3+ ClassNode ,
4+ CommentTrivia ,
5+ PropertyNode ,
6+ SourceFileNode ,
7+ TypeDeclarationNode ,
8+ TypeReferenceNode ,
9+ } from "./ast" ;
210
311export interface GenerateOptions {
412 exportModule ?: boolean ;
@@ -10,6 +18,7 @@ export interface GenerateOptions {
1018 normalizeAcronyms ?: boolean ;
1119 preserveComments ?: boolean ;
1220 convertDocumentationComments ?: boolean ;
21+ inlineInheritedProperties ?: boolean ;
1322}
1423
1524export interface GeneratedFile {
@@ -21,6 +30,11 @@ interface TypeMap {
2130 declarations : Map < string , TypeDeclarationNode > ;
2231}
2332
33+ interface EmittableProperty {
34+ owner : ClassNode ;
35+ property : PropertyNode ;
36+ }
37+
2438const numberTypes = new Set ( [
2539 "byte" ,
2640 "sbyte" ,
@@ -77,7 +91,7 @@ function generateEnum(node: Extract<TypeDeclarationNode, { kind: "enum" }>, opti
7791}
7892
7993function generateClass ( node : ClassNode , typeMap : TypeMap , options : Required < GenerateOptions > ) : string {
80- const imports = collectImports ( node , typeMap ) ;
94+ const imports = collectImports ( node , typeMap , options ) ;
8195 const lines : string [ ] = [ ] ;
8296 for ( const importName of imports ) {
8397 lines . push ( `import { ${ importName } } from ${ quote ( `./${ importName } ` , options ) } ${ statementEnd ( options ) } ` ) ;
@@ -92,35 +106,58 @@ function generateClass(node: ClassNode, typeMap: TypeMap, options: Required<Gene
92106 hasAttribute ( node . attributes , "ReadOnly" ) || hasAttribute ( node . attributes , "ReadonlyProperties" ) ;
93107 pushComments ( lines , node . leadingComments , "" , options ) ;
94108 lines . push ( `export interface ${ node . name } ${ typeParams } ${ base } {` ) ;
109+ const emittedNames = new Set < string > ( ) ;
95110 for ( const prop of node . properties ) {
96- pushComments ( lines , prop . leadingComments , " " , options ) ;
97- const union = typeUnion ( prop . attributes , options ) ;
98- const readonly = readonlyClass || hasAttribute ( prop . attributes , "Readonly" ) ||
99- hasAttribute ( prop . attributes , "ReadOnly" ) ;
100- const prefix = readonly ? "readonly " : "" ;
101- if ( union ) {
102- lines . push (
103- ` ${ prefix } ${ propertyName ( prop . name , options ) } : ${ union } ${ statementEnd ( options ) } ${
104- trailingComment ( prop . trailingComments , options )
105- } `,
106- ) ;
107- continue ;
111+ emitProperty ( lines , prop , readonlyClass , node , typeMap , options ) ;
112+ emittedNames . add ( propertyName ( prop . name , options ) ) ;
113+ }
114+ if ( options . inlineInheritedProperties ) {
115+ for ( const inherited of collectInheritedProperties ( node , typeMap ) ) {
116+ const name = propertyName ( inherited . property . name , options ) ;
117+ if ( emittedNames . has ( name ) ) continue ;
118+ const inheritedReadonlyClass = options . readonlyProperties || hasAttribute ( inherited . owner . attributes , "Readonly" ) ||
119+ hasAttribute ( inherited . owner . attributes , "ReadOnly" ) || hasAttribute ( inherited . owner . attributes , "ReadonlyProperties" ) ;
120+ emitProperty ( lines , inherited . property , inheritedReadonlyClass , node , typeMap , options ) ;
121+ emittedNames . add ( name ) ;
108122 }
123+ }
124+ lines . push ( "}" , "" ) ;
125+ return lines . join ( "\n" ) ;
126+ }
109127
110- const optional = hasAttribute ( prop . attributes , "Optional" ) || prop . type . nullable ;
111- const nullable = hasAttribute ( prop . attributes , "Nullable" ) ? " | null" : "" ;
112- let typeName = hasAttribute ( prop . attributes , "UnknownObject" )
113- ? "unknown"
114- : toTypeScriptType ( prop . type , node , typeMap , options ) ;
115- if ( hasAttribute ( prop . attributes , "Partial" ) ) typeName = `Partial<${ typeName } >` ;
128+ function emitProperty (
129+ lines : string [ ] ,
130+ prop : PropertyNode ,
131+ readonlyClass : boolean ,
132+ typeOwner : ClassNode ,
133+ typeMap : TypeMap ,
134+ options : Required < GenerateOptions > ,
135+ ) : void {
136+ pushComments ( lines , prop . leadingComments , " " , options ) ;
137+ const union = typeUnion ( prop . attributes , options ) ;
138+ const readonly = readonlyClass || hasAttribute ( prop . attributes , "Readonly" ) ||
139+ hasAttribute ( prop . attributes , "ReadOnly" ) ;
140+ const prefix = readonly ? "readonly " : "" ;
141+ if ( union ) {
116142 lines . push (
117- ` ${ prefix } ${ propertyName ( prop . name , options ) } ${ optional ? "?" : "" } : ${ typeName } ${ nullable } ${
118- statementEnd ( options )
119- } ${ trailingComment ( prop . trailingComments , options ) } `,
143+ ` ${ prefix } ${ propertyName ( prop . name , options ) } : ${ union } ${ statementEnd ( options ) } ${
144+ trailingComment ( prop . trailingComments , options )
145+ } `,
120146 ) ;
147+ return ;
121148 }
122- lines . push ( "}" , "" ) ;
123- return lines . join ( "\n" ) ;
149+
150+ const optional = hasAttribute ( prop . attributes , "Optional" ) || prop . type . nullable ;
151+ const nullable = hasAttribute ( prop . attributes , "Nullable" ) ? " | null" : "" ;
152+ let typeName = hasAttribute ( prop . attributes , "UnknownObject" )
153+ ? "unknown"
154+ : toTypeScriptType ( prop . type , typeOwner , typeMap , options ) ;
155+ if ( hasAttribute ( prop . attributes , "Partial" ) ) typeName = `Partial<${ typeName } >` ;
156+ lines . push (
157+ ` ${ prefix } ${ propertyName ( prop . name , options ) } ${ optional ? "?" : "" } : ${ typeName } ${ nullable } ${
158+ statementEnd ( options )
159+ } ${ trailingComment ( prop . trailingComments , options ) } `,
160+ ) ;
124161}
125162
126163function pushComments (
@@ -196,7 +233,7 @@ function documentationText(text: string): string {
196233 . trim ( ) ;
197234}
198235
199- function collectImports ( node : ClassNode , typeMap : TypeMap ) : string [ ] {
236+ function collectImports ( node : ClassNode , typeMap : TypeMap , options : Required < GenerateOptions > ) : string [ ] {
200237 const imports = new Set < string > ( ) ;
201238 const visit = ( type : TypeReferenceNode ) => {
202239 const base = simpleName ( type . name ) ;
@@ -207,9 +244,62 @@ function collectImports(node: ClassNode, typeMap: TypeMap): string[] {
207244 } ;
208245 for ( const baseType of node . baseTypes ?? ( node . baseType ? [ node . baseType ] : [ ] ) ) visit ( baseType ) ;
209246 for ( const prop of node . properties ) visit ( prop . type ) ;
247+ if ( options . inlineInheritedProperties ) {
248+ for ( const inherited of collectInheritedProperties ( node , typeMap ) ) visit ( inherited . property . type ) ;
249+ }
210250 return [ ...imports ] ;
211251}
212252
253+ function collectInheritedProperties ( node : ClassNode , typeMap : TypeMap ) : EmittableProperty [ ] {
254+ const properties : EmittableProperty [ ] = [ ] ;
255+ const seenTypes = new Set < string > ( ) ;
256+ const visitBase = ( baseType : TypeReferenceNode ) => {
257+ const baseName = simpleName ( baseType . name ) ;
258+ if ( seenTypes . has ( baseName ) ) return ;
259+ seenTypes . add ( baseName ) ;
260+
261+ const declaration = typeMap . declarations . get ( baseName ) ;
262+ if ( ! declaration || declaration . kind !== "class" ) return ;
263+
264+ const substitutions = new Map < string , TypeReferenceNode > ( ) ;
265+ for ( let i = 0 ; i < declaration . typeParameters . length ; i ++ ) {
266+ const replacement = baseType . args [ i ] ;
267+ if ( replacement ) substitutions . set ( declaration . typeParameters [ i ] , replacement ) ;
268+ }
269+
270+ for ( const property of declaration . properties ) {
271+ properties . push ( {
272+ owner : declaration ,
273+ property : {
274+ ...property ,
275+ type : substituteType ( property . type , substitutions ) ,
276+ } ,
277+ } ) ;
278+ }
279+ for ( const inheritedBaseType of declaration . baseTypes ?? ( declaration . baseType ? [ declaration . baseType ] : [ ] ) ) {
280+ visitBase ( substituteType ( inheritedBaseType , substitutions ) ) ;
281+ }
282+ } ;
283+
284+ for ( const baseType of node . baseTypes ?? ( node . baseType ? [ node . baseType ] : [ ] ) ) visitBase ( baseType ) ;
285+ return properties ;
286+ }
287+
288+ function substituteType ( type : TypeReferenceNode , substitutions : Map < string , TypeReferenceNode > ) : TypeReferenceNode {
289+ const replacement = substitutions . get ( simpleName ( type . name ) ) ;
290+ if ( replacement && type . args . length === 0 ) {
291+ return {
292+ ...replacement ,
293+ nullable : replacement . nullable || type . nullable ,
294+ arrayRank : replacement . arrayRank + type . arrayRank ,
295+ } ;
296+ }
297+ return {
298+ ...type ,
299+ args : type . args . map ( ( arg ) => substituteType ( arg , substitutions ) ) ,
300+ } ;
301+ }
302+
213303function toTypeScriptType (
214304 type : TypeReferenceNode ,
215305 owner : ClassNode ,
@@ -306,6 +396,7 @@ function normalizeOptions(options: GenerateOptions): Required<GenerateOptions> {
306396 normalizeAcronyms : options . normalizeAcronyms ?? false ,
307397 preserveComments : options . preserveComments ?? false ,
308398 convertDocumentationComments : options . convertDocumentationComments ?? false ,
399+ inlineInheritedProperties : options . inlineInheritedProperties ?? false ,
309400 } ;
310401}
311402
0 commit comments