function mergeVariablesIntoClasses()

in lib/merge.ts [366:548]


  function mergeVariablesIntoClasses(n: ts.Node, classes: Map<string, base.ClassLike>) {
    // In TypeScript, a constructor may be represented by an object whose type contains a "new"
    // method. When a variable has a type with a "new" method, it means that the variable must
    // either be an ES6 class or the equivalent desugared constructor function. As Dart does not
    // support calling arbitrary functions like constructors, we need to upgrade the variable to be
    // a class with the appropriate members so that we can invoke the constructor on that class.
    if (ts.isVariableStatement(n)) {
      const statement = n;
      statement.declarationList.declarations.forEach((variableDecl: ts.VariableDeclaration) => {
        if (ts.isIdentifier(variableDecl.name)) {
          const name = base.ident(variableDecl.name);
          const variableType = variableDecl.type;
          if (!variableType) {
            return;
          }

          // We need to find the declaration of the variable's type in order to acccess the
          // members of that type.
          let variableTypeDeclaration: ts.TypeLiteralNode|ts.InterfaceDeclaration;
          if (ts.isTypeReferenceNode(variableType)) {
            variableTypeDeclaration =
                fc.getDeclarationOfReferencedType(variableType, (declaration: ts.Declaration) => {
                  return ts.isTypeLiteralNode(declaration) ||
                      ts.isInterfaceDeclaration(declaration);
                }) as ts.TypeLiteralNode | ts.InterfaceDeclaration;
          } else if (ts.isTypeLiteralNode(variableType)) {
            variableTypeDeclaration = variableType;
          }
          if (!variableTypeDeclaration) {
            return;
          }

          // Try to find a Constructor within the variable's type.
          const constructor: base.Constructor = findConstructorInType(variableTypeDeclaration);
          if (constructor) {
            // Get the type of object that the constructor creates.
            const constructedType = getConstructedObjectType(constructor);

            if (classes.has(name)) {
              const existing = classes.get(name);
              // If a class or interface with the same name as the variable already exists, we
              // should suppress that declaration because it will be cloned into a stub class or
              // interface below.
              nodeReplacements.set(existing, undefined);
            }

            // These properties do not exist on TypeLiteralNodes.
            let clazzTypeParameters, clazzHeritageClauses;
            if (ts.isClassDeclaration(constructedType) ||
                ts.isInterfaceDeclaration(constructedType)) {
              clazzTypeParameters = base.cloneNodeArray(constructedType.typeParameters);
              clazzHeritageClauses = base.cloneNodeArray(constructedType.heritageClauses);
            }

            let clazz: ts.InterfaceDeclaration|ts.ClassDeclaration;
            if (ts.isClassDeclaration(constructedType)) {
              clazz = ts.createClassDeclaration(
                  base.cloneNodeArray(constructedType.decorators),
                  base.cloneNodeArray(constructedType.modifiers),
                  ts.createIdentifier(base.ident(variableDecl.name)),
                  base.cloneNodeArray(clazzTypeParameters),
                  base.cloneNodeArray(clazzHeritageClauses),
                  base.cloneNodeArray(constructedType.members));
            } else if (
                ts.isTypeLiteralNode(constructedType) ||
                ts.isInterfaceDeclaration(constructedType)) {
              // TODO(derekx): Try creating abstract class declarations in these cases.
              // InterfaceDeclarations get emitted as abstract classes regardless, it would just
              // make the JSON output more accurate.
              clazz = ts.createInterfaceDeclaration(
                  base.cloneNodeArray(constructedType.decorators),
                  base.cloneNodeArray(constructedType.modifiers),
                  ts.createIdentifier(base.ident(variableDecl.name)),
                  base.cloneNodeArray(clazzTypeParameters),
                  base.cloneNodeArray(clazzHeritageClauses),
                  base.cloneNodeArray(constructedType.members));
              (clazz as base.ExtendedInterfaceDeclaration).constructedType = constructedType;
            }

            base.copyLocation(variableDecl, clazz);
            clazz.flags = variableDecl.flags;
            nodeReplacements.set(n, clazz);
            classes.set(name, clazz);
          } else {
            if (renameConflictingTypes) {
              // If we cannot find a constructor within the variable's type and the
              // --rename-conflicting-types flag is set, we need to check whether or not a type
              // with the same name as the variable already exists. If it does, we must rename it.
              // That type is not directly associated with this variable, so they cannot be
              // combined.
              const variableSymbol = fc.getSymbolAtLocation(variableDecl.name);
              if (variableSymbol.getDeclarations()) {
                for (const declaration of variableSymbol.getDeclarations()) {
                  if (ts.isInterfaceDeclaration(declaration) ||
                      ts.isTypeAliasDeclaration(declaration)) {
                    declaration.name.escapedText = ts.escapeLeadingUnderscores(name + 'Type');
                  }
                }
              }
              return;
            } else if (!renameConflictingTypes && classes.has(name)) {
              // If we cannot find a constructor and there exists a class with the exact same name,
              // we assume by default that the variable and type are related as they have the exact
              // same name. Thus, the variable declaration is suppressed and the members of its type
              // are merged into the existing class below.
              nodeReplacements.set(variableDecl, undefined);
            }
          }

          // Merge the members of the variable's type into the existing class.
          const existing = classes.get(name);
          if (!existing) {
            return;
          }

          const members = existing.members;
          variableTypeDeclaration.members.forEach((member: ts.TypeElement|ts.ClassElement) => {
            // Array.prototype.push is used below as a small hack to get around NodeArrays being
            // readonly.
            switch (member.kind) {
              case ts.SyntaxKind.Constructor:
              case ts.SyntaxKind.ConstructorType:
              case ts.SyntaxKind.ConstructSignature: {
                const clonedConstructor = ts.getMutableClone(member);
                clonedConstructor.name = ts.getMutableClone(variableDecl.name) as ts.PropertyName;
                clonedConstructor.parent = existing;

                const existingConstructIndex = members.findIndex(base.isConstructor);
                if (existingConstructIndex === -1) {
                  Array.prototype.push.call(members, clonedConstructor);
                } else {
                  Array.prototype.splice.call(members, existingConstructIndex, clonedConstructor);
                }
              } break;
              case ts.SyntaxKind.MethodSignature:
                member.parent = existing;
                Array.prototype.push.call(members, member);
                break;
              case ts.SyntaxKind.PropertySignature:
                // TODO(derekx): This should also be done to methods.
                if (!explicitStatic) {
                  // Finds all existing declarations of this property in the inheritance
                  // hierarchy of this class.
                  const existingDeclarations =
                      findPropertyInHierarchy(base.ident(member.name), existing, classes);

                  if (existingDeclarations.size) {
                    for (const existingDecl of existingDeclarations) {
                      addModifier(existingDecl, ts.createModifier(ts.SyntaxKind.StaticKeyword));
                    }
                  }
                }

                // If needed, add declaration of property to the interface that we are
                // currently handling.
                if (!findPropertyInClass(base.ident(member.name), existing)) {
                  if (!explicitStatic) {
                    addModifier(member, ts.createModifier(ts.SyntaxKind.StaticKeyword));
                  }
                  member.parent = existing;
                  Array.prototype.push.call(members, member);
                }
                break;
              case ts.SyntaxKind.IndexSignature:
                member.parent = existing;
                Array.prototype.push.call(members, member);
                break;
              case ts.SyntaxKind.CallSignature:
                member.parent = existing;
                Array.prototype.push.call(members, member);
                break;
              default:
                throw 'Unhandled TypeLiteral member type:' + member.kind;
            }
          });
        } else {
          throw 'Unexpected VariableStatement identifier kind';
        }
      });
    } else if (ts.isModuleBlock(n)) {
      ts.forEachChild(n, (child) => mergeVariablesIntoClasses(child, classes));
    }
  }