private async _visitClass()

in packages/jsii/lib/assembler.ts [1218:1579]


  private async _visitClass(
    type: ts.Type,
    ctx: EmitContext,
  ): Promise<spec.ClassType | undefined> {
    if (LOG.isTraceEnabled()) {
      LOG.trace(
        `Processing class: ${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 jsiiType: spec.ClassType = bindings.setClassRelatedNode(
      {
        assembly: this.projectInfo.name,
        fqn,
        kind: spec.TypeKind.Class,
        name: type.symbol.name,
        namespace:
          ctx.namespace.length > 0 ? ctx.namespace.join('.') : undefined,
        docs: this._visitDocumentation(type.symbol, ctx).docs,
      },
      type.symbol.valueDeclaration as ts.ClassDeclaration,
    );

    if (_isAbstract(type.symbol, jsiiType)) {
      jsiiType.abstract = true;
    }

    const erasedBases = new Array<ts.BaseType>();
    for (let base of type.getBaseTypes() ?? []) {
      if (jsiiType.base) {
        // Ignoring this - there has already been a compilation error generated by tsc here.
        continue;
      }

      //
      // base classes ("extends foo")

      // Crawl up the inheritance tree if the current base type is not exported, so we identify the type(s) to be
      // erased, and identify the closest exported base class, should there be one.
      while (base && this._isPrivateOrInternal(base.symbol)) {
        LOG.debug(
          `Base class of ${chalk.green(jsiiType.fqn)} named ${chalk.green(
            base.symbol.name,
          )} is not exported, erasing it...`,
        );
        erasedBases.push(base);
        base = (base.getBaseTypes() ?? [])[0];
      }
      if (!base) {
        // There is no exported base class to be found, pretend this class has no base class.
        continue;
      }

      // eslint-disable-next-line no-await-in-loop
      const ref = await this._typeReference(
        base,
        type.symbol.valueDeclaration,
        'base class',
      );

      if (!spec.isNamedTypeReference(ref)) {
        this._diagnostics.push(
          JsiiDiagnostic.JSII_3006_TYPE_USED_AS_CLASS.create(
            base.symbol.valueDeclaration ?? base.symbol.declarations[0],
            ref,
          ),
        );
        continue;
      }
      this._deferUntilTypesAvailable(
        fqn,
        [ref],
        base.symbol.valueDeclaration,
        (deref) => {
          if (!spec.isClassType(deref)) {
            this._diagnostics.push(
              JsiiDiagnostic.JSII_3006_TYPE_USED_AS_CLASS.create(
                base.symbol.valueDeclaration ?? base.symbol.declarations[0],
                ref,
              ),
            );
          }
        },
      );
      jsiiType.base = ref.fqn;
    }

    //
    // base interfaces ("implements foo")

    // collect all "implements" declarations from the current type and all
    // erased base types (because otherwise we lose them, see jsii#487)
    const implementsClauses = new Array<ts.HeritageClause>();
    for (const heritage of [type, ...erasedBases].map(
      (t) =>
        (t.symbol.valueDeclaration as ts.ClassDeclaration).heritageClauses ??
        [],
    )) {
      for (const clause of heritage) {
        if (clause.token === ts.SyntaxKind.ExtendsKeyword) {
          // Handled by `getBaseTypes`
          continue;
        } else if (clause.token !== ts.SyntaxKind.ImplementsKeyword) {
          this._diagnostics.push(
            JsiiDiagnostic.JSII_9998_UNSUPPORTED_NODE.create(
              clause,
              `Ignoring ${ts.SyntaxKind[clause.token]} heritage clause`,
            ),
          );
          continue;
        }

        implementsClauses.push(clause);
      }
    }

    // process all "implements" clauses
    const allInterfaces = new Set<string>();
    const baseInterfaces = implementsClauses.map((clause) =>
      this._processBaseInterfaces(
        fqn,
        clause.types.map((t) => this._getTypeFromTypeNode(t)),
      ),
    );
    for (const { interfaces } of await Promise.all(baseInterfaces)) {
      for (const ifc of interfaces ?? []) {
        allInterfaces.add(ifc.fqn);
      }
      if (interfaces) {
        this._deferUntilTypesAvailable(
          jsiiType.fqn,
          interfaces,
          type.symbol.valueDeclaration,
          (...ifaces) => {
            for (const iface of ifaces) {
              if (spec.isInterfaceType(iface) && iface.datatype) {
                this._diagnostics.push(
                  JsiiDiagnostic.JSII_3007_ILLEGAL_STRUCT_EXTENSION.create(
                    type.symbol.valueDeclaration ?? type.symbol.declarations[0],
                    jsiiType,
                    iface,
                  ),
                );
              }
            }
          },
        );
      }
    }

    if (allInterfaces.size > 0) {
      jsiiType.interfaces = Array.from(allInterfaces);
    }

    if (!type.isClass()) {
      throw new Error('Oh no');
    }

    const allDeclarations: Array<{
      decl: ts.Declaration;
      type: ts.InterfaceType | ts.BaseType;
    }> = type.symbol.declarations.map((decl) => ({ decl, type }));

    // Considering erased bases' declarations, too, so they are "blended in"
    for (const base of erasedBases) {
      allDeclarations.push(
        ...base.symbol.declarations.map((decl) => ({ decl, type: base })),
      );
    }

    for (const { decl, type: declaringType } of allDeclarations) {
      const classDecl = decl as ts.ClassDeclaration | ts.InterfaceDeclaration;
      if (!classDecl.members) {
        continue;
      }

      for (const memberDecl of classDecl.members) {
        if (ts.isSemicolonClassElement(memberDecl)) {
          this._diagnostics.push(
            JsiiDiagnostic.JSII_9996_UNNECESSARY_TOKEN.create(memberDecl),
          );
          continue;
        }

        const member: ts.Symbol = ts.isConstructorDeclaration(memberDecl)
          ? (memberDecl as any).symbol
          : this._typeChecker.getSymbolAtLocation(
              ts.getNameOfDeclaration(memberDecl)!,
            )!;

        if (
          !(declaringType.symbol.getDeclarations() ?? []).find(
            (d) => d === memberDecl.parent,
          )
        ) {
          continue;
        }

        if (this._isPrivateOrInternal(member, memberDecl as ts.ClassElement)) {
          continue;
        }

        // constructors are handled later
        if (ts.isConstructorDeclaration(memberDecl)) {
          continue;
        }

        // eslint-disable-next-line no-await-in-loop
        if (
          ts.isMethodDeclaration(memberDecl) ||
          ts.isMethodSignature(memberDecl)
        ) {
          // eslint-disable-next-line no-await-in-loop
          await this._visitMethod(
            member,
            jsiiType,
            ctx.replaceStability(jsiiType.docs?.stability),
            classDecl,
          );
        } else if (
          ts.isPropertyDeclaration(memberDecl) ||
          ts.isPropertySignature(memberDecl) ||
          ts.isAccessor(memberDecl)
        ) {
          // eslint-disable-next-line no-await-in-loop
          await this._visitProperty(
            member,
            jsiiType,
            ctx.replaceStability(jsiiType.docs?.stability),
            classDecl,
          );
        } else {
          this._diagnostics.push(
            JsiiDiagnostic.JSII_9998_UNSUPPORTED_NODE.create(
              ts.getNameOfDeclaration(memberDecl) ?? memberDecl,
              memberDecl.kind,
            ),
          );
        }
        /* eslint-enable no-await-in-loop */
      }
    }

    const memberEmitContext = ctx.replaceStability(
      jsiiType.docs && jsiiType.docs.stability,
    );

    // Find the first defined constructor in this class, or it's erased bases
    const constructor = [type, ...erasedBases]
      .map(getConstructor)
      .find((ctor) => ctor != null);
    const ctorDeclaration =
      constructor && (constructor.declarations[0] as ts.ConstructorDeclaration);
    if (constructor && ctorDeclaration) {
      const signature =
        this._typeChecker.getSignatureFromDeclaration(ctorDeclaration);

      if (
        (ts.getCombinedModifierFlags(ctorDeclaration) &
          ts.ModifierFlags.Private) ===
        0
      ) {
        jsiiType.initializer = {
          locationInModule: this.declarationLocation(ctorDeclaration),
        };
        if (signature) {
          for (const param of signature.getParameters()) {
            jsiiType.initializer.parameters =
              jsiiType.initializer.parameters ?? [];
            jsiiType.initializer.parameters.push(
              // eslint-disable-next-line no-await-in-loop
              await this._toParameter(
                param,
                ctx.replaceStability(jsiiType.docs?.stability),
              ),
            );
            jsiiType.initializer.variadic =
              jsiiType.initializer?.parameters?.some((p) => !!p.variadic) ||
              undefined;
            jsiiType.initializer.protected =
              (ts.getCombinedModifierFlags(ctorDeclaration) &
                ts.ModifierFlags.Protected) !==
                0 || undefined;
          }
        }
        this._verifyConsecutiveOptionals(
          ctorDeclaration,
          jsiiType.initializer.parameters,
        );
        jsiiType.initializer.docs = this._visitDocumentation(
          constructor,
          memberEmitContext,
        ).docs;
        this.overrideDocComment(
          constructor,
          jsiiType.initializer.docs,
          paramDocs(jsiiType.initializer.parameters),
        );
      }

      // Process constructor-based property declarations even if constructor is private
      if (signature) {
        for (const param of signature.getParameters()) {
          if (
            ts.isParameterPropertyDeclaration(
              param.valueDeclaration,
              param.valueDeclaration.parent,
            ) &&
            !this._isPrivateOrInternal(param)
          ) {
            // eslint-disable-next-line no-await-in-loop
            await this._visitProperty(
              param,
              jsiiType,
              memberEmitContext,
              ctorDeclaration.parent,
            );
          }
        }
      }
    } else if (jsiiType.base) {
      this._deferUntilTypesAvailable(
        fqn,
        [jsiiType.base],
        type.symbol.valueDeclaration,
        (baseType) => {
          if (spec.isClassType(baseType)) {
            jsiiType.initializer = baseType.initializer;
          } else {
            this._diagnostics.push(
              JsiiDiagnostic.JSII_3999_INCOHERENT_TYPE_MODEL.create(
                type.symbol.valueDeclaration ?? type.symbol.declarations[0],
                `Base type of ${jsiiType.fqn} (${jsiiType.base}) is not a class`,
              ),
            );
          }
        },
      );
    } else {
      jsiiType.initializer = {
        docs: ctx.stability && { stability: ctx.stability },
      };
    }

    this._verifyNoStaticMixing(jsiiType, type.symbol.valueDeclaration);

    this.overrideDocComment(type.getSymbol(), jsiiType?.docs);

    return _sortMembers(jsiiType);
  }