in src/assembler.ts [1652:1853]
private _visitInterface(type: ts.Type, ctx: EmitContext): spec.InterfaceType | undefined {
if (LOG.isTraceEnabled()) {
LOG.trace(`Processing interface: ${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 { docs, hints } = this._visitDocumentation(type.symbol, ctx);
const jsiiType: spec.InterfaceType = bindings.setInterfaceRelatedNode(
{
assembly: this.projectInfo.name,
fqn,
kind: spec.TypeKind.Interface,
name: type.symbol.name,
namespace: ctx.namespace.length > 0 ? ctx.namespace.join('.') : undefined,
docs,
},
type.symbol.declarations?.[0] as ts.InterfaceDeclaration | undefined,
);
const { interfaces, erasedBases } = this._processBaseInterfaces(fqn, type.getBaseTypes());
jsiiType.interfaces = apply(interfaces, (arr) => arr.map((i) => i.fqn));
const typeDecl = (type.symbol.valueDeclaration ?? type.symbol.declarations?.[0]) as
| ts.ClassLikeDeclaration
| ts.InterfaceDeclaration
| undefined;
for (const typeParam of typeDecl?.typeParameters ?? []) {
this._diagnostics.push(JsiiDiagnostic.JSII_1006_GENERIC_TYPE.create(typeParam));
}
for (const decl of (typeDecl?.members as ReadonlyArray<ts.ClassElement | ts.TypeElement> | undefined)?.filter(
(mem) => ts.isIndexSignatureDeclaration(mem),
) ?? []) {
const sym =
type.symbol.members?.get(ts.InternalSymbolName.Index) ?? type.symbol.exports?.get(ts.InternalSymbolName.Index);
if (sym != null && this._isPrivateOrInternal(sym, decl)) {
continue;
}
// Index signatures (static or not) are not supported in the jsii type model.
this._diagnostics.push(
JsiiDiagnostic.JSII_1999_UNSUPPORTED.create(decl, { what: 'Index signatures', suggestInternal: true }),
);
}
for (const declaringType of [type, ...erasedBases]) {
for (const member of declaringType.getProperties()) {
const decl = member.valueDeclaration ?? (member.declarations?.[0] as ts.PropertyDeclaration | undefined);
if (!(declaringType.symbol.getDeclarations() ?? []).find((d) => d === decl?.parent)) {
continue;
}
if (this._isPrivateOrInternal(member, decl)) {
continue;
}
if (decl && (ts.isMethodDeclaration(decl) || ts.isMethodSignature(decl))) {
// eslint-disable-next-line no-await-in-loop
this._visitMethod(member, jsiiType, ctx.replaceStability(jsiiType.docs?.stability), typeDecl);
} else if (decl && (ts.isPropertyDeclaration(decl) || ts.isPropertySignature(decl) || ts.isAccessor(decl))) {
// eslint-disable-next-line no-await-in-loop
this._visitProperty(member, jsiiType, ctx.replaceStability(jsiiType.docs?.stability), typeDecl);
} else {
this._diagnostics.push(
JsiiDiagnostic.JSII_9998_UNSUPPORTED_NODE.create(
_nameOrDeclarationNode(member),
(member.valueDeclaration ?? member.declarations?.[0])?.kind ?? ts.SyntaxKind.Unknown,
),
);
}
}
}
// Calculate datatype based on the datatypeness of this interface and all of its parents
// To keep the spec minimal the actual values of the attribute are "true" or "undefined" (to represent "false").
const declaration = type.symbol.valueDeclaration ?? type.symbol.declarations?.[0];
this._deferUntilTypesAvailable(fqn, jsiiType.interfaces ?? [], declaration, (...bases: spec.Type[]) => {
if ((jsiiType.methods ?? []).length === 0) {
jsiiType.datatype = true;
} else if (hints.struct) {
this._diagnostics.push(
jsiiType.methods!.reduce((diag, mthod) => {
const node = bindings.getMethodRelatedNode(mthod);
return node
? diag.addRelatedInformation(ts.getNameOfDeclaration(node) ?? node, 'A method is declared here')
: diag;
}, JsiiDiagnostic.JSII_7001_ILLEGAL_HINT.create(declaration && _findHint(declaration, 'struct')!, 'struct', 'interfaces with only readonly properties').addRelatedInformationIf(ts.getNameOfDeclaration(declaration) ?? declaration, 'The annotated declartion is here')),
);
}
for (const base of bases) {
if (spec.isInterfaceType(base) && !base.datatype) {
jsiiType.datatype = undefined;
}
}
const interfaceName = isInterfaceName(jsiiType.name);
// If it's not a datatype the name must start with an "I".
if (!jsiiType.datatype && !interfaceName) {
this._diagnostics.push(
JsiiDiagnostic.JSII_8007_BEHAVIORAL_INTERFACE_NAME.create(
ts.getNameOfDeclaration(declaration) ?? declaration,
jsiiType.name,
),
);
}
// NOTE: We need to be careful with the `I` prefix for behavioral interfaces, as this can mess with PascalCase
// transformations, especially with short names such as `IA`, ...
const expectedName = interfaceName ? `I${Case.pascal(type.symbol.name.slice(1))}` : Case.pascal(type.symbol.name);
if (expectedName !== type.symbol.name) {
this._diagnostics.push(
JsiiDiagnostic.JSII_8000_PASCAL_CASED_TYPE_NAMES.create(
(type.symbol.declarations?.[0] as ts.InterfaceDeclaration | undefined)?.name,
type.symbol.name,
expectedName,
),
);
}
// If the name starts with an "I" it is not intended as a datatype, so switch that off,
// unless a TSDoc hint was set to force this to be considered a behavioral interface.
if (jsiiType.datatype && interfaceName && !hints.struct) {
delete jsiiType.datatype;
}
// Okay, this is a data type, check that all properties are readonly
if (jsiiType.datatype) {
for (const prop of jsiiType.properties ?? []) {
if (!prop.immutable) {
const p = type.getProperty(prop.name)!;
this._diagnostics.push(
JsiiDiagnostic.JSII_3008_STRUCT_PROPS_MUST_BE_READONLY.create(
_nameOrDeclarationNode(p),
p.name,
jsiiType,
),
);
// force property to be "readonly" since jsii languages will pass this by-value
prop.immutable = true;
}
}
} else {
// This is *NOT* a data type, so it may not extend something that is one.
for (const base of bases) {
if (!spec.isInterfaceType(base)) {
// Invalid type we already warned about earlier, just ignoring it here..
continue;
}
if (base.datatype) {
this._diagnostics.push(
JsiiDiagnostic.JSII_3007_ILLEGAL_STRUCT_EXTENSION.create(
type.symbol.valueDeclaration ?? type.symbol.declarations?.[0],
jsiiType,
base,
),
);
}
}
}
});
// Check that no interface declares a member that's already declared
// in a base type (not allowed in C#).
const names = memberNames(jsiiType);
const checkNoIntersection = (...bases: spec.Type[]) => {
for (const base of bases) {
if (!spec.isInterfaceType(base)) {
continue;
}
const baseMembers = memberNames(base);
for (const memberName of names) {
if (baseMembers.includes(memberName)) {
this._diagnostics.push(
JsiiDiagnostic.JSII_5015_REDECLARED_INTERFACE_MEMBER.create(
type.symbol.valueDeclaration ?? type.symbol.declarations?.[0],
memberName,
jsiiType,
),
);
}
}
// Recurse upwards
this._deferUntilTypesAvailable(fqn, base.interfaces ?? [], type.symbol.valueDeclaration, checkNoIntersection);
}
};
this._deferUntilTypesAvailable(fqn, jsiiType.interfaces ?? [], type.symbol.valueDeclaration, checkNoIntersection);
return _sortMembers(jsiiType);
}