private async _visitInterface()

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