| Index: lib/merge.ts
|
| diff --git a/lib/merge.ts b/lib/merge.ts
|
| index 0b9c8d746496b1608dd7eb08d7b35a25735520ed..49180029a4467d5b680f36e0e183f9e4a1567df0 100644
|
| --- a/lib/merge.ts
|
| +++ b/lib/merge.ts
|
| @@ -14,12 +14,18 @@ export class MergedType {
|
| if (t) {
|
| // TODO(jacobr): get a better unique name for a type.
|
| switch (t.kind) {
|
| + case ts.SyntaxKind.NullKeyword:
|
| + // No need to include the null type as all Dart types are nullable anyway.
|
| + return;
|
| 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);
|
| + case ts.SyntaxKind.IntersectionType:
|
| + // Arbitrarily pick the first type of the intersection type as the merged type.
|
| + // TODO(jacobr): re-evaluate this logic.
|
| + let intersection = <ts.IntersectionTypeNode>t;
|
| + this.merge(intersection.types[0]);
|
| return;
|
| case ts.SyntaxKind.TypePredicate:
|
| this.merge((t as ts.TypePredicateNode).type);
|
| @@ -31,19 +37,21 @@ export class MergedType {
|
| let decl = this.fc.getDeclaration(typeRef.typeName);
|
| if (decl !== null && decl.kind === ts.SyntaxKind.TypeAliasDeclaration) {
|
| let alias = <ts.TypeAliasDeclaration>decl;
|
| + if (!base.supportedAliasType(alias)) {
|
| + if (typeRef.typeArguments) {
|
| + console.log(
|
| + 'Warning: typeReference with arguements not supported yet:' + t.getText());
|
| + }
|
|
|
| - if (typeRef.typeArguments) {
|
| - throw 'TypeReference with arguements not supported yet:' + t.getText();
|
| + this.merge(alias.type);
|
| }
|
| -
|
| - this.merge(alias.type);
|
| return;
|
| }
|
| break;
|
| default:
|
| break;
|
| }
|
| - this.types[this.fc.generateDartTypeName(t, true)] = t;
|
| + this.types[this.fc.generateDartTypeName(t, {insideComment: true})] = t;
|
| }
|
| }
|
|
|
| @@ -109,9 +117,91 @@ export class MergedParameter {
|
| }
|
|
|
| /**
|
| + * Handle a parameter that is the result of merging parameter declarations from
|
| + * multiple method overloads.
|
| + */
|
| +export class MergedTypeParameter {
|
| + constructor(param: ts.TypeParameterDeclaration, fc: FacadeConverter) {
|
| + this.constraint = new MergedType(fc);
|
| + this.textRange = param;
|
| + this.merge(param);
|
| + this.name = base.ident(param.name);
|
| + }
|
| +
|
| + merge(param: ts.TypeParameterDeclaration) {
|
| + this.constraint.merge(param.constraint);
|
| + // We ignore param.expression as it is not supported by Dart.
|
| + }
|
| +
|
| + toTypeParameterDeclaration(): ts.TypeParameterDeclaration {
|
| + let ret = <ts.TypeParameterDeclaration>ts.createNode(ts.SyntaxKind.TypeParameter);
|
| + let nameIdentifier = <ts.Identifier>ts.createNode(ts.SyntaxKind.Identifier);
|
| + nameIdentifier.text = this.name;
|
| + ret.name = nameIdentifier;
|
| + base.copyLocation(this.textRange, ret);
|
| + let constraint = this.constraint.toTypeNode();
|
| + // TODO(jacobr): remove this check once we have support for union types within comments.
|
| + // We can't currently handle union types in merged type parameters as the comments for type
|
| + // parameters in function types are not there for documentation and impact strong mode.
|
| + if (constraint && constraint.kind !== ts.SyntaxKind.UnionType) {
|
| + ret.constraint = constraint;
|
| + }
|
| + return ret;
|
| + }
|
| +
|
| + private name: string;
|
| + private constraint: MergedType;
|
| + private textRange: ts.TextRange;
|
| +}
|
| +
|
| +/**
|
| + * Handle a parameter that is the result of merging parameter declarations from
|
| + * multiple method overloads.
|
| + */
|
| +export class MergedTypeParameters {
|
| + private mergedParameters: {[s: string]: MergedTypeParameter} = {};
|
| + private textRange: ts.TextRange;
|
| +
|
| + constructor(private fc: FacadeConverter) {}
|
| +
|
| + merge(params: ts.NodeArray<ts.TypeParameterDeclaration>) {
|
| + if (!params) return;
|
| + if (!this.textRange) {
|
| + this.textRange = params;
|
| + }
|
| + for (let i = 0; i < params.length; i++) {
|
| + let param = params[i];
|
| + let name = base.ident(param.name);
|
| + if (Object.hasOwnProperty.call(this.mergedParameters, name)) {
|
| + let merged = this.mergedParameters[name];
|
| + if (merged) {
|
| + merged.merge(param);
|
| + }
|
| + } else {
|
| + this.mergedParameters[name] = new MergedTypeParameter(param, this.fc);
|
| + }
|
| + }
|
| + }
|
| +
|
| + toTypeParameters(): ts.NodeArray<ts.TypeParameterDeclaration> {
|
| + let names = Object.getOwnPropertyNames(this.mergedParameters);
|
| + if (names.length === 0) {
|
| + return undefined;
|
| + }
|
| +
|
| + let ret = [] as ts.NodeArray<ts.TypeParameterDeclaration>;
|
| + base.copyLocation(this.textRange, ret);
|
| + for (let i = 0; i < names.length; ++i) {
|
| + ret.push(this.mergedParameters[names[i]].toTypeParameterDeclaration());
|
| + }
|
| + return ret;
|
| + }
|
| +}
|
| +
|
| +/**
|
| * Normalize a SourceFile
|
| */
|
| -export function normalizeSourceFile(f: ts.SourceFile) {
|
| +export function normalizeSourceFile(f: ts.SourceFile, fc: FacadeConverter) {
|
| let modules: {[name: string]: ts.ModuleDeclaration} = {};
|
|
|
| // Merge top level modules.
|
| @@ -120,7 +210,7 @@ export function normalizeSourceFile(f: ts.SourceFile) {
|
| if (statement.kind !== ts.SyntaxKind.ModuleDeclaration) continue;
|
| let moduleDecl = <ts.ModuleDeclaration>statement;
|
| let name = moduleDecl.name.text;
|
| - if (modules.hasOwnProperty(name)) {
|
| + if (Object.hasOwnProperty.call(modules, name)) {
|
| let srcBody = modules[name].body;
|
| let srcBodyBlock: ts.ModuleBlock;
|
|
|
| @@ -162,13 +252,78 @@ export function normalizeSourceFile(f: ts.SourceFile) {
|
| declaration: ts.VariableDeclaration) {
|
| if (declaration.name.kind === ts.SyntaxKind.Identifier) {
|
| let name: string = (<ts.Identifier>(declaration.name)).text;
|
| - if (classes.hasOwnProperty(name)) {
|
| + let existingClass = Object.hasOwnProperty.call(classes, name);
|
| + let hasConstructor = false;
|
| + if (declaration.type) {
|
| + let type: ts.TypeNode = declaration.type;
|
| + if (type.kind === ts.SyntaxKind.TypeLiteral) {
|
| + let literal = <ts.TypeLiteralNode>type;
|
| + hasConstructor = literal.members.some((member: ts.Node) => {
|
| + return member.kind === ts.SyntaxKind.ConstructSignature;
|
| + });
|
| + } else if (type.kind === ts.SyntaxKind.TypeReference) {
|
| + // Handle interfaces with constructors. As Dart does not support calling arbitrary
|
| + // functions like constructors we need to upgrade the interface to be a class
|
| + // so we call invoke the constructor on the interface class.
|
| + // Example typescript library definition matching this pattern:
|
| + //
|
| + // interface XStatic {
|
| + // new (a: string, b): XStatic;
|
| + // foo();
|
| + // }
|
| + //
|
| + // declare var X: XStatic;
|
| + //
|
| + // In JavaScript you could just write new X() and create an
|
| + // instance of XStatic. We don't
|
| + let typeRef = type as ts.TypeReferenceNode;
|
| + let typeName = typeRef.typeName;
|
| + let symbol = fc.tc.getSymbolAtLocation(typeName);
|
| + if (symbol == null) return;
|
| + let decl = fc.getSymbolDeclaration(symbol, typeName);
|
| + if (decl == null) return;
|
| + if (decl.kind !== ts.SyntaxKind.InterfaceDeclaration) return;
|
| + let interfaceDecl = decl as base.ExtendedInterfaceDeclaration;
|
| + if (!interfaceDecl.members.some(
|
| + (member) => { return member.kind === ts.SyntaxKind.ConstructSignature; }))
|
| + return;
|
| +
|
| + if (interfaceDecl.classLikeVariableDeclaration == null) {
|
| + // We could add extra logic to be safer such as only infering that variable names
|
| + // are class like for cases where variable names are UpperCamelCase matching JS
|
| + // conventions that a variable is a Class definition.
|
| + interfaceDecl.classLikeVariableDeclaration = declaration;
|
| + }
|
| + }
|
| + }
|
| +
|
| + if (existingClass || hasConstructor) {
|
| + if (!existingClass) {
|
| + // Create a stub existing class to upgrade the object literal to if there is not an
|
| + // existing class with the same name.
|
| + let clazz = <ts.ClassDeclaration>ts.createNode(ts.SyntaxKind.ClassDeclaration);
|
| + base.copyLocation(declaration, clazz);
|
| + clazz.name = declaration.name as ts.Identifier;
|
| + clazz.members = <ts.NodeArray<ts.ClassElement>>[];
|
| + base.copyLocation(declaration, clazz.members);
|
| + replaceNode(n, clazz);
|
| + classes[name] = clazz;
|
| + }
|
| +
|
| let existing = classes[name];
|
| + if (existing.kind === ts.SyntaxKind.InterfaceDeclaration) {
|
| + let interfaceDecl = existing as base.ExtendedInterfaceDeclaration;
|
| + // It is completely safe to assume that we know the precise class like variable
|
| + // declaration for the interface in this case as they have the same exact name.
|
| + interfaceDecl.classLikeVariableDeclaration = declaration;
|
| + }
|
| 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);
|
| + if (existingClass) {
|
| + removeNode(n);
|
| + }
|
| let literal = <ts.TypeLiteralNode>type;
|
| literal.members.forEach((member: ts.Node) => {
|
| switch (member.kind) {
|
| @@ -249,37 +404,29 @@ export function normalizeSourceFile(f: ts.SourceFile) {
|
| }
|
| }
|
|
|
| - 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);
|
| + function replaceInArray(nodes: ts.NodeArray<ts.Node>, v: ts.Node, replacement: ts.Node) {
|
| + for (let i = 0, len = nodes.length; i < len; ++i) {
|
| + if (nodes[i] === v) {
|
| + nodes[i] = replacement;
|
| + break;
|
| }
|
| - 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 replaceNode(n: ts.Node, replacement: ts.Node) {
|
| + let parent = n.parent;
|
| + replacement.parent = parent;
|
| + switch (parent.kind) {
|
| + case ts.SyntaxKind.ModuleBlock:
|
| + let block = <ts.ModuleBlock>parent;
|
| + replaceInArray(block.statements, n, replacement);
|
| + break;
|
| + case ts.SyntaxKind.SourceFile:
|
| + let sourceFile = <ts.SourceFile>parent;
|
| + replaceInArray(sourceFile.statements, n, replacement);
|
| + break;
|
| + default:
|
| + throw 'replaceNode not implemented for kind:' + parent.kind;
|
| }
|
| }
|
|
|
| @@ -291,7 +438,7 @@ export function normalizeSourceFile(f: ts.SourceFile) {
|
| let name = classDecl.name.text;
|
| // TODO(jacobr): validate that the classes have consistent
|
| // modifiers, etc.
|
| - if (classes.hasOwnProperty(name)) {
|
| + if (Object.hasOwnProperty.call(classes, name)) {
|
| let existing = classes[name];
|
| (classDecl.members as Array<ts.ClassElement>).forEach((e: ts.ClassElement) => {
|
| (existing.members as Array<ts.ClassElement>).push(e);
|
| @@ -301,7 +448,6 @@ export function normalizeSourceFile(f: ts.SourceFile) {
|
| } else {
|
| classes[name] = classDecl;
|
| // Perform other class level post processing here.
|
| - makeCallableClassesImplementFunction(classDecl);
|
| }
|
| break;
|
| case ts.SyntaxKind.ModuleDeclaration:
|
|
|