in packages/jsii/lib/assembler.ts [899:1099]
private async _visitNode(
node: ts.Declaration,
context: EmitContext,
): Promise<spec.Type[]> {
if (ts.isNamespaceExport(node)) {
// export * as ns from 'module';
// Note: the "ts.NamespaceExport" refers to the "export * as ns" part of
// the statement only. We must refer to `node.parent` in order to be able
// to access the module specifier ("from 'module'") part.
const symbol = this._typeChecker.getSymbolAtLocation(
node.parent.moduleSpecifier!,
)!;
if (LOG.isTraceEnabled()) {
LOG.trace(
`Entering submodule: ${chalk.cyan(
[...context.namespace, symbol.name].join('.'),
)}`,
);
}
const nsContext = context.appendNamespace(node.name.text);
const promises = new Array<Promise<spec.Type[]>>();
for (const child of this._typeChecker.getExportsOfModule(symbol)) {
promises.push(this._visitNode(child.declarations[0], nsContext));
}
const allTypes = flattenPromises(promises);
if (LOG.isTraceEnabled()) {
LOG.trace(
`Leaving submodule: ${chalk.cyan(
[...context.namespace, symbol.name].join('.'),
)}`,
);
}
return allTypes;
}
if (ts.isExportSpecifier(node)) {
// This is what happens when one does `export { Symbol } from "./location";`
// ExportSpecifier: ~~~~~~
const resolvedSymbol =
this._typeChecker.getExportSpecifierLocalTargetSymbol(node);
if (!resolvedSymbol) {
// A grammar error, compilation will already have failed
return [];
}
return this._visitNode(
resolvedSymbol.valueDeclaration ?? resolvedSymbol.declarations[0],
context,
);
}
if ((ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export) === 0) {
return [];
}
let jsiiType: spec.Type | undefined;
if (ts.isClassDeclaration(node) && _isExported(node)) {
// export class Name { ... }
this._validateHeritageClauses(node.heritageClauses);
jsiiType = await this._visitClass(
this._typeChecker.getTypeAtLocation(node),
context,
);
if (jsiiType) {
this.registerExportedClassFqn(node, jsiiType.fqn);
}
} else if (ts.isInterfaceDeclaration(node) && _isExported(node)) {
// export interface Name { ... }
this._validateHeritageClauses(node.heritageClauses);
jsiiType = await this._visitInterface(
this._typeChecker.getTypeAtLocation(node),
context,
);
} else if (ts.isEnumDeclaration(node) && _isExported(node)) {
// export enum Name { ... }
jsiiType = await this._visitEnum(
this._typeChecker.getTypeAtLocation(node),
context,
);
} else if (ts.isModuleDeclaration(node)) {
// export namespace name { ... }
const name = node.name.getText();
const symbol = this._typeChecker.getSymbolAtLocation(node.name)!;
if (LOG.isTraceEnabled()) {
LOG.trace(
`Entering namespace: ${chalk.cyan(
[...context.namespace, name].join('.'),
)}`,
);
}
const allTypesPromises = new Array<Promise<spec.Type[]>>();
for (const prop of this._typeChecker.getExportsOfModule(symbol)) {
allTypesPromises.push(
this._visitNode(
prop.declarations[0],
context.appendNamespace(node.name.getText()),
),
);
}
const allTypes = await flattenPromises(allTypesPromises);
if (LOG.isTraceEnabled()) {
LOG.trace(
`Leaving namespace: ${chalk.cyan(
[...context.namespace, name].join('.'),
)}`,
);
}
return allTypes;
} else {
this._diagnostics.push(
JsiiDiagnostic.JSII_9998_UNSUPPORTED_NODE.create(
ts.getNameOfDeclaration(node) ?? node,
node.kind,
),
);
}
if (!jsiiType) {
return [];
}
// If symbolId hasn't been set yet, set it here
if (!jsiiType.symbolId) {
jsiiType.symbolId = this.getSymbolId(node);
}
// Let's quickly verify the declaration does not collide with a submodule. Submodules get case-adjusted for each
// target language separately, so names cannot collide with case-variations.
for (const submodule of this._submodules.keys()) {
const candidates = Array.from(
new Set([
submodule.name,
Case.camel(submodule.name),
Case.pascal(submodule.name),
Case.snake(submodule.name),
]),
);
const colliding = candidates.find(
(name) => `${this.projectInfo.name}.${name}` === jsiiType!.fqn,
);
if (colliding != null) {
const submoduleDeclName = _nameOrDeclarationNode(submodule);
this._diagnostics.push(
JsiiDiagnostic.JSII_5011_SUBMODULE_NAME_CONFLICT.create(
ts.getNameOfDeclaration(node) ?? node,
submodule.name,
jsiiType.name,
candidates,
).addRelatedInformation(
submoduleDeclName,
`This is the conflicting submodule declaration`,
),
);
}
}
if (LOG.isInfoEnabled()) {
LOG.info(
`Registering JSII ${chalk.magenta(jsiiType.kind)}: ${chalk.green(
jsiiType.fqn,
)}`,
);
}
this._types[jsiiType.fqn] = jsiiType;
jsiiType.locationInModule = this.declarationLocation(node);
const type = this._typeChecker.getTypeAtLocation(node);
if (type.symbol.exports) {
const nestedContext = context.appendNamespace(type.symbol.name);
const visitedNodes = this._typeChecker
.getExportsOfModule(type.symbol)
.filter((s) => s.declarations)
.map((exportedNode) =>
this._visitNode(exportedNode.declarations[0], nestedContext),
);
for (const nestedTypes of await Promise.all(visitedNodes)) {
for (const nestedType of nestedTypes) {
if (nestedType.namespace !== nestedContext.namespace.join('.')) {
this._diagnostics.push(
JsiiDiagnostic.JSII_5012_NAMESPACE_IN_TYPE.create(
ts.getNameOfDeclaration(node) ?? node,
jsiiType.fqn,
nestedType.namespace!,
),
);
}
}
}
}
return [jsiiType];
}