@philpl/buble
Version:
The blazing fast, batteries-included ES2015 compiler
111 lines (89 loc) • 3.17 kB
JavaScript
import Node from '../../Node.js';
export default class LoopStatement extends Node {
findScope(functionScope) {
return functionScope || !this.createdScope
? this.parent.findScope(functionScope)
: this.body.scope;
}
initialise(transforms) {
this.body.createScope();
this.createdScope = true;
// this is populated as and when reassignments occur
this.reassigned = Object.create(null);
this.aliases = Object.create(null);
this.thisRefs = [];
super.initialise(transforms);
if (transforms.letConst) {
// see if any block-scoped declarations are referenced
// inside function expressions
const names = Object.keys(this.body.scope.declarations);
let i = names.length;
while (i--) {
const name = names[i];
const declaration = this.body.scope.declarations[name];
let j = declaration.instances.length;
while (j--) {
const instance = declaration.instances[j];
const nearestFunctionExpression = instance.findNearest(/Function/);
if (
nearestFunctionExpression &&
nearestFunctionExpression.depth > this.depth
) {
this.shouldRewriteAsFunction = true;
for (const node of this.thisRefs) {
node.alias = node.alias || node.findLexicalBoundary().getThisAlias();
}
break;
}
}
if (this.shouldRewriteAsFunction) break;
}
}
}
transpile(code, transforms) {
const needsBlock =
this.type != 'ForOfStatement' &&
(this.body.type !== 'BlockStatement' ||
(this.body.type === 'BlockStatement' && this.body.synthetic));
if (this.shouldRewriteAsFunction) {
const i0 = this.getIndentation();
const i1 = i0 + code.getIndentString();
const argString = this.args ? ` ${this.args.join(', ')} ` : '';
const paramString = this.params ? ` ${this.params.join(', ')} ` : '';
const functionScope = this.findScope(true);
const loop = functionScope.createIdentifier('loop');
const before =
`var ${loop} = function (${paramString}) ` +
(this.body.synthetic ? `{\n${i0}${code.getIndentString()}` : '');
const after = (this.body.synthetic ? `\n${i0}}` : '') + `;\n\n${i0}`;
code.prependRight(this.body.start, before);
code.appendLeft(this.body.end, after);
code.move(this.start, this.body.start, this.body.end);
if (this.canBreak || this.canReturn) {
const returned = functionScope.createIdentifier('returned');
let insert = `{\n${i1}var ${returned} = ${loop}(${argString});\n`;
if (this.canBreak)
insert += `\n${i1}if ( ${returned} === 'break' ) break;`;
if (this.canReturn)
insert += `\n${i1}if ( ${returned} ) return ${returned}.v;`;
insert += `\n${i0}}`;
code.prependRight(this.body.end, insert);
} else {
const callExpression = `${loop}(${argString});`;
if (this.type === 'DoWhileStatement') {
code.overwrite(
this.start,
this.body.start,
`do {\n${i1}${callExpression}\n${i0}}`
);
} else {
code.prependRight(this.body.end, callExpression);
}
}
} else if (needsBlock) {
code.appendLeft(this.body.start, '{ ');
code.prependRight(this.body.end, ' }');
}
super.transpile(code, transforms);
}
}