@@ -27,14 +27,19 @@ function isConstructorClassLike(node: ts.Node): node is ts.InterfaceDeclaration
2727 return ts . isInterfaceDeclaration ( node ) || ts . isClassDeclaration ( node ) || ts . isTypeLiteralNode ( node ) ;
2828}
2929
30+ // Parse a property (field) of a class. A getter function is generated for
31+ // every property. For non-`readonly` properties, we also generate a setter.
3032function parseProperty ( parser : Parser , declaration : ts . PropertySignature | ts . PropertyDeclaration , generics : Generics , parent : Class ) : void {
33+ // 1. Get type info and name of the property.
3134 const info = parser . getTypeNodeInfo ( declaration . type ! , generics ) ;
3235 const [ interfaceName , escapedName ] = getName ( declaration ) ;
3336
37+ // 2. If the property is optional, add that to the type info.
3438 if ( declaration . questionToken ) {
3539 info . setOptional ( ) ;
3640 }
3741
42+ // 3. Generate the getter function.
3843 const func = new Function ( `get_${ escapedName } ` , info . asReturnType ( parser ) ) ;
3944 func . setInterfaceName ( `get_${ interfaceName } ` ) ;
4045 func . setDeclaration ( declaration ) ;
@@ -43,6 +48,9 @@ function parseProperty(parser: Parser, declaration: ts.PropertySignature | ts.Pr
4348 const readonly = ( declaration . modifiers ?? [ ] )
4449 . some ( modifier => ts . isReadonlyKeywordOrPlusOrMinusToken ( modifier ) ) ;
4550
51+ // 4. If the property is not `readonly`, also generate setter functions.
52+ // The setter functions may be overloaded, depending on the type of the
53+ // property, and the logic in `asParameterTypes`.
4654 if ( ! readonly ) {
4755 for ( const parameter of info . asParameterTypes ( ) ) {
4856 const func = new Function ( `set_${ escapedName } ` , VOID_TYPE ) ;
@@ -54,34 +62,94 @@ function parseProperty(parser: Parser, declaration: ts.PropertySignature | ts.Pr
5462 }
5563}
5664
65+ // Parse a "constructor" object. A constructor object is a variable with the
66+ // same name as a class or interface declaration.
67+ //
68+ // This matches a common pattern in typescript declarations:
69+ // ```
70+ // declare interface Foo {
71+ // method(): void;
72+ // }
73+ //
74+ // declare interface FooConstructor {
75+ // staticMethod(): void;
76+ // new(): Foo;
77+ // }
78+ //
79+ // // The constructor object shares its name with the interface `Foo`:
80+ // declare var Foo: FooConstructor;
81+ // ```
82+ //
83+ // Instead of generating an actual variable, we add the members of the object
84+ // as members of the class that shares its name with the variable:
85+ // ```
86+ // class Foo : public Object {
87+ // public:
88+ // // From `method(): void;` in `interface Foo`.
89+ // void method();
90+ //
91+ // // From `staticMethod(): void;` in `interface FooConstructor`.
92+ // static void staticMethod();
93+ //
94+ // // From `new(): Foo` in `interface FooConstructor`.
95+ // Foo();
96+ // };
97+ // ```
5798function parseConstructor ( parser : Parser , node : Child , declaration : ts . VariableDeclaration | ts . PropertySignature | ts . PropertyDeclaration , generics : Generics , parent : Class ) : void {
99+ // 1. Get the type of the constructor object.
58100 const type = parser . getTypeFromTypeNode ( declaration . type ! ) ;
101+
102+ // 2. Get the symbol and type parameters of the constructor object type.
103+ //
104+ // If the type is generic, it has the form `T<U...>`. The symbol is `T`,
105+ // and the type parameters are `U...`.
106+ //
107+ // If the type is not generic, it has the form `T`. The symbol is `T`, and
108+ // the type parameters are an empty map.
59109 const [ symbol , types ] = parser . getSymbol ( type , generics ) ;
110+
111+ // 3. Update the generics map state with the type parameters of the
112+ // constructor object.
60113 generics = new Generics ( generics . getNextId ( ) , types ) ;
61114
115+ // 4. Gather all members of all declarations of the symbol.
62116 const members = ( symbol ?. declarations ?? [ ] )
63117 . filter ( declaration => parser . includesDeclaration ( declaration ) )
64118 . filter ( declaration => isConstructorClassLike ( declaration ) )
65119 . flatMap ( declaration => ( declaration as any ) . members ) ;
66120
121+ // 5. Parse and add the members to the class.
67122 for ( const member of members ) {
68123 if ( isMethodLike ( member ) ) {
124+ // Methods are parsed using `parseFunction`.
69125 parseFunction ( parser , member , generics , true , parent ) ;
70126 } else if ( isPropertyLike ( member ) ) {
127+ // Property members may be regular properties, but they may also
128+ // be the constructor object for an inner class.
71129 const flags = ts . getCombinedModifierFlags ( member ) ;
72130 const [ interfaceName , escapedName ] = getName ( member ) ;
73131 const child = node . getChild ( escapedName ) ;
74132
75- if ( ! ( flags & ts . ModifierFlags . Static ) ) {
76- if ( child && child . basicClass ) {
77- parseConstructor ( parser , child , member , generics , child . basicClass ) ;
133+ // Ignore static properties.
134+ if ( flags & ts . ModifierFlags . Static ) {
135+ continue ;
136+ }
78137
79- if ( child . genericClass ) {
80- parseConstructor ( parser , child , member , generics , child . genericClass ) ;
81- }
82- } else {
83- parseVariable ( parser , member , generics , parent ) ;
138+ if ( child && child . basicClass ) {
139+ // If there is an inner class with the same name as this
140+ // property, parse the property as a constructor object for the
141+ // inner class.
142+ parseConstructor ( parser , child , member , generics , child . basicClass ) ;
143+
144+ // Also parse the constructor object for the generic version of
145+ // this class.
146+ if ( child . genericClass ) {
147+ parseConstructor ( parser , child , member , generics , child . genericClass ) ;
84148 }
149+ } else {
150+ // If there is no inner class with the same name, parse it as a
151+ // regular property.
152+ parseVariable ( parser , member , generics , parent ) ;
85153 }
86154 }
87155 }
@@ -92,26 +160,38 @@ export function parseClass(parser: Parser, node: Child, object: Class, generics:
92160 generics = generics . clone ( ) ;
93161
94162 if ( object . isGenericVersion ( ) ) {
163+ // 1.1. If this is the generic version of this class, use
164+ // `createParameters` to parse the type parameters.
95165 const [ parameters , constraints ] = generics . createParameters ( parser , declarations ) ;
166+
167+ // 1.2. Add the type parameters and constraints to the class.
96168 parameters . forEach ( parameter => object . addTypeParameter ( parameter . getName ( ) ) ) ;
97169 constraints . forEach ( constraint => object . addConstraint ( constraint ) ) ;
98170 } else {
171+ // 2.1. If this is the basic version of this class, use
172+ // `createConstraints` to parse the type parameters.
99173 generics . createConstraints ( parser , declarations ) ;
100174 }
101-
175+
102176 const includedDeclarations = declarations
103177 . filter ( declaration => parser . includesDeclaration ( declaration ) ) ;
104178
179+ // 3. Gather all types in heritage clauses on any of the included
180+ // declarations of this class.
105181 const heritageTypes = includedDeclarations
106182 . flatMap ( declaration => declaration . heritageClauses ?? [ ] )
107183 . flatMap ( heritageClause => heritageClause . types ) ;
108184
185+ // 4. And add them as bases of the class.
109186 for ( const heritageType of heritageTypes ) {
110187 const type = parser . getTypeAtLocation ( heritageType ) ;
111188 const info = parser . getTypeInfo ( type , generics ) ;
112189 object . addBase ( info . asBaseType ( ) , Visibility . Public ) ;
113190 }
114191
192+ // 5. If there are no explicit bases, we implicitly add `Object` as a base
193+ // class. Or if this *is* the `Object` class, we add `_Any` as a base
194+ // class.
115195 if ( object . getBases ( ) . length === 0 ) {
116196 if ( object !== parser . getRootClass ( "Object" ) ) {
117197 object . addBase ( parser . getRootType ( "Object" ) , Visibility . Public ) ;
@@ -120,27 +200,67 @@ export function parseClass(parser: Parser, node: Child, object: Class, generics:
120200 }
121201 }
122202
203+ // 6. Gather all members of the included declarations of this class.
123204 const members = includedDeclarations
124205 . flatMap < ts . TypeElement | ts . ClassElement > ( declaration => declaration . members ) ;
125206
207+ // 7. Parse and add members to the class.
126208 for ( const member of members ) {
127209 if ( isFunctionLike ( member ) ) {
210+ // Methods are parsed using `parseFunction`.
128211 parseFunction ( parser , member , generics , false , object ) ;
129212 } else if ( isPropertyLike ( member ) ) {
130213 const flags = ts . getCombinedModifierFlags ( member ) ;
131214
132215 if ( flags & ts . ModifierFlags . Static ) {
216+ // Static properties are turned into static member variables,
217+ // and are parsed using `parseVariable`.
133218 parseVariable ( parser , member , generics , object ) ;
134219 } else {
220+ // Non-static properties are turend into getter and setter
221+ // functions, and are parsed using `parseProperty`.
135222 parseProperty ( parser , member , generics , object ) ;
136223 }
137224 }
138225 }
139226
227+ // 8. Parse the children of a namespace with the same name as this class.
228+ //
229+ // This matches a common pattern in typescript declarations:
230+ // ```
231+ // declare interface Foo {
232+ // method(): void;
233+ // }
234+ //
235+ // // The namespace shares its name with the interface `Foo`.
236+ // declare namespace Foo {
237+ // function staticMethod(): void;
238+ // }
239+ // ```
240+ //
241+ // Instead of generating an actual namespace, we add the children of the
242+ // namespace as members of the class that shares its name with the
243+ // namespace:
244+ // ```
245+ // class Foo {
246+ // public:
247+ // // From `method(): void;` in `interface Foo`.
248+ // void method();
249+ //
250+ // // From `function staticMethod(): void;` in `namespace Foo`.
251+ // static void staticMethod();
252+ // };
253+ // ```
140254 for ( const child of node . getChildren ( ) ) {
141255 const functionDeclarations = child . getFunctionDeclarations ( ) ;
142256
143257 if ( child . basicClass ) {
258+ // If the child is another class, it is parsed using `parseClass`
259+ // as an inner class of this class.
260+ //
261+ // Inner classes are not generated for the generic version of the
262+ // parent class. There is no `TParent<T>::InnerClass`, only
263+ // `Parent::InnerClass`.
144264 if ( ! object . isGenericVersion ( ) ) {
145265 parseClass ( parser , child , child . basicClass , generics , object ) ;
146266
@@ -149,25 +269,50 @@ export function parseClass(parser: Parser, node: Child, object: Class, generics:
149269 }
150270 }
151271 } else if ( functionDeclarations . length > 0 ) {
272+ // If the child is a function declaration, it is parsed using
273+ // `parseFunction` as a static method of this class.
152274 for ( const declaration of functionDeclarations ) {
153275 parseFunction ( parser , declaration , generics , true , object ) ;
154276 }
155277 } else if ( child . variableDeclaration ) {
278+ // If the child is a variable declaration, it is parsed using
279+ // `parseVariable` as a static member variable of this class.
156280 parseVariable ( parser , child . variableDeclaration , generics , object ) ;
157281 } else if ( child . typeAliasDeclaration && child . basicTypeAlias ) {
282+ // If the child is a type alias, it is parsed using
283+ // `parseTypeAlias` as a member type of this class.
284+ //
285+ // Member types are not generated for the generic version of the
286+ // parent class. There is no `TParent<T>::MemberType`, only
287+ // `Parent::MemberType`.
158288 if ( ! object . isGenericVersion ( ) ) {
159289 parseTypeAlias ( parser , child . typeAliasDeclaration , child . basicTypeAlias , generics , object ) ;
160290
161291 if ( child . genericTypeAlias ) {
162292 parseTypeAlias ( parser , child . typeAliasDeclaration , child . genericTypeAlias , generics , object ) ;
163293 }
164294 }
165- } else if ( ! object . isGenericVersion ( ) ) {
295+ }
296+
297+ // TODO: generate an inner class, but its members should be static.
298+ /*
299+ else if (!object.isGenericVersion()) {
166300 child.basicClass = new Class(child.getName());
167301 parseClass(parser, child, child.basicClass, generics, object);
168302 }
303+ */
169304 }
170305
306+ // 9. If there is a variable declaration with the same name as this class,
307+ // it is parsed as a constructor object of this class. See the comments on
308+ // `parseConstructor` for a detailed description of constructor objects.
309+ //
310+ // There is one exception. Usually, constructor objects have the form
311+ // `declare var Foo: FooConstructor;`. But if the type of the constructor
312+ // object is also `Foo`, as in `declare var Foo: Foo;`. Then the variable
313+ // is not a constructor object. In this case, the class is renamed to
314+ // `FooClass` and the variable is parsed as a regular variable using
315+ // `parseVariable`.
171316 if ( node . variableDeclaration ) {
172317 const variableType = parser . getTypeFromTypeNode ( node . variableDeclaration . type ! ) ;
173318 const classType = declarations [ 0 ] && parser . getTypeAtLocation ( declarations [ 0 ] ) ;
@@ -180,6 +325,11 @@ export function parseClass(parser: Parser, node: Child, object: Class, generics:
180325 }
181326 }
182327
328+ // 10. Mark this class as coming from `node.moduleDeclaration`, or one of
329+ // the declarations in `declarations`. It does not particularly matter
330+ // which, they are probably from the same file anyways.
183331 object . setDeclaration ( node . moduleDeclaration ?? declarations [ 0 ] ) ;
332+
333+ // 11. Add it to the parent declaration.
184334 parser . addDeclaration ( object , parent ) ;
185335}
0 commit comments