angular/tools/transpiler/src/codegeneration/ClassTransformer.js
Caitlin Potter 9d21a6f40d chore(package.json): upgrade traceur to v0.0.87
Fix in source-map test to follow through the sourcemap chain.
2015-03-26 18:37:03 -07:00

246 lines
7.5 KiB
JavaScript

import {ParseTreeTransformer} from './ParseTreeTransformer';
import {
BINARY_EXPRESSION,
CALL_EXPRESSION,
IDENTIFIER_EXPRESSION,
MEMBER_EXPRESSION,
PROPERTY_METHOD_ASSIGNMENT,
SUPER_EXPRESSION,
THIS_EXPRESSION
} from 'traceur/src/syntax/trees/ParseTreeType';
import {EQUAL} from 'traceur/src/syntax/TokenType';
import {CONSTRUCTOR} from 'traceur/src/syntax/PredefinedName';
import {propName} from 'traceur/src/staticsemantics/PropName';
import {
BinaryExpression,
BindingIdentifier,
FormalParameterList,
FunctionBody,
IdentifierExpression
} from 'traceur/src/syntax/trees/ParseTrees';
import {
PropertyConstructorAssignment,
ImplementsDeclaration
} from '../syntax/trees/ParseTrees';
/**
* Transforms class declaration:
* - rename constructor to the name of the class (default Dart constructor),
* - class fields are extracted from `this.field = expression;` in the ctor body,
* - `@CONST` annotations on the ctor result in a const constructor & final fields in Dart,
* - const constructor body is converted to an initializerList
*/
export class ClassTransformer extends ParseTreeTransformer {
constructor(idGenerator, reporter) {
super(idGenerator);
this.reporter_ = reporter;
}
/**
* @param {ClassDeclaration} tree
* @returns {ParseTree}
*/
transformClassDeclaration(tree) {
var className = tree.name.identifierToken.toString();
var argumentTypesMap = {};
var fields = [];
var isConst;
var hasConstructor = false;
var that = this;
tree.elements.forEach(function(elementTree, index) {
if (elementTree.type === PROPERTY_METHOD_ASSIGNMENT &&
!elementTree.isStatic &&
propName(elementTree) === CONSTRUCTOR) {
hasConstructor = true;
isConst = elementTree.annotations.some(that._isConstAnnotation);
// Store constructor argument types,
// so that we can use them to set the types of simple-assigned fields.
elementTree.parameterList.parameters.forEach(function(p) {
var binding = p.parameter.binding;
if (binding && binding.identifierToken) {
argumentTypesMap[binding.identifierToken.value] = p.typeAnnotation;
}
});
// Rename "constructor" to the class name.
elementTree.name.literalToken.value = className;
// Compute the initializer list
var initializerList = [];
var superCall = that._extractSuperCall(elementTree.body);
if (isConst) {
initializerList = that._extractFieldInitializers(elementTree.body);
if (elementTree.body.statements.length > 0) {
that.reporter_.reportError(
elementTree.location,
'Const constructor body can only contain field initialization & super call');
}
}
if (superCall) initializerList.push(superCall);
// Replace the `PROPERTY_METHOD_ASSIGNMENT` with a Dart specific
// `PROPERTY_CONSTRUCTOR_ASSIGNMENT`
tree.elements[index] = new PropertyConstructorAssignment(
elementTree.location,
elementTree.isStatic,
elementTree.functionKind,
elementTree.name,
elementTree.parameterList,
elementTree.typeAnnotation,
elementTree.annotations,
elementTree.body,
isConst,
initializerList
);
}
});
// If no constructor exists then look to see if we should create a const constructor
// when the class is annotated with @CONST.
if (!hasConstructor) {
let constClassAnnotation = this._extractConstAnnotation(tree);
if (constClassAnnotation !== null) {
tree.elements = [(new PropertyConstructorAssignment(
constClassAnnotation.location,
false,
null,
tree.name,
new FormalParameterList(constClassAnnotation.location, []),
null,
[constClassAnnotation],
new FunctionBody(constClassAnnotation.location, []),
true,
[]
))].concat(tree.elements);
}
}
let implementsAnnotation = this._extractImplementsAnnotation(tree);
if (implementsAnnotation !== null) {
tree.implements = new ImplementsDeclaration(
implementsAnnotation.location,
implementsAnnotation.args.args);
}
// Add the field definitions to the beginning of the class.
tree.elements = fields.concat(tree.elements);
if (isConst) {
tree.elements.forEach(function(element) {
element.isFinal = true;
});
}
return super.transformClassDeclaration(tree);
}
/**
* Extract field initialization (`this.field = <expression>;`) from the body of the constructor.
* The init statements are removed from the body statements and returned as an array.
*/
_extractFieldInitializers(body) {
var statements = [];
var fieldInitializers = [];
body.statements.forEach(function(statement) {
var exp = statement.expression;
if (exp.type === BINARY_EXPRESSION &&
exp.operator.type === EQUAL &&
exp.left.type === MEMBER_EXPRESSION &&
exp.left.operand.type === THIS_EXPRESSION) {
// `this.field = exp` -> `field = exp`
// todo(vicb): check for `this.` on rhs, not allowed in Dart
// -> remove if possible (arguments), throw otherwise.
var fieldName = exp.left.memberName.value;
fieldInitializers.push(new BinaryExpression(
statement.location,
new IdentifierExpression(statement.location, fieldName),
EQUAL,
exp.right
));
} else {
statements.push(statement);
}
});
body.statements = statements;
return fieldInitializers;
}
/**
* Extract the super call (`super(<arg list>)`) from the body of the constructor.
* When found the super call statement is removed from the body statements and returned.
*/
_extractSuperCall(body) {
var statements = [];
var superCall = null;
body.statements.forEach(function (statement) {
if (statement.expression &&
statement.expression.type === CALL_EXPRESSION &&
statement.expression.operand.type === SUPER_EXPRESSION) {
superCall = statement.expression;
} else {
statements.push(statement);
}
});
body.statements = statements;
return superCall;
}
/**
* Extract the @IMPLEMENTS annotation from the class annotations.
* When found the annotation is removed from the class annotations and returned.
*/
_extractImplementsAnnotation(tree) {
return this._extractAnnotation(tree, this._isImplementsAnnotation);
}
/**
* Extract the @CONST annotation from the class annotations.
* When found the annotation is removed from the class annotations and returned.
*/
_extractConstAnnotation(tree) {
return this._extractAnnotation(tree, this._isConstAnnotation);
}
_extractAnnotation(tree, predicate) {
var annotations = [];
var extractedAnnotation = null;
tree.annotations.forEach((annotation) => {
if (predicate(annotation)) {
extractedAnnotation = annotation;
} else {
annotations.push(annotation);
}
});
tree.annotations = annotations;
return extractedAnnotation;
}
/**
* Returns true if the annotation is @CONST
*/
_isConstAnnotation(annotation) {
return annotation.name.identifierToken.value === 'CONST';
}
/**
* Returns true if the annotation is @IMPLEMENTS
*/
_isImplementsAnnotation(annotation) {
return annotation.name.identifierToken.value === 'IMPLEMENTS';
}
}