in src/assembler.ts [779:933]
private _visitNode(node: ts.Declaration, context: EmitContext): 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 allTypes = this._typeChecker.getExportsOfModule(symbol).flatMap((child) => {
const decl = child.declarations?.[0];
if (decl == null) {
return [];
}
return this._visitNode(decl, nsContext);
});
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);
const decl = resolvedSymbol?.valueDeclaration ?? resolvedSymbol?.declarations?.[0];
if (!decl) {
// A grammar error, compilation will already have failed
return [];
}
return this._visitNode(decl, 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 = 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 = this._visitInterface(this._typeChecker.getTypeAtLocation(node), context);
} else if (ts.isEnumDeclaration(node) && _isExported(node)) {
// export enum Name { ... }
jsiiType = 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 nsContext = context.appendNamespace(node.name.getText());
const allTypes = this._typeChecker.getExportsOfModule(symbol).flatMap((prop) => {
const decl = prop.declarations?.[0];
if (decl == null) {
return [];
}
return this._visitNode(decl, nsContext);
});
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,
).addRelatedInformationIf(submoduleDeclName, 'This is the conflicting submodule declaration'),
);
}
}
if (LOG.isInfoEnabled()) {
LOG.info(`Registering JSII ${chalk.magenta(jsiiType.kind)}: ${chalk.green(jsiiType.fqn)}`);
}
this._types.set(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)
.flatMap((exportedNode) => {
const decl = exportedNode.valueDeclaration ?? exportedNode.declarations?.[0];
if (decl == null) {
return [];
}
return [this._visitNode(decl, nestedContext)];
});
for (const nestedTypes of 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];
}