function createMemberTypeDeclaration()

in src/jsdoc_transformer.ts [189:309]


function createMemberTypeDeclaration(
    mtt: ModuleTypeTranslator,
    typeDecl: ts.ClassDeclaration|ts.InterfaceDeclaration): ts.IfStatement|null {
  // Gather parameter properties from the constructor, if it exists.
  const ctors: ts.ConstructorDeclaration[] = [];
  let paramProps: ts.ParameterDeclaration[] = [];
  const nonStaticProps: ClosureProperty[] = [];
  const staticProps: ClosureProperty[] = [];
  const unhandled: ts.NamedDeclaration[] = [];
  const abstractMethods: ts.FunctionLikeDeclaration[] = [];
  for (const member of typeDecl.members) {
    if (member.kind === ts.SyntaxKind.Constructor) {
      ctors.push(member as ts.ConstructorDeclaration);
    } else if (
        ts.isPropertyDeclaration(member) || ts.isPropertySignature(member) ||
        (ts.isMethodDeclaration(member) && member.questionToken)) {
      const isStatic =
          transformerUtil.hasModifierFlag(member, ts.ModifierFlags.Static);
      if (isStatic) {
        staticProps.push(member);
      } else {
        nonStaticProps.push(member);
      }
    } else if (
        member.kind === ts.SyntaxKind.MethodDeclaration ||
        member.kind === ts.SyntaxKind.MethodSignature ||
        member.kind === ts.SyntaxKind.GetAccessor ||
        member.kind === ts.SyntaxKind.SetAccessor) {
      if (transformerUtil.hasModifierFlag(member, ts.ModifierFlags.Abstract) ||
          ts.isInterfaceDeclaration(typeDecl)) {
        abstractMethods.push(
            member as ts.MethodDeclaration | ts.GetAccessorDeclaration |
            ts.SetAccessorDeclaration);
      }
      // Non-abstract methods only exist on classes, and are handled in regular
      // emit.
    } else {
      unhandled.push(member);
    }
  }

  if (ctors.length > 0) {
    // Only the actual constructor implementation, which must be last in a potential sequence of
    // overloaded constructors, may contain parameter properties.
    const ctor = ctors[ctors.length - 1];
    paramProps = ctor.parameters.filter(
        p => transformerUtil.hasModifierFlag(p, ts.ModifierFlags.ParameterPropertyModifier));
  }

  if (nonStaticProps.length === 0 && paramProps.length === 0 && staticProps.length === 0 &&
      abstractMethods.length === 0) {
    // There are no members so we don't need to emit any type
    // annotations helper.
    return null;
  }

  if (!typeDecl.name) {
    mtt.debugWarn(typeDecl, 'cannot add types on unnamed declarations');
    return null;
  }

  const className = transformerUtil.getIdentifierText(typeDecl.name);
  const staticPropAccess = ts.createIdentifier(className);
  const instancePropAccess = ts.createPropertyAccess(staticPropAccess, 'prototype');
  // Closure Compiler will report conformance errors about this being unknown type when emitting
  // class properties as {?|undefined}, instead of just {?}. So make sure to only emit {?|undefined}
  // on interfaces.
  const isInterface = ts.isInterfaceDeclaration(typeDecl);
  const propertyDecls = staticProps.map(
      p => createClosurePropertyDeclaration(
          mtt, staticPropAccess, p, isInterface && !!p.questionToken));
  propertyDecls.push(...[...nonStaticProps, ...paramProps].map(
      p => createClosurePropertyDeclaration(
          mtt, instancePropAccess, p, isInterface && !!p.questionToken)));
  propertyDecls.push(...unhandled.map(
      p => transformerUtil.createMultiLineComment(
          p, `Skipping unhandled member: ${escapeForComment(p.getText())}`)));

  for (const fnDecl of abstractMethods) {
    // If the function declaration is computed, its name is the computed expression; otherwise, its
    // name can be resolved to a string.
    const name = fnDecl.name && ts.isComputedPropertyName(fnDecl.name) ? fnDecl.name.expression :
                                                                         propertyName(fnDecl);
    if (!name) {
      mtt.error(fnDecl, 'anonymous abstract function');
      continue;
    }
    const {tags, parameterNames} = mtt.getFunctionTypeJSDoc([fnDecl], []);
    if (hasExportingDecorator(fnDecl, mtt.typeChecker)) tags.push({tagName: 'export'});
    // Use element access instead of property access for computed names.
    const lhs = typeof name === 'string' ? ts.createPropertyAccess(instancePropAccess, name) :
                                           ts.createElementAccess(instancePropAccess, name);
    // memberNamespace because abstract methods cannot be static in TypeScript.
    const abstractFnDecl = ts.createStatement(ts.createAssignment(
        lhs,
        ts.createFunctionExpression(
            /* modifiers */ undefined,
            /* asterisk */ undefined,
            /* name */ undefined,
            /* typeParameters */ undefined,
            parameterNames.map(
                n => ts.createParameter(
                    /* decorators */ undefined, /* modifiers */ undefined,
                    /* dotDotDot */ undefined, n)),
            undefined,
            ts.createBlock([]),
            )));
    ts.setSyntheticLeadingComments(abstractFnDecl, [jsdoc.toSynthesizedComment(tags)]);
    propertyDecls.push(ts.setSourceMapRange(abstractFnDecl, fnDecl));
  }

  // Wrap the property declarations in an 'if (false)' block.
  // See test_files/fields/fields.ts:BaseThatThrows for a note on this wrapper.
  const ifStmt =
      ts.createIf(ts.createLiteral(false), ts.createBlock(propertyDecls, true));
  // Also add a comment above the block to exclude it from coverage.
  ts.addSyntheticLeadingComment(
      ifStmt, ts.SyntaxKind.MultiLineCommentTrivia, ' istanbul ignore if ',
      /* trailing newline */ true);
  return ifStmt;
}