in packages/jsii/lib/assembler.ts [1218:1579]
private async _visitClass(
type: ts.Type,
ctx: EmitContext,
): Promise<spec.ClassType | undefined> {
if (LOG.isTraceEnabled()) {
LOG.trace(
`Processing class: ${chalk.gray(ctx.namespace.join('.'))}.${chalk.cyan(
type.symbol.name,
)}`,
);
}
if (_hasInternalJsDocTag(type.symbol)) {
return undefined;
}
this._warnAboutReservedWords(type.symbol);
const fqn = `${[this.projectInfo.name, ...ctx.namespace].join('.')}.${
type.symbol.name
}`;
const jsiiType: spec.ClassType = bindings.setClassRelatedNode(
{
assembly: this.projectInfo.name,
fqn,
kind: spec.TypeKind.Class,
name: type.symbol.name,
namespace:
ctx.namespace.length > 0 ? ctx.namespace.join('.') : undefined,
docs: this._visitDocumentation(type.symbol, ctx).docs,
},
type.symbol.valueDeclaration as ts.ClassDeclaration,
);
if (_isAbstract(type.symbol, jsiiType)) {
jsiiType.abstract = true;
}
const erasedBases = new Array<ts.BaseType>();
for (let base of type.getBaseTypes() ?? []) {
if (jsiiType.base) {
// Ignoring this - there has already been a compilation error generated by tsc here.
continue;
}
//
// base classes ("extends foo")
// Crawl up the inheritance tree if the current base type is not exported, so we identify the type(s) to be
// erased, and identify the closest exported base class, should there be one.
while (base && this._isPrivateOrInternal(base.symbol)) {
LOG.debug(
`Base class of ${chalk.green(jsiiType.fqn)} named ${chalk.green(
base.symbol.name,
)} is not exported, erasing it...`,
);
erasedBases.push(base);
base = (base.getBaseTypes() ?? [])[0];
}
if (!base) {
// There is no exported base class to be found, pretend this class has no base class.
continue;
}
// eslint-disable-next-line no-await-in-loop
const ref = await this._typeReference(
base,
type.symbol.valueDeclaration,
'base class',
);
if (!spec.isNamedTypeReference(ref)) {
this._diagnostics.push(
JsiiDiagnostic.JSII_3006_TYPE_USED_AS_CLASS.create(
base.symbol.valueDeclaration ?? base.symbol.declarations[0],
ref,
),
);
continue;
}
this._deferUntilTypesAvailable(
fqn,
[ref],
base.symbol.valueDeclaration,
(deref) => {
if (!spec.isClassType(deref)) {
this._diagnostics.push(
JsiiDiagnostic.JSII_3006_TYPE_USED_AS_CLASS.create(
base.symbol.valueDeclaration ?? base.symbol.declarations[0],
ref,
),
);
}
},
);
jsiiType.base = ref.fqn;
}
//
// base interfaces ("implements foo")
// collect all "implements" declarations from the current type and all
// erased base types (because otherwise we lose them, see jsii#487)
const implementsClauses = new Array<ts.HeritageClause>();
for (const heritage of [type, ...erasedBases].map(
(t) =>
(t.symbol.valueDeclaration as ts.ClassDeclaration).heritageClauses ??
[],
)) {
for (const clause of heritage) {
if (clause.token === ts.SyntaxKind.ExtendsKeyword) {
// Handled by `getBaseTypes`
continue;
} else if (clause.token !== ts.SyntaxKind.ImplementsKeyword) {
this._diagnostics.push(
JsiiDiagnostic.JSII_9998_UNSUPPORTED_NODE.create(
clause,
`Ignoring ${ts.SyntaxKind[clause.token]} heritage clause`,
),
);
continue;
}
implementsClauses.push(clause);
}
}
// process all "implements" clauses
const allInterfaces = new Set<string>();
const baseInterfaces = implementsClauses.map((clause) =>
this._processBaseInterfaces(
fqn,
clause.types.map((t) => this._getTypeFromTypeNode(t)),
),
);
for (const { interfaces } of await Promise.all(baseInterfaces)) {
for (const ifc of interfaces ?? []) {
allInterfaces.add(ifc.fqn);
}
if (interfaces) {
this._deferUntilTypesAvailable(
jsiiType.fqn,
interfaces,
type.symbol.valueDeclaration,
(...ifaces) => {
for (const iface of ifaces) {
if (spec.isInterfaceType(iface) && iface.datatype) {
this._diagnostics.push(
JsiiDiagnostic.JSII_3007_ILLEGAL_STRUCT_EXTENSION.create(
type.symbol.valueDeclaration ?? type.symbol.declarations[0],
jsiiType,
iface,
),
);
}
}
},
);
}
}
if (allInterfaces.size > 0) {
jsiiType.interfaces = Array.from(allInterfaces);
}
if (!type.isClass()) {
throw new Error('Oh no');
}
const allDeclarations: Array<{
decl: ts.Declaration;
type: ts.InterfaceType | ts.BaseType;
}> = type.symbol.declarations.map((decl) => ({ decl, type }));
// Considering erased bases' declarations, too, so they are "blended in"
for (const base of erasedBases) {
allDeclarations.push(
...base.symbol.declarations.map((decl) => ({ decl, type: base })),
);
}
for (const { decl, type: declaringType } of allDeclarations) {
const classDecl = decl as ts.ClassDeclaration | ts.InterfaceDeclaration;
if (!classDecl.members) {
continue;
}
for (const memberDecl of classDecl.members) {
if (ts.isSemicolonClassElement(memberDecl)) {
this._diagnostics.push(
JsiiDiagnostic.JSII_9996_UNNECESSARY_TOKEN.create(memberDecl),
);
continue;
}
const member: ts.Symbol = ts.isConstructorDeclaration(memberDecl)
? (memberDecl as any).symbol
: this._typeChecker.getSymbolAtLocation(
ts.getNameOfDeclaration(memberDecl)!,
)!;
if (
!(declaringType.symbol.getDeclarations() ?? []).find(
(d) => d === memberDecl.parent,
)
) {
continue;
}
if (this._isPrivateOrInternal(member, memberDecl as ts.ClassElement)) {
continue;
}
// constructors are handled later
if (ts.isConstructorDeclaration(memberDecl)) {
continue;
}
// eslint-disable-next-line no-await-in-loop
if (
ts.isMethodDeclaration(memberDecl) ||
ts.isMethodSignature(memberDecl)
) {
// eslint-disable-next-line no-await-in-loop
await this._visitMethod(
member,
jsiiType,
ctx.replaceStability(jsiiType.docs?.stability),
classDecl,
);
} else if (
ts.isPropertyDeclaration(memberDecl) ||
ts.isPropertySignature(memberDecl) ||
ts.isAccessor(memberDecl)
) {
// eslint-disable-next-line no-await-in-loop
await this._visitProperty(
member,
jsiiType,
ctx.replaceStability(jsiiType.docs?.stability),
classDecl,
);
} else {
this._diagnostics.push(
JsiiDiagnostic.JSII_9998_UNSUPPORTED_NODE.create(
ts.getNameOfDeclaration(memberDecl) ?? memberDecl,
memberDecl.kind,
),
);
}
/* eslint-enable no-await-in-loop */
}
}
const memberEmitContext = ctx.replaceStability(
jsiiType.docs && jsiiType.docs.stability,
);
// Find the first defined constructor in this class, or it's erased bases
const constructor = [type, ...erasedBases]
.map(getConstructor)
.find((ctor) => ctor != null);
const ctorDeclaration =
constructor && (constructor.declarations[0] as ts.ConstructorDeclaration);
if (constructor && ctorDeclaration) {
const signature =
this._typeChecker.getSignatureFromDeclaration(ctorDeclaration);
if (
(ts.getCombinedModifierFlags(ctorDeclaration) &
ts.ModifierFlags.Private) ===
0
) {
jsiiType.initializer = {
locationInModule: this.declarationLocation(ctorDeclaration),
};
if (signature) {
for (const param of signature.getParameters()) {
jsiiType.initializer.parameters =
jsiiType.initializer.parameters ?? [];
jsiiType.initializer.parameters.push(
// eslint-disable-next-line no-await-in-loop
await this._toParameter(
param,
ctx.replaceStability(jsiiType.docs?.stability),
),
);
jsiiType.initializer.variadic =
jsiiType.initializer?.parameters?.some((p) => !!p.variadic) ||
undefined;
jsiiType.initializer.protected =
(ts.getCombinedModifierFlags(ctorDeclaration) &
ts.ModifierFlags.Protected) !==
0 || undefined;
}
}
this._verifyConsecutiveOptionals(
ctorDeclaration,
jsiiType.initializer.parameters,
);
jsiiType.initializer.docs = this._visitDocumentation(
constructor,
memberEmitContext,
).docs;
this.overrideDocComment(
constructor,
jsiiType.initializer.docs,
paramDocs(jsiiType.initializer.parameters),
);
}
// Process constructor-based property declarations even if constructor is private
if (signature) {
for (const param of signature.getParameters()) {
if (
ts.isParameterPropertyDeclaration(
param.valueDeclaration,
param.valueDeclaration.parent,
) &&
!this._isPrivateOrInternal(param)
) {
// eslint-disable-next-line no-await-in-loop
await this._visitProperty(
param,
jsiiType,
memberEmitContext,
ctorDeclaration.parent,
);
}
}
}
} else if (jsiiType.base) {
this._deferUntilTypesAvailable(
fqn,
[jsiiType.base],
type.symbol.valueDeclaration,
(baseType) => {
if (spec.isClassType(baseType)) {
jsiiType.initializer = baseType.initializer;
} else {
this._diagnostics.push(
JsiiDiagnostic.JSII_3999_INCOHERENT_TYPE_MODEL.create(
type.symbol.valueDeclaration ?? type.symbol.declarations[0],
`Base type of ${jsiiType.fqn} (${jsiiType.base}) is not a class`,
),
);
}
},
);
} else {
jsiiType.initializer = {
docs: ctx.stability && { stability: ctx.stability },
};
}
this._verifyNoStaticMixing(jsiiType, type.symbol.valueDeclaration);
this.overrideDocComment(type.getSymbol(), jsiiType?.docs);
return _sortMembers(jsiiType);
}