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