Skip to content

Commit fe2457e

Browse files
committed
Cleanup and comments (part 14)
1 parent 80d2fc7 commit fe2457e

5 files changed

Lines changed: 289 additions & 27 deletions

File tree

src/parser/class.ts

Lines changed: 160 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
3032
function 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+
// ```
5798
function 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

Comments
 (0)