ts2dart
Version:
Transpile TypeScript code to Dart
213 lines (199 loc) • 8.22 kB
text/typescript
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;
}
}