traceur
Version:
ES6 to ES5 compiler
324 lines (274 loc) • 8.99 kB
JavaScript
// Copyright 2014 Traceur Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {
AnonBlock,
Catch,
FunctionBody,
ForInStatement,
ForOfStatement,
ForStatement,
VariableDeclarationList,
VariableStatement
} from '../syntax/trees/ParseTrees.js';
import {
OBJECT_PATTERN,
VARIABLE_DECLARATION_LIST
} from '../syntax/trees/ParseTreeType.js';
import {ParseTreeTransformer} from './ParseTreeTransformer.js';
import {StringSet} from '../util/StringSet.js';
import {VAR} from '../syntax/TokenType.js';
import {
createAssignmentExpression,
createCommaExpression,
createExpressionStatement,
createIdentifierExpression as id,
createParenExpression,
createVariableDeclaration
} from './ParseTreeFactory.js';
import {prependStatements} from './PrependStatements.js';
/**
* Hoists variables to the top of the function body. This only transforms the
* current function scope. This does not yet handle destructuring so
* destructuring should have been transformed away earlier.
*
* function f() {
* foo();
* var x = 1, y, z = 2;
* for (var w in obj) {}
* }
*
* =>
*
* function f() {
* var x, y, z, w;
* foo();
* x = 1, z = 2;
* for (w in obj) {}
* }
*/
class HoistVariablesTransformer extends ParseTreeTransformer {
/**
* @param {boolean} shouldHoistFunctions Whether to also hoist function
* declarations.
*/
constructor(shouldHoistFunctions = false) {
super();
this.hoistedFunctions_ = [];
this.hoistedVariables_ = new StringSet();
this.keepBindingIdentifiers_ = false;
this.inBlockOrFor_ = false;
this.shouldHoistFunctions_ = shouldHoistFunctions;
}
transformFunctionBody(tree) {
let statements = this.transformList(tree.statements);
if (statements === tree.statements)
return tree;
statements = this.prependVariables(statements);
statements = this.prependFunctions(statements);
return new FunctionBody(tree.location, statements);
}
addVariable(name) {
this.hoistedVariables_.add(name);
}
addFunctionDeclaration(tree) {
this.hoistedFunctions_.push(tree);
}
hasVariables() {
return !this.hoistedVariables_.isEmpty();
}
hasFunctions() {
return this.hoistedFunctions_.length > 0;
}
getVariableNames() {
return this.hoistedVariables_.valuesAsArray();
}
getVariableStatement() {
if (!this.hasVariables())
return new AnonBlock(null, []);
let declarations = this.getVariableNames().map((name) => {
return createVariableDeclaration(name, null);
});
return new VariableStatement(null,
new VariableDeclarationList(null, VAR, declarations));
}
getFunctions() {
return this.hoistedFunctions_;
}
prependVariables(statements) {
if (!this.hasVariables())
return statements;
return prependStatements(statements, this.getVariableStatement());
}
prependFunctions(statements) {
if (!this.hasFunctions())
return statements;
return prependStatements(statements, this.getFunctionDeclarations());
}
transformVariableStatement(tree) {
let declarations = this.transformAny(tree.declarations);
if (declarations === tree.declarations)
return tree;
if (declarations === null)
return new AnonBlock(null, []);
// let/const are not hoisted. Just return a variable statement.
if (declarations.type === VARIABLE_DECLARATION_LIST)
return new VariableStatement(tree.location, declarations);
return createExpressionStatement(declarations);
}
transformVariableDeclaration(tree) {
let lvalue = this.transformAny(tree.lvalue);
let initializer = this.transformAny(tree.initializer);
if (initializer) {
let expression = createAssignmentExpression(lvalue, initializer);
if (lvalue.type === OBJECT_PATTERN)
expression = createParenExpression(expression);
return expression;
}
return null;
}
transformObjectPattern(tree) {
// AssignmentPatterns incorrectly uses BindingIdentifiers.
// https://github.com/google/traceur-compiler/issues/969
let keepBindingIdentifiers = this.keepBindingIdentifiers_;
this.keepBindingIdentifiers_ = true;
let transformed = super.transformObjectPattern(tree);
this.keepBindingIdentifiers_ = keepBindingIdentifiers;
return transformed;
}
transformArrayPattern(tree) {
// AssignmentPatterns incorrectly uses BindingIdentifiers.
// https://github.com/google/traceur-compiler/issues/969
let keepBindingIdentifiers = this.keepBindingIdentifiers_;
this.keepBindingIdentifiers_ = true;
let transformed = super.transformArrayPattern(tree);
this.keepBindingIdentifiers_ = keepBindingIdentifiers;
return transformed;
}
transformBindingIdentifier(tree) {
let idToken = tree.identifierToken;
this.addVariable(idToken.value);
if (this.keepBindingIdentifiers_)
return tree;
return id(idToken);
}
transformVariableDeclarationList(tree) {
// Hoist var declarations and top-level let/const declarations. If
// const-ness is ever enforced, note that all hoisted declarations become
// var declarations.
if (tree.declarationType === VAR || !this.inBlockOrFor_) {
let expressions = this.transformList(tree.declarations);
// Any var without an initializer becomes null in
// transformVariableDeclaration Remove these null trees now.
expressions = expressions.filter((tree) => tree);
if (expressions.length === 0)
return null;
if (expressions.length === 1)
return expressions[0];
return createCommaExpression(expressions);
}
// let/const - do not transform.
return tree;
}
transformCatch(tree) {
// Ensure that we do not transform the catch binding.
let catchBody = this.transformAny(tree.catchBody);
if (catchBody === tree.catchBody)
return tree;
return new Catch(tree.location, tree.binding, catchBody);
}
transformForInStatement(tree) {
return this.transformLoop_(tree, ForInStatement);
}
transformForOfStatement(tree) {
return this.transformLoop_(tree, ForOfStatement);
}
transformForOnStatement(tree) {
return this.transformLoop_(tree, ForOfStatement);
}
transformLoop_(tree, ctor) {
let initializer = this.transformLoopIninitaliser_(tree.initializer);
let collection = this.transformAny(tree.collection);
let body = this.transformAny(tree.body);
if (initializer === tree.initializer &&
collection === tree.collection &&
body === tree.body) {
return tree;
}
return new ctor(tree.location, initializer, collection, body);
}
transformLoopIninitaliser_(tree) {
if (tree.type !== VARIABLE_DECLARATION_LIST || tree.declarationType !== VAR)
return tree;
return this.transformAny(tree.declarations[0].lvalue);
}
transformForStatement(tree) {
let inBlockOrFor = this.inBlockOrFor_;
this.inBlockOrFor_ = true;
let initializer = this.transformAny(tree.initializer);
this.inBlockOrFor_ = inBlockOrFor;
let condition = this.transformAny(tree.condition);
let increment = this.transformAny(tree.increment);
let body = this.transformAny(tree.body);
if (initializer === tree.initializer &&
condition === tree.condition &&
increment === tree.increment &&
body === tree.body) {
return tree;
}
return new ForStatement(tree.location, initializer,
condition, increment, body);
}
transformBlock(tree) {
let inBlockOrFor = this.inBlockOrFor_;
this.inBlockOrFor_ = true;
tree = super.transformBlock(tree);
this.inBlockOrFor_ = inBlockOrFor;
return tree;
}
addMachineVariable(name) {
this.machineVariables_[name] = true;
}
transformClassDeclaration(tree) {
return tree;
}
transformClassExpression(tree) {
return tree;
}
transformFunctionDeclaration(tree) {
if (this.shouldHoistFunctions_) {
this.addFunctionDeclaration(tree);
return new AnonBlock(null, []);
}
return tree;
}
transformFunctionExpression(tree) {
return tree;
}
transformGetAccessor(tree) {
return tree;
}
transformSetAccessor(tree) {
return tree;
}
transformMethod(tree) {
return tree;
}
transformArrowFunction(tree) {
return tree;
}
transformComprehensionFor(tree) {
return tree;
}
}
export default HoistVariablesTransformer;