private _visitNode()

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];
  }