UNPKG

ts2dart

Version:

Transpile TypeScript code to Dart

213 lines (199 loc) 8.22 kB
import ts = require('typescript'); import base = require('./base'); import ts2dart = require('./main'); import {FacadeConverter} from './facade_converter'; export default class CallTranspiler extends base.TranspilerBase { constructor(tr: ts2dart.Transpiler, private fc: FacadeConverter) { super(tr); } visitNode(node: ts.Node): boolean { switch (node.kind) { case ts.SyntaxKind.Block: // This is a bit ugly: to separate Declarations from Calls, this code has to special case // blocks that are actually constructor bodies. if (node.parent && node.parent.kind === ts.SyntaxKind.Constructor) { return this.visitConstructorBody(<ts.ConstructorDeclaration>node.parent); } return false; case ts.SyntaxKind.NewExpression: let newExpr = <ts.NewExpression>node; if (this.hasAncestor(node, ts.SyntaxKind.Decorator)) { // Constructor calls in annotations must be const constructor calls. this.emit('const'); } else if (this.fc.isInsideConstExpr(node)) { this.emit('const'); } else { // Some implementations can replace the `new` keyword. if (this.fc.shouldEmitNew(newExpr)) { this.emit('new'); } } if (this.fc.maybeHandleCall(newExpr)) break; this.visitCall(newExpr); break; case ts.SyntaxKind.CallExpression: let callExpr = <ts.CallExpression>node; if (this.fc.maybeHandleCall(callExpr)) break; if (this.maybeHandleSuperCall(callExpr)) break; this.visitCall(callExpr); break; case ts.SyntaxKind.SuperKeyword: this.emit('super'); break; default: return false; } return true; } private visitCall(c: ts.CallExpression) { if (c.expression.kind === ts.SyntaxKind.Identifier) { this.fc.visitTypeName(<ts.Identifier>c.expression); } else { this.visit(c.expression); } if (c.typeArguments) { // For DDC, emit generic method arguments in /* block comments */ // NB: Surprisingly, whitespace within the comment is significant here :-( // TODO(martinprobst): Remove once Dart natively supports generic methods. if (c.kind !== ts.SyntaxKind.NewExpression) this.emit('/*'); this.maybeVisitTypeArguments(c); if (c.kind !== ts.SyntaxKind.NewExpression) this.emitNoSpace('*/'); } this.emit('('); if (c.arguments && !this.handleNamedParamsCall(c)) { this.visitList(c.arguments); } this.emit(')'); } private handleNamedParamsCall(c: ts.CallExpression): boolean { // Preamble: This is all committed in the name of backwards compat with the traceur transpiler. // Terrible hack: transform foo(a, b, {c: d}) into foo(a, b, c: d), which is Dart's calling // syntax for named/optional parameters. An alternative would be to transform the method // declaration to take a plain object literal and destructure in the method, but then client // code written against Dart wouldn't get nice named parameters. if (c.arguments.length === 0) return false; let last = c.arguments[c.arguments.length - 1]; if (last.kind !== ts.SyntaxKind.ObjectLiteralExpression) return false; let objLit = <ts.ObjectLiteralExpression>last; if (objLit.properties.length === 0) return false; // Even worse: foo(a, b, {'c': d}) is considered to *not* be a named parameters call. let hasNonPropAssignments = objLit.properties.some( (p) => (p.kind !== ts.SyntaxKind.PropertyAssignment || (<ts.PropertyAssignment>p).name.kind !== ts.SyntaxKind.Identifier)); if (hasNonPropAssignments) return false; let len = c.arguments.length - 1; this.visitList(c.arguments.slice(0, len)); if (len) this.emit(','); let props = objLit.properties; for (let i = 0; i < props.length; i++) { let prop = <ts.PropertyAssignment>props[i]; this.emit(base.ident(prop.name)); this.emit(':'); this.visit(prop.initializer); if (i < objLit.properties.length - 1) this.emit(','); } return true; } /** * Handles constructor initializer lists and bodies. * * <p>Dart's super() ctor calls have to be moved to the constructors initializer list, and `const` * constructors must be completely empty, only assigning into fields through the initializer list. * The code below finds super() calls and handles const constructors, marked with the special * `@CONST` annotation on the class. * * <p>Not emitting super() calls when traversing the ctor body is handled by maybeHandleSuperCall * below. */ private visitConstructorBody(ctor: ts.ConstructorDeclaration): boolean { let body = ctor.body; if (!body) return false; let errorAssignmentsSuper = 'const constructors can only contain assignments and super calls'; let errorThisAssignment = 'assignments in const constructors must assign into this.'; let parent = <base.ClassLike>ctor.parent; let parentIsConst = this.fc.isConstClass(parent); let superCall: ts.CallExpression; let expressions: ts.Expression[] = []; // Find super() calls and (if in a const ctor) collect assignment expressions (not statements!) body.statements.forEach((stmt) => { if (stmt.kind !== ts.SyntaxKind.ExpressionStatement) { if (parentIsConst) this.reportError(stmt, errorAssignmentsSuper); return; } let nestedExpr = (<ts.ExpressionStatement>stmt).expression; // super() call? if (nestedExpr.kind === ts.SyntaxKind.CallExpression) { let callExpr = <ts.CallExpression>nestedExpr; if (callExpr.expression.kind !== ts.SyntaxKind.SuperKeyword) { if (parentIsConst) this.reportError(stmt, errorAssignmentsSuper); return; } superCall = callExpr; return; } // this.x assignment? if (parentIsConst) { // Check for assignment. if (nestedExpr.kind !== ts.SyntaxKind.BinaryExpression) { this.reportError(nestedExpr, errorAssignmentsSuper); return; } let binExpr = <ts.BinaryExpression>nestedExpr; if (binExpr.operatorToken.kind !== ts.SyntaxKind.EqualsToken) { this.reportError(binExpr, errorAssignmentsSuper); return; } // Check for 'this.' if (binExpr.left.kind !== ts.SyntaxKind.PropertyAccessExpression) { this.reportError(binExpr, errorThisAssignment); return; } let lhs = <ts.PropertyAccessExpression>binExpr.left; if (lhs.expression.kind !== ts.SyntaxKind.ThisKeyword) { this.reportError(binExpr, errorThisAssignment); return; } let ident = lhs.name; binExpr.left = ident; expressions.push(nestedExpr); } }); let hasInitializerExpr = expressions.length > 0; if (hasInitializerExpr) { // Write out the assignments. this.emit(':'); this.visitList(expressions); } if (superCall) { this.emit(hasInitializerExpr ? ',' : ':'); this.emit('super ('); if (!this.handleNamedParamsCall(superCall)) { this.visitList(superCall.arguments); } this.emit(')'); } if (parentIsConst) { // Const ctors don't have bodies. this.emit(';'); return true; // completely handled. } else { return false; } } /** * Checks whether `callExpr` is a super() call that should be ignored because it was already * handled by `maybeEmitSuperInitializer` above. */ private maybeHandleSuperCall(callExpr: ts.CallExpression): boolean { if (callExpr.expression.kind !== ts.SyntaxKind.SuperKeyword) return false; // Sanity check that there was indeed a ctor directly above this call. let exprStmt = callExpr.parent; let ctorBody = exprStmt.parent; let ctor = ctorBody.parent; if (ctor.kind !== ts.SyntaxKind.Constructor) { this.reportError(callExpr, 'super calls must be immediate children of their constructors'); return false; } this.emit('/* super call moved to initializer */'); return true; } }