in src/assembler.ts [1038:1344]
private _visitClass(type: ts.Type, ctx: EmitContext): 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}`;
if (Case.pascal(type.symbol.name) !== type.symbol.name) {
this._diagnostics.push(
JsiiDiagnostic.JSII_8000_PASCAL_CASED_TYPE_NAMES.create(
(type.symbol.valueDeclaration as ts.ClassDeclaration).name ??
type.symbol.valueDeclaration ??
type.symbol.declarations?.[0],
type.symbol.name,
),
);
}
const classDeclaration = type.symbol.valueDeclaration as ts.ClassDeclaration;
for (const typeParam of classDeclaration.typeParameters ?? []) {
this._diagnostics.push(JsiiDiagnostic.JSII_1006_GENERIC_TYPE.create(typeParam));
}
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,
},
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 || isInternalSymbol(base.symbol)) {
// 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 = this._typeReference(
base,
type.symbol.valueDeclaration ?? type.symbol.declarations?.[0],
'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 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.isConstructorDeclaration(memberDecl)
? getConstructor(this._typeChecker.getTypeAtLocation(memberDecl.parent))
: ts.isIndexSignatureDeclaration(memberDecl)
? type.symbol.members?.get(ts.InternalSymbolName.Index) ??
type.symbol.exports?.get(ts.InternalSymbolName.Index)
: this._typeChecker.getSymbolAtLocation(ts.getNameOfDeclaration(memberDecl) ?? memberDecl);
if (member && this._isPrivateOrInternal(member, memberDecl as ts.ClassElement)) {
continue;
}
if (ts.isIndexSignatureDeclaration(memberDecl)) {
// Index signatures (static or not) are not supported in the jsii type model.
this._diagnostics.push(
JsiiDiagnostic.JSII_1999_UNSUPPORTED.create(memberDecl, {
what: 'Index signatures',
suggestInternal: true,
}),
);
continue;
}
if (!(declaringType.symbol.getDeclarations() ?? []).find((d) => d === memberDecl.parent)) {
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
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
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 | undefined);
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
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;
}
// Process constructor-based property declarations even if constructor is private
if (signature) {
for (const param of signature.getParameters()) {
const decl = param.valueDeclaration ?? param.declarations?.[0];
if (decl && ts.isParameterPropertyDeclaration(decl, decl.parent) && !this._isPrivateOrInternal(param)) {
// eslint-disable-next-line no-await-in-loop
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 ?? type.symbol.declarations?.[0]);
return _sortMembers(jsiiType);
}