in packages/jsii/lib/assembler.ts [1871:2107]
private async _visitInterface(
type: ts.Type,
ctx: EmitContext,
): Promise<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,
);
const { interfaces, erasedBases } = await this._processBaseInterfaces(
fqn,
type.getBaseTypes(),
);
jsiiType.interfaces = apply(interfaces, (arr) => arr.map((i) => i.fqn));
for (const declaringType of [type, ...erasedBases]) {
for (const member of declaringType.getProperties()) {
if (
!(declaringType.symbol.getDeclarations() ?? []).find(
(decl) => decl === member.valueDeclaration?.parent,
)
) {
continue;
}
if (
this._isPrivateOrInternal(
member,
member.valueDeclaration as ts.PropertyDeclaration,
)
) {
continue;
}
if (
ts.isMethodDeclaration(member.valueDeclaration) ||
ts.isMethodSignature(member.valueDeclaration)
) {
// eslint-disable-next-line no-await-in-loop
await this._visitMethod(
member,
jsiiType,
ctx.replaceStability(jsiiType.docs?.stability),
(type.symbol.valueDeclaration ??
type.symbol.declarations[0]) as ts.InterfaceDeclaration,
);
} else if (
ts.isPropertyDeclaration(member.valueDeclaration) ||
ts.isPropertySignature(member.valueDeclaration) ||
ts.isAccessor(member.valueDeclaration)
) {
// eslint-disable-next-line no-await-in-loop
await this._visitProperty(
member,
jsiiType,
ctx.replaceStability(jsiiType.docs?.stability),
(type.symbol.valueDeclaration ??
type.symbol.declarations[0]) as ts.InterfaceDeclaration,
);
} else {
this._diagnostics.push(
JsiiDiagnostic.JSII_9998_UNSUPPORTED_NODE.create(
_nameOrDeclarationNode(member),
(member.valueDeclaration ?? member.declarations[0]).kind,
),
);
}
}
}
// 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(
_findHint(declaration, 'struct')!,
'struct',
'interfaces with only readonly properties',
)
.addRelatedInformation(
ts.getNameOfDeclaration(declaration) ?? declaration,
'The annotated declartion is here',
)
.preformat(this.projectInfo.projectRoot),
),
);
}
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,
),
);
}
// 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,
);
this.overrideDocComment(type.getSymbol(), jsiiType?.docs);
return _sortMembers(jsiiType);
}