| Index: lib/merge.ts
|
| diff --git a/lib/merge.ts b/lib/merge.ts
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..0b9c8d746496b1608dd7eb08d7b35a25735520ed
|
| --- /dev/null
|
| +++ b/lib/merge.ts
|
| @@ -0,0 +1,322 @@
|
| +import ts = require('typescript');
|
| +import base = require('./base');
|
| +import {FacadeConverter} from './facade_converter';
|
| +
|
| +/**
|
| + * To support arbitrary d.ts files in Dart we often have to merge two TypeScript
|
| + * types into a single Dart type because Dart lacks features such as method
|
| + * overloads, type aliases, and union types.
|
| + */
|
| +export class MergedType {
|
| + constructor(private fc: FacadeConverter) {}
|
| +
|
| + merge(t?: ts.TypeNode) {
|
| + if (t) {
|
| + // TODO(jacobr): get a better unique name for a type.
|
| + switch (t.kind) {
|
| + case ts.SyntaxKind.UnionType:
|
| + let union = <ts.UnionTypeNode>t;
|
| + union.types.forEach(this.merge.bind(this));
|
| + return;
|
| + case ts.SyntaxKind.LastTypeNode:
|
| + this.merge((t as ts.ParenthesizedTypeNode).type);
|
| + return;
|
| + case ts.SyntaxKind.TypePredicate:
|
| + this.merge((t as ts.TypePredicateNode).type);
|
| + return;
|
| + case ts.SyntaxKind.TypeReference:
|
| + // We need to follow Alais types as Dart does not support them for non
|
| + // function types. TODO(jacobr): handle them for Function types?
|
| + let typeRef = <ts.TypeReferenceNode>t;
|
| + let decl = this.fc.getDeclaration(typeRef.typeName);
|
| + if (decl !== null && decl.kind === ts.SyntaxKind.TypeAliasDeclaration) {
|
| + let alias = <ts.TypeAliasDeclaration>decl;
|
| +
|
| + if (typeRef.typeArguments) {
|
| + throw 'TypeReference with arguements not supported yet:' + t.getText();
|
| + }
|
| +
|
| + this.merge(alias.type);
|
| + return;
|
| + }
|
| + break;
|
| + default:
|
| + break;
|
| + }
|
| + this.types[this.fc.generateDartTypeName(t, true)] = t;
|
| + }
|
| + }
|
| +
|
| + toTypeNode(): ts.TypeNode {
|
| + let names = Object.getOwnPropertyNames(this.types);
|
| + if (names.length === 0) {
|
| + return null;
|
| + }
|
| + if (names.length === 1) {
|
| + return this.types[names[0]];
|
| + }
|
| + let union = <ts.UnionTypeNode>ts.createNode(ts.SyntaxKind.UnionType);
|
| + base.copyLocation(this.types[names[0]], union);
|
| +
|
| + union.types = <ts.NodeArray<ts.TypeNode>>[];
|
| + for (let i = 0; i < names.length; ++i) {
|
| + union.types.push(this.types[names[i]]);
|
| + }
|
| + return union;
|
| + }
|
| +
|
| + private types: {[name: string]: ts.TypeNode} = {};
|
| +}
|
| +
|
| +/**
|
| + * Handle a parameter that is the result of merging parameter declarations from
|
| + * multiple method overloads.
|
| + */
|
| +export class MergedParameter {
|
| + constructor(param: ts.ParameterDeclaration, fc: FacadeConverter) {
|
| + this.type = new MergedType(fc);
|
| + this.textRange = param;
|
| + this.merge(param);
|
| + }
|
| +
|
| + merge(param: ts.ParameterDeclaration) {
|
| + this.name[base.ident(param.name)] = true;
|
| + if (!this.optional) {
|
| + this.optional = !!param.questionToken;
|
| + }
|
| + this.type.merge(param.type);
|
| + }
|
| +
|
| + toParameterDeclaration(): ts.ParameterDeclaration {
|
| + let ret = <ts.ParameterDeclaration>ts.createNode(ts.SyntaxKind.Parameter);
|
| + let nameIdentifier = <ts.Identifier>ts.createNode(ts.SyntaxKind.Identifier);
|
| + nameIdentifier.text = Object.getOwnPropertyNames(this.name).join('_');
|
| + ret.name = nameIdentifier;
|
| + if (this.optional) {
|
| + ret.questionToken = ts.createNode(ts.SyntaxKind.QuestionToken);
|
| + }
|
| + base.copyLocation(this.textRange, ret);
|
| + ret.type = this.type.toTypeNode();
|
| + return ret;
|
| + }
|
| +
|
| + setOptional() { this.optional = true; }
|
| +
|
| + private name: {[s: string]: boolean} = {};
|
| + private type: MergedType;
|
| + private optional: boolean = false;
|
| + private textRange: ts.TextRange;
|
| +}
|
| +
|
| +/**
|
| + * Normalize a SourceFile
|
| + */
|
| +export function normalizeSourceFile(f: ts.SourceFile) {
|
| + let modules: {[name: string]: ts.ModuleDeclaration} = {};
|
| +
|
| + // Merge top level modules.
|
| + for (let i = 0; i < f.statements.length; ++i) {
|
| + let statement = f.statements[i];
|
| + if (statement.kind !== ts.SyntaxKind.ModuleDeclaration) continue;
|
| + let moduleDecl = <ts.ModuleDeclaration>statement;
|
| + let name = moduleDecl.name.text;
|
| + if (modules.hasOwnProperty(name)) {
|
| + let srcBody = modules[name].body;
|
| + let srcBodyBlock: ts.ModuleBlock;
|
| +
|
| + if (srcBody.kind !== ts.SyntaxKind.ModuleBlock) {
|
| + throw 'Module body must be a module block.';
|
| + }
|
| + srcBodyBlock = <ts.ModuleBlock>srcBody;
|
| +
|
| + let body = moduleDecl.body;
|
| + if (body.kind === ts.SyntaxKind.ModuleBlock) {
|
| + let bodyBlock = <ts.ModuleBlock>body;
|
| + Array.prototype.push.apply(srcBodyBlock.statements, bodyBlock.statements);
|
| + } else {
|
| + // moduleDecl.body is a ModuleDeclaration.
|
| + srcBodyBlock.statements.push(moduleDecl.body);
|
| + }
|
| +
|
| + f.statements.splice(i, 1);
|
| + i--;
|
| + } else {
|
| + modules[name] = moduleDecl;
|
| + }
|
| + }
|
| +
|
| + function addModifier(n: ts.Node, modifier: ts.Node) {
|
| + if (!n.modifiers) {
|
| + n.modifiers = <ts.ModifiersArray>[];
|
| + n.modifiers.flags = 0;
|
| + }
|
| + modifier.parent = n;
|
| + n.modifiers.push(modifier);
|
| + }
|
| +
|
| + function mergeVariablesIntoClasses(n: ts.Node, classes: {[name: string]: base.ClassLike}) {
|
| + switch (n.kind) {
|
| + case ts.SyntaxKind.VariableStatement:
|
| + let statement = <ts.VariableStatement>n;
|
| + statement.declarationList.declarations.forEach(function(
|
| + declaration: ts.VariableDeclaration) {
|
| + if (declaration.name.kind === ts.SyntaxKind.Identifier) {
|
| + let name: string = (<ts.Identifier>(declaration.name)).text;
|
| + if (classes.hasOwnProperty(name)) {
|
| + let existing = classes[name];
|
| + let members = existing.members as Array<ts.ClassElement>;
|
| + if (declaration.type) {
|
| + let type: ts.TypeNode = declaration.type;
|
| + if (type.kind === ts.SyntaxKind.TypeLiteral) {
|
| + removeNode(n);
|
| + let literal = <ts.TypeLiteralNode>type;
|
| + literal.members.forEach((member: ts.Node) => {
|
| + switch (member.kind) {
|
| + case ts.SyntaxKind.ConstructSignature:
|
| + let signature: any = member;
|
| + let constructor =
|
| + <ts.ConstructorDeclaration>ts.createNode(ts.SyntaxKind.Constructor);
|
| + constructor.parameters = signature.parameters;
|
| + constructor.type = signature.type;
|
| + base.copyLocation(signature, constructor);
|
| + constructor.typeParameters = signature.typeParameters;
|
| + constructor.parent = existing;
|
| + members.push(<ts.ClassElement>constructor);
|
| + break;
|
| + case ts.SyntaxKind.Constructor:
|
| + member.parent = existing.parent;
|
| + members.push(<ts.ClassElement>member);
|
| + break;
|
| + case ts.SyntaxKind.MethodSignature:
|
| + member.parent = existing.parent;
|
| + members.push(<ts.ClassElement>member);
|
| + break;
|
| + case ts.SyntaxKind.PropertySignature:
|
| + addModifier(member, ts.createNode(ts.SyntaxKind.StaticKeyword));
|
| + member.parent = existing;
|
| + members.push(<ts.ClassElement>member);
|
| + break;
|
| + case ts.SyntaxKind.IndexSignature:
|
| + member.parent = existing.parent;
|
| + members.push(<ts.ClassElement>member);
|
| + break;
|
| + case ts.SyntaxKind.CallSignature:
|
| + member.parent = existing.parent;
|
| + members.push(<ts.ClassElement>member);
|
| + break;
|
| + default:
|
| + throw 'Unhandled TypeLiteral member type:' + member.kind;
|
| + }
|
| + });
|
| + }
|
| + }
|
| + }
|
| + } else {
|
| + throw 'Unexpected VariableStatement identifier kind';
|
| + }
|
| + });
|
| + break;
|
| + case ts.SyntaxKind.ModuleBlock:
|
| + ts.forEachChild(n, (child) => mergeVariablesIntoClasses(child, classes));
|
| + break;
|
| + default:
|
| + break;
|
| + }
|
| + }
|
| +
|
| + function removeFromArray(nodes: ts.NodeArray<ts.Node>, v: ts.Node) {
|
| + for (let i = 0, len = nodes.length; i < len; ++i) {
|
| + if (nodes[i] === v) {
|
| + nodes.splice(i, 1);
|
| + break;
|
| + }
|
| + }
|
| + }
|
| +
|
| + function removeNode(n: ts.Node) {
|
| + let parent = n.parent;
|
| + switch (parent.kind) {
|
| + case ts.SyntaxKind.ModuleBlock:
|
| + let block = <ts.ModuleBlock>parent;
|
| + removeFromArray(block.statements, n);
|
| + break;
|
| + case ts.SyntaxKind.SourceFile:
|
| + let sourceFile = <ts.SourceFile>parent;
|
| + removeFromArray(sourceFile.statements, n);
|
| + break;
|
| + default:
|
| + throw 'removeNode not implemented for kind:' + parent.kind;
|
| + }
|
| + }
|
| +
|
| + function makeCallableClassesImplementFunction(decl: base.ClassLike) {
|
| + if (base.isCallable(decl)) {
|
| + // Modify the AST to explicitly state that the class implements Function
|
| + if (!decl.heritageClauses) {
|
| + decl.heritageClauses = <ts.NodeArray<ts.HeritageClause>>[];
|
| + base.copyLocation(decl, decl.heritageClauses);
|
| + }
|
| + let clauses = decl.heritageClauses;
|
| + let clause = base.arrayFindPolyfill(
|
| + clauses, (c) => c.token !== ts.SyntaxKind.ExtendsKeyword ||
|
| + decl.kind === ts.SyntaxKind.InterfaceDeclaration);
|
| + if (clause == null) {
|
| + clause = <ts.HeritageClause>ts.createNode(ts.SyntaxKind.HeritageClause);
|
| + clause.token = decl.kind === ts.SyntaxKind.InterfaceDeclaration ?
|
| + ts.SyntaxKind.ExtendsKeyword :
|
| + ts.SyntaxKind.ImplementsKeyword;
|
| + clause.types = <ts.NodeArray<ts.ExpressionWithTypeArguments>>[];
|
| + clause.parent = decl;
|
| + base.copyLocation(decl, clause);
|
| + clauses.push(clause);
|
| + }
|
| + let functionType =
|
| + <ts.ExpressionWithTypeArguments>ts.createNode(ts.SyntaxKind.ExpressionWithTypeArguments);
|
| + functionType.parent = clause;
|
| + base.copyLocation(clause, functionType);
|
| + let fn = <ts.Identifier>ts.createNode(ts.SyntaxKind.Identifier);
|
| + fn.text = 'Function';
|
| + fn.parent = functionType;
|
| + base.copyLocation(functionType, fn);
|
| + functionType.expression = fn;
|
| + clause.types.push(functionType);
|
| + }
|
| + }
|
| +
|
| + function gatherClasses(n: ts.Node, classes: {[name: string]: base.ClassLike}) {
|
| + switch (n.kind) {
|
| + case ts.SyntaxKind.ClassDeclaration:
|
| + case ts.SyntaxKind.InterfaceDeclaration:
|
| + let classDecl = <base.ClassLike>n;
|
| + let name = classDecl.name.text;
|
| + // TODO(jacobr): validate that the classes have consistent
|
| + // modifiers, etc.
|
| + if (classes.hasOwnProperty(name)) {
|
| + let existing = classes[name];
|
| + (classDecl.members as Array<ts.ClassElement>).forEach((e: ts.ClassElement) => {
|
| + (existing.members as Array<ts.ClassElement>).push(e);
|
| + e.parent = existing;
|
| + });
|
| + removeNode(classDecl);
|
| + } else {
|
| + classes[name] = classDecl;
|
| + // Perform other class level post processing here.
|
| + makeCallableClassesImplementFunction(classDecl);
|
| + }
|
| + break;
|
| + case ts.SyntaxKind.ModuleDeclaration:
|
| + case ts.SyntaxKind.SourceFile:
|
| + let moduleClasses: {[name: string]: base.ClassLike} = {};
|
| + ts.forEachChild(n, (child) => gatherClasses(child, moduleClasses));
|
| + ts.forEachChild(n, (child) => mergeVariablesIntoClasses(child, moduleClasses));
|
| +
|
| + break;
|
| + case ts.SyntaxKind.ModuleBlock:
|
| + ts.forEachChild(n, (child) => gatherClasses(child, classes));
|
| + break;
|
| + default:
|
| + break;
|
| + }
|
| + }
|
| + gatherClasses(f, {});
|
| +}
|
|
|