private _visitInterface()

in src/assembler.ts [1652:1853]


  private _visitInterface(type: ts.Type, ctx: EmitContext): 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 | undefined,
    );

    const { interfaces, erasedBases } = this._processBaseInterfaces(fqn, type.getBaseTypes());
    jsiiType.interfaces = apply(interfaces, (arr) => arr.map((i) => i.fqn));

    const typeDecl = (type.symbol.valueDeclaration ?? type.symbol.declarations?.[0]) as
      | ts.ClassLikeDeclaration
      | ts.InterfaceDeclaration
      | undefined;

    for (const typeParam of typeDecl?.typeParameters ?? []) {
      this._diagnostics.push(JsiiDiagnostic.JSII_1006_GENERIC_TYPE.create(typeParam));
    }

    for (const decl of (typeDecl?.members as ReadonlyArray<ts.ClassElement | ts.TypeElement> | undefined)?.filter(
      (mem) => ts.isIndexSignatureDeclaration(mem),
    ) ?? []) {
      const sym =
        type.symbol.members?.get(ts.InternalSymbolName.Index) ?? type.symbol.exports?.get(ts.InternalSymbolName.Index);
      if (sym != null && this._isPrivateOrInternal(sym, decl)) {
        continue;
      }

      // Index signatures (static or not) are not supported in the jsii type model.
      this._diagnostics.push(
        JsiiDiagnostic.JSII_1999_UNSUPPORTED.create(decl, { what: 'Index signatures', suggestInternal: true }),
      );
    }

    for (const declaringType of [type, ...erasedBases]) {
      for (const member of declaringType.getProperties()) {
        const decl = member.valueDeclaration ?? (member.declarations?.[0] as ts.PropertyDeclaration | undefined);

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

        if (this._isPrivateOrInternal(member, decl)) {
          continue;
        }

        if (decl && (ts.isMethodDeclaration(decl) || ts.isMethodSignature(decl))) {
          // eslint-disable-next-line no-await-in-loop
          this._visitMethod(member, jsiiType, ctx.replaceStability(jsiiType.docs?.stability), typeDecl);
        } else if (decl && (ts.isPropertyDeclaration(decl) || ts.isPropertySignature(decl) || ts.isAccessor(decl))) {
          // eslint-disable-next-line no-await-in-loop
          this._visitProperty(member, jsiiType, ctx.replaceStability(jsiiType.docs?.stability), typeDecl);
        } else {
          this._diagnostics.push(
            JsiiDiagnostic.JSII_9998_UNSUPPORTED_NODE.create(
              _nameOrDeclarationNode(member),
              (member.valueDeclaration ?? member.declarations?.[0])?.kind ?? ts.SyntaxKind.Unknown,
            ),
          );
        }
      }
    }

    // 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(declaration && _findHint(declaration, 'struct')!, 'struct', 'interfaces with only readonly properties').addRelatedInformationIf(ts.getNameOfDeclaration(declaration) ?? declaration, 'The annotated declartion is here')),
        );
      }

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

      // NOTE: We need to be careful with the `I` prefix for behavioral interfaces, as this can mess with PascalCase
      // transformations, especially with short names such as `IA`, ...
      const expectedName = interfaceName ? `I${Case.pascal(type.symbol.name.slice(1))}` : Case.pascal(type.symbol.name);
      if (expectedName !== type.symbol.name) {
        this._diagnostics.push(
          JsiiDiagnostic.JSII_8000_PASCAL_CASED_TYPE_NAMES.create(
            (type.symbol.declarations?.[0] as ts.InterfaceDeclaration | undefined)?.name,
            type.symbol.name,
            expectedName,
          ),
        );
      }

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

    return _sortMembers(jsiiType);
  }