function writeType()

in src/externs.ts [406:516]


  function writeType(
      decl: ts.InterfaceDeclaration|ts.ClassDeclaration, namespace: ReadonlyArray<string>) {
    const name = decl.name;
    if (!name) {
      reportDiagnostic(diagnostics, decl, 'anonymous type in externs');
      return;
    }
    const typeName = namespace.concat([name.getText()]).join('.');
    if (PREDECLARED_CLOSURE_EXTERNS_LIST.indexOf(typeName) >= 0) return;

    if (isFirstValueDeclaration(decl)) {
      // Emit the 'function' that is actually the declaration of the interface
      // itself.  If it's a class, this function also must include the type
      // annotations of the constructor.
      let paramNames: string[] = [];
      const jsdocTags: jsdoc.Tag[] = [];
      let wroteJsDoc = false;
      maybeAddHeritageClauses(jsdocTags, mtt, decl);
      maybeAddTemplateClause(jsdocTags, decl);
      if (decl.kind === ts.SyntaxKind.ClassDeclaration) {
        // TODO: it appears you can just write 'class Foo { ...' in externs.
        // This code instead tries to translate it to a function.
        jsdocTags.push({tagName: 'constructor'}, {tagName: 'struct'});
        const ctors = (decl as ts.ClassDeclaration)
                          .members.filter((m) => m.kind === ts.SyntaxKind.Constructor);
        if (ctors.length) {
          const firstCtor: ts.ConstructorDeclaration = ctors[0] as ts.ConstructorDeclaration;
          if (ctors.length > 1) {
            paramNames = emitFunctionType(ctors as ts.ConstructorDeclaration[], jsdocTags);
          } else {
            paramNames = emitFunctionType([firstCtor], jsdocTags);
          }
          wroteJsDoc = true;
        }
      } else {
        // Otherwise it's an interface; tag it as structurally typed.
        jsdocTags.push({tagName: 'record'}, {tagName: 'struct'});
      }
      if (!wroteJsDoc) emit(jsdoc.toString(jsdocTags));
      writeFunction(name, paramNames, namespace);
    }

    // Process everything except (MethodSignature|MethodDeclaration|Constructor)
    const methods = new Map<string, ts.MethodDeclaration[]>();
    for (const member of decl.members) {
      switch (member.kind) {
        case ts.SyntaxKind.PropertySignature:
        case ts.SyntaxKind.PropertyDeclaration:
          const prop = member as ts.PropertySignature;
          if (prop.name.kind === ts.SyntaxKind.Identifier) {
            let type = mtt.typeToClosure(prop);
            if (prop.questionToken && type === '?') {
              // An optional 'any' type translates to '?|undefined' in Closure.
              type = '?|undefined';
            }
            emit(jsdoc.toString([{tagName: 'type', type}]));
            if (hasModifierFlag(prop, ts.ModifierFlags.Static)) {
              emit(`\n${typeName}.${prop.name.getText()};\n`);
            } else {
              emit(`\n${typeName}.prototype.${prop.name.getText()};\n`);
            }
            continue;
          }
          // TODO: For now property names other than Identifiers are not handled; e.g.
          //    interface Foo { "123bar": number }
          break;
        case ts.SyntaxKind.MethodSignature:
        case ts.SyntaxKind.MethodDeclaration:
          const method = member as ts.MethodDeclaration;
          const isStatic = hasModifierFlag(method, ts.ModifierFlags.Static);
          const methodSignature = `${method.name.getText()}$$$${isStatic ? 'static' : 'instance'}`;

          if (methods.has(methodSignature)) {
            methods.get(methodSignature)!.push(method);
          } else {
            methods.set(methodSignature, [method]);
          }
          continue;
        case ts.SyntaxKind.Constructor:
          continue;  // Handled above.
        default:
          // Members can include things like index signatures, for e.g.
          //   interface Foo { [key: string]: number; }
          // For now, just skip it.
          break;
      }
      // If we get here, the member wasn't handled in the switch statement.
      let memberName = namespace;
      if (member.name) {
        memberName = memberName.concat([member.name.getText()]);
      }
      emit(`\n/* TODO: ${ts.SyntaxKind[member.kind]}: ${memberName.join('.')} */\n`);
    }

    // Handle method declarations/signatures separately, since we need to deal with overloads.
    for (const methodVariants of Array.from(methods.values())) {
      const firstMethodVariant = methodVariants[0];
      let parameterNames: string[];
      if (methodVariants.length > 1) {
        parameterNames = emitFunctionType(methodVariants);
      } else {
        parameterNames = emitFunctionType([firstMethodVariant]);
      }
      const methodNamespace = namespace.concat([name.getText()]);
      // If the method is static, don't add the prototype.
      if (!hasModifierFlag(firstMethodVariant, ts.ModifierFlags.Static)) {
        methodNamespace.push('prototype');
      }
      writeFunction(firstMethodVariant.name, parameterNames, methodNamespace);
    }
  }