Repository: Rich-Harris/buble (js)

Issue:
bug: arrow function with computed property producing wrong result
Edited once bug was found. See bug in following comment.

~~This is just a case of suboptimal code generation.~~ The temporary variable used by computed properties within an arrow function is not in the correct scope which can lead to slower code:
```
$ bin/buble -v
Bublé version 0.18.0
```
```
$ echo 'var f = (x) => ({ [x]: 1 });' | bin/buble | uglifyjs -b
var obj;

var f = function(x) {
    return obj = {}, obj[x] = 1, obj;
};
```
compared to:
```
$ buble -v
Bublé version 0.15.2
```
```
$ echo 'var f = (x) => ({ [x]: 1 });' | buble | uglifyjs -b
var f = function(x) {
    return obj = {}, obj[x] = 1, obj;
    var obj;
};
```
and
```
$ echo 'var f = (x) => ({ [x]: 1 });' | babel -L
"use strict";

var f = function f(x) {
  var _ref;

  return _ref = {}, _ref[x] = 1, _ref;
};
```

Pull request description:
Fix which scope is used for declaring helpers for computed properties.
Closes #57. I also converted `CallExpression` and `AssignmentExpression` to my `Scope#createDeclaration` in two separate commits.

Patch:
diff --git a/src/program/BlockStatement.js b/src/program/BlockStatement.js
index 0c887ce..b1d8959 100644
--- a/src/program/BlockStatement.js
+++ b/src/program/BlockStatement.js
@@ -16,7 +16,8 @@ export default class BlockStatement extends Node {
 		this.isFunctionBlock = this.parentIsFunction || this.parent.type === 'Root';
 		this.scope = new Scope({
 			block: !this.isFunctionBlock,
-			parent: this.parent.findScope(false)
+			parent: this.parent.findScope(false),
+			declare: id => this.createdDeclarations.push(id)
 		});
 
 		if (this.parentIsFunction) {
@@ -211,12 +212,6 @@ export default class BlockStatement extends Node {
 		});
 	}
 
-	declareIdentifier(name) {
-		const id = this.scope.createIdentifier(name);
-		this.createdDeclarations.push(id);
-		return id;
-	}
-
 	transpileParameters(code, transforms, indentation, introStatementGenerators) {
 		const params = this.parent.params;
 
diff --git a/src/program/Program.js b/src/program/Program.js
index 5a6065d..534d00c 100644
--- a/src/program/Program.js
+++ b/src/program/Program.js
@@ -18,6 +18,15 @@ export default function Program(source, ast, transforms, options) {
 	wrap((this.body = ast), this);
 	this.body.__proto__ = BlockStatement.prototype;
 
+	this.templateLiteralQuasis = Object.create(null);
+	for (let i = 0; i < this.body.body.length; ++i) {
+		if (!this.body.body[i].directive) {
+			this.prependAt = this.body.body[i].start;
+			break;
+		}
+	}
+	this.objectWithoutPropertiesHelper = null;
+
 	this.indentExclusionElements = [];
 	this.body.initialise(transforms);
 
@@ -49,5 +58,17 @@ Program.prototype = {
 
 	findScope() {
 		return null;
+	},
+
+	getObjectWithoutPropertiesHelper(code) {
+		if (!this.objectWithoutPropertiesHelper) {
+			this.objectWithoutPropertiesHelper = this.body.scope.createIdentifier('objectWithoutProperties');
+			code.prependLeft(this.prependAt, `function ${this.objectWithoutPropertiesHelper} (obj, keys) { ` +
+				`var target = {}; for (var k in obj) ` +
+				`if (Object.prototype.hasOwnProperty.call(obj, k) && exclude.indexOf(k) === -1) ` +
+				`target[k] = obj[k]; return target; }\n`
+			);
+		}
+		return this.objectWithoutPropertiesHelper;
 	}
 };
diff --git a/src/program/Scope.js b/src/program/Scope.js
index 18f1664..e3ad149 100644
--- a/src/program/Scope.js
+++ b/src/program/Scope.js
@@ -6,6 +6,7 @@ export default function Scope(options) {
 
 	this.parent = options.parent;
 	this.isBlockScope = !!options.block;
+	this.createDeclarationCallback = options.declare;
 
 	let scope = this;
 	while (scope.isBlockScope) scope = scope.parent;
@@ -15,7 +16,7 @@ export default function Scope(options) {
 	this.declarations = Object.create(null);
 	this.references = Object.create(null);
 	this.blockScopedDeclarations = this.isBlockScope ? null : Object.create(null);
-	this.aliases = this.isBlockScope ? null : Object.create(null);
+	this.aliases = Object.create(null);
 }
 
 Scope.prototype = {
@@ -94,6 +95,12 @@ Scope.prototype = {
 		return name;
 	},
 
+	createDeclaration(base) {
+		const id = this.createIdentifier(base);
+		this.createDeclarationCallback(id);
+		return id;
+	},
+
 	findDeclaration(name) {
 		return (
 			this.declarations[name] ||
diff --git a/src/program/types/AssignmentExpression.js b/src/program/types/AssignmentExpression.js
index a80dc60..f337a86 100644
--- a/src/program/types/AssignmentExpression.js
+++ b/src/program/types/AssignmentExpression.js
@@ -35,8 +35,7 @@ export default class AssignmentExpression extends Node {
 
 	transpileDestructuring(code) {
 		const scope = this.findScope(true);
-		const assign = scope.createIdentifier('assign');
-		const temporaries = [assign];
+		const assign = scope.createDeclaration('assign');
 
 		const start = this.start;
 
@@ -84,9 +83,8 @@ export default class AssignmentExpression extends Node {
 				} else {
 					code.remove(pattern.left.end, pattern.right.start);
 
-					const target = scope.createIdentifier('temp');
+					const target = scope.createDeclaration('temp');
 					let source = ref;
-					temporaries.push(target);
 					if (!mayDuplicate) {
 						write(`, ${target} = ${ref}`);
 						source = target;
@@ -104,8 +102,7 @@ export default class AssignmentExpression extends Node {
 					code.remove(elements[0].end, pattern.end);
 				} else {
 					if (!mayDuplicate) {
-						const temp = scope.createIdentifier('array');
-						temporaries.push(temp);
+						const temp = scope.createDeclaration('array');
 						write(`, ${temp} = ${ref}`);
 						ref = temp;
 					}
@@ -141,8 +138,7 @@ export default class AssignmentExpression extends Node {
 					code.remove(prop.end, pattern.end);
 				} else {
 					if (!mayDuplicate) {
-						const temp = scope.createIdentifier('obj');
-						temporaries.push(temp);
+						const temp = scope.createDeclaration('obj');
 						write(`, ${temp} = ${ref}`);
 						ref = temp;
 					}
@@ -180,12 +176,6 @@ export default class AssignmentExpression extends Node {
 			// destructuring is part of an expression - need an rvalue
 			code.prependRight(start, `${text}, ${assign})`);
 		}
-
-		const statement = this.findNearest(/(?:Statement|Declaration)$/);
-		code.appendLeft(
-			statement.start,
-			`var ${temporaries.join(', ')};\n${statement.getIndentation()}`
-		);
 	}
 
 	transpileExponentiation(code) {
@@ -223,24 +213,24 @@ export default class AssignmentExpression extends Node {
 					? getAlias(left.property.name)
 					: left.property.name;
 			} else {
-				property = scope.createIdentifier('property');
+				property = scope.createDeclaration('property');
 				needsPropertyVar = true;
 			}
 
 			if (left.object.type === 'Identifier') {
 				object = getAlias(left.object.name);
 			} else {
-				object = scope.createIdentifier('object');
+				object = scope.createDeclaration('object');
 				needsObjectVar = true;
 			}
 
 			if (left.start === statement.start) {
 				if (needsObjectVar && needsPropertyVar) {
-					code.prependRight(statement.start, `var ${object} = `);
+					code.prependRight(statement.start, `${object} = `);
 					code.overwrite(
 						left.object.end,
 						left.property.start,
-						`;\n${i0}var ${property} = `
+						`;\n${i0}${property} = `
 					);
 					code.overwrite(
 						left.property.end,
@@ -248,11 +238,11 @@ export default class AssignmentExpression extends Node {
 						`;\n${i0}${object}[${property}]`
 					);
 				} else if (needsObjectVar) {
-					code.prependRight(statement.start, `var ${object} = `);
+					code.prependRight(statement.start, `${object} = `);
 					code.appendLeft(left.object.end, `;\n${i0}`);
 					code.appendLeft(left.object.end, object);
 				} else if (needsPropertyVar) {
-					code.prependRight(left.property.start, `var ${property} = `);
+					code.prependRight(left.property.start, `${property} = `);
 					code.appendLeft(left.property.end, `;\n${i0}`);
 					code.move(left.property.start, left.property.end, this.start);
 
@@ -261,17 +251,6 @@ export default class AssignmentExpression extends Node {
 					code.remove(left.property.end, left.end);
 				}
 			} else {
-				let declarators = [];
-				if (needsObjectVar) declarators.push(object);
-				if (needsPropertyVar) declarators.push(property);
-
-				if (declarators.length) {
-					code.prependRight(
-						statement.start,
-						`var ${declarators.join(', ')};\n${i0}`
-					);
-				}
-
 				if (needsObjectVar && needsPropertyVar) {
 					code.prependRight(left.start, `( ${object} = `);
 					code.overwrite(
diff --git a/src/program/types/CallExpression.js b/src/program/types/CallExpression.js
index 3f6b2c3..3671f52 100644
--- a/src/program/types/CallExpression.js
+++ b/src/program/types/CallExpression.js
@@ -57,17 +57,10 @@ export default class CallExpression extends Node {
 					if (this.callee.object.type === 'Identifier') {
 						context = this.callee.object.name;
 					} else {
-						context = this.findScope(true).createIdentifier('ref');
+						context = this.findScope(true).createDeclaration('ref');
 						const callExpression = this.callee.object;
-						const enclosure = callExpression.findNearest(/Function/);
-						const block = enclosure
-							? enclosure.body.body
-							: callExpression.findNearest(/^Program$/).body;
-						const lastStatementInBlock = block[block.length - 1];
-						const i0 = lastStatementInBlock.getIndentation();
 						code.prependRight(callExpression.start, `(${context} = `);
 						code.appendLeft(callExpression.end, `)`);
-						code.appendLeft(lastStatementInBlock.end, `\n${i0}var ${context};`);
 					}
 				} else {
 					context = 'void 0';
diff --git a/src/program/types/ClassDeclaration.js b/src/program/types/ClassDeclaration.js
index ddca8e7..d3563b8 100644
--- a/src/program/types/ClassDeclaration.js
+++ b/src/program/types/ClassDeclaration.js
@@ -31,10 +31,10 @@ export default class ClassDeclaration extends Node {
 				code.remove(this.parent.start, this.start);
 			}
 
-			let c = this.start
+			let c = this.start;
 			if (this.id) {
 				code.overwrite(c, this.id.start, 'var ');
-				c = this.id.end
+				c = this.id.end;
 			} else {
 				code.prependLeft(c, `var ${this.name}`);
 			}
diff --git a/src/program/types/NewExpression.js b/src/program/types/NewExpression.js
index 8d33c06..2df5314 100644
--- a/src/program/types/NewExpression.js
+++ b/src/program/types/NewExpression.js
@@ -1,5 +1,6 @@
 import Node from '../Node.js';
 import spread, { isArguments } from '../../utils/spread.js';
+import removeTrailingComma from '../../utils/removeTrailingComma.js';
 
 export default class NewExpression extends Node {
 	initialise(transforms) {
@@ -46,5 +47,8 @@ export default class NewExpression extends Node {
 		}
 
 		super.transpile(code, transforms);
+		if (this.arguments.length) {
+			removeTrailingComma(code, this.arguments[this.arguments.length - 1].end);
+		}
 	}
 }
diff --git a/src/program/types/ObjectExpression.js b/src/program/types/ObjectExpression.js
index 9afff13..489a9b2 100644
--- a/src/program/types/ObjectExpression.js
+++ b/src/program/types/ObjectExpression.js
@@ -132,7 +132,7 @@ export default class ObjectExpression extends Node {
 					firstSpreadProperty === null ||
 					firstComputedProperty < firstSpreadProperty
 				) {
-					name = this.findLexicalBoundary().declareIdentifier('obj');
+					name = this.findScope(true).createDeclaration('obj');
 
 					code.prependRight(this.start, `( ${name} = `);
 				} else name = null; // We don't actually need this variable
@@ -155,7 +155,7 @@ export default class ObjectExpression extends Node {
 					lastComputedProp = prop;
 
 					if (!name) {
-						name = this.findLexicalBoundary().declareIdentifier('obj');
+						name = this.findScope(true).createDeclaration('obj');
 
 						const propId = name + (prop.computed ? '' : '.');
 						code.appendRight(prop.start, `( ${name} = {}, ${propId}`);
diff --git a/src/program/types/TaggedTemplateExpression.js b/src/program/types/TaggedTemplateExpression.js
index 987aabe..b3d607e 100644
--- a/src/program/types/TaggedTemplateExpression.js
+++ b/src/program/types/TaggedTemplateExpression.js
@@ -22,14 +22,26 @@ export default class TaggedTemplateExpression extends Node {
 				.concat(this.quasi.quasis)
 				.sort((a, b) => a.start - b.start);
 
+			const program = this.program;
+			const rootScope = program.body.scope;
+
 			// insert strings at start
 			const templateStrings = this.quasi.quasis.map(quasi =>
 				JSON.stringify(quasi.value.cooked)
-			);
+			).join(', ');
+
+			let templateObject = this.program.templateLiteralQuasis[templateStrings];
+			if (!templateObject) {
+				templateObject = rootScope.createIdentifier('templateObject');
+				code.prependRight(this.program.prependAt, `var ${templateObject} = Object.freeze([${templateStrings}]);\n`);
+
+				this.program.templateLiteralQuasis[templateStrings] = templateObject;
+			}
+
 			code.overwrite(
 				this.tag.end,
 				ordered[0].start,
-				`([${templateStrings.join(', ')}]`
+				`(${templateObject}`
 			);
 
 			let lastIndex = ordered[0].start;
diff --git a/src/utils/destructure.js b/src/utils/destructure.js
index 0f620fd..cbd8668 100644
--- a/src/utils/destructure.js
+++ b/src/utils/destructure.js
@@ -128,12 +128,11 @@ function destructureObjectPattern(
 			value = scope.createIdentifier('rest');
 			const n = scope.createIdentifier('n');
 			statementGenerators.push((start, prefix, suffix) => {
+				const helper = prop.program.getObjectWithoutPropertiesHelper(code);
 				code.overwrite(
 					prop.start,
 					(c = prop.argument.start),
-					`${prefix}var ${value} = {}; for (var ${n} in ${ref}) if([${nonRestKeys.join(
-						', '
-					)}].indexOf(${n}) === -1) ${value}[${n}] = ${ref}[${n}]${suffix}`
+					(inline ? prefix : `${prefix}var `) + `${value} = ${helper}( ${ref}, [${nonRestKeys.join(', ')}] )${suffix}`
 				);
 				code.move(prop.start, c, start);
 			});
@@ -190,9 +189,9 @@ function handleProperty(
 				if (inline) {
 					code.prependRight(
 						node.right.start,
-						`${name} = ${value} === undefined ? `
+						`${name} = ${value}, ${name} = ${name} === void 0 ? `
 					);
-					code.appendLeft(node.right.end, ` : ${value}`);
+					code.appendLeft(node.right.end, ` : ${name}${suffix}`);
 				} else {
 					code.prependRight(
 						node.right.start,
@@ -233,14 +232,14 @@ function handleProperty(
 				statementGenerators.push((start, prefix, suffix) => {
 					// this feels a tiny bit hacky, but we can't do a
 					// straightforward appendLeft and keep correct order...
-					code.prependRight(node.start, `${prefix}var ${ref} = `);
+					code.prependRight(node.start, (inline ? '' : `${prefix}var `) + `${ref} = `);
 					code.overwrite(node.start, (c = node.start + 1), value);
 					code.appendLeft(c, suffix);
 
 					code.overwrite(
 						node.start,
 						(c = node.start + 1),
-						`${prefix}var ${ref} = ${value}${suffix}`
+						(inline ? '' : `${prefix}var `) + `${ref} = ${value}${suffix}`
 					);
 					code.move(node.start, c, start);
 				});
@@ -265,7 +264,7 @@ function handleProperty(
 				const ref = scope.createIdentifier(value);
 
 				statementGenerators.push((start, prefix, suffix) => {
-					code.prependRight(node.start, `${prefix}var ${ref} = `);
+					code.prependRight(node.start, (inline ? '' : `${prefix}var `) + `${ref} = `);
 					code.overwrite(node.start, (c = node.start + 1), value, {
 						contentOnly: true
 					});


Test patch:
diff --git a/test/samples/computed-properties.js b/test/samples/computed-properties.js
index d68001d..f7753f5 100644
--- a/test/samples/computed-properties.js
+++ b/test/samples/computed-properties.js
@@ -227,9 +227,11 @@ module.exports = [
 			foo => bar({[x - y]: obj});
 		`,
 		output: `
-			var obj$1;
+			!function(foo) {
+				var obj$1;
 
-			!function(foo) { return bar(( obj$1 = {}, obj$1[x - y] = obj, obj$1 )); };
+				return bar(( obj$1 = {}, obj$1[x - y] = obj, obj$1 ));
+			};
 		`
 	},
 
@@ -244,5 +246,18 @@ module.exports = [
 			var obj, obj$1;
  return ( obj$1 = {}, obj$1[key] = ( obj = {}, obj[key] = val, obj ), obj$1 ) })
 		`
+	},
+
+	{
+		description: 'Puts helper variables in correct scope',
+
+		input: `
+			((x) => {var obj = 2; console.log([{[x]: 1}, obj]);})(3);
+		`,
+		output: `
+			(function (x) {
+			var obj$1;
+var obj = 2; console.log([( obj$1 = {}, obj$1[x] = 1, obj$1 ), obj]);})(3);
+		`
 	}
 ];
diff --git a/test/samples/destructuring.js b/test/samples/destructuring.js
index 8ea25cb..fef5c75 100644
--- a/test/samples/destructuring.js
+++ b/test/samples/destructuring.js
@@ -343,8 +343,9 @@ module.exports = [
 			({ [FirstProp]: one, [SecondProp]: two = 'Too', 3: three, Fore: four } = x);
 		`,
 		output: `
-			var one, two, three, four;
 			var assign;
+
+			var one, two, three, four;
 			((assign = x, one = assign[FirstProp], two = assign[SecondProp], two = two === void 0 ? 'Too' : two, three = assign[3], four = assign.Fore));
 		`
 	},
@@ -468,9 +469,10 @@ module.exports = [
 			console.log(a, b, c, d);
 		`,
 		output: `
+			var assign, array, obj, temp;
+
 			var x = [1, 2, {r: 9}, {s: ["table"]} ];
 			var a, b, c, d;
-			var assign, array, obj, temp;
 			((assign = x, a = assign[0], array = assign.slice(1), b = array[1].r, obj = array[2], c = obj.r, c = c === void 0 ? "nothing" : c, temp = obj.s, temp = temp === void 0 ? "nope" : temp, d = temp[0]));
 			console.log(a, b, c, d);
 		`
@@ -511,6 +513,7 @@ module.exports = [
 			[x, y] = [1, 2];`,
 		output: `
 			var assign;
+
 			(assign = [1, 2], x = assign[0], y = assign[1]);`
 	},
 
@@ -521,6 +524,7 @@ module.exports = [
 			[x = 4, y] = [1, 2];`,
 		output: `
 			var assign;
+
 			(assign = [1, 2], x = assign[0], x = x === void 0 ? 4 : x, y = assign[1]);`
 	},
 
@@ -530,6 +534,7 @@ module.exports = [
 			[[x], y] = [1, 2];`,
 		output: `
 			var assign;
+
 			(assign = [1, 2], x = assign[0][0], y = assign[1]);`
 	},
 
@@ -540,6 +545,7 @@ module.exports = [
 			[[x, z], y] = [1, 2];`,
 		output: `
 			var assign, array;
+
 			(assign = [1, 2], array = assign[0], x = array[0], z = array[1], y = assign[1]);`
 	},
 
@@ -550,6 +556,7 @@ module.exports = [
 			[[x] = [], y] = [1, 2];`,
 		output: `
 			var assign, temp;
+
 			(assign = [1, 2], temp = assign[0], temp = temp === void 0 ? [] : temp, x = temp[0], y = assign[1]);`
 	},
 
@@ -559,6 +566,7 @@ module.exports = [
 			[x, y.z] = [1, 2];`,
 		output: `
 			var assign;
+
 			(assign = [1, 2], x = assign[0], y.z = assign[1]);`
 	},
 
@@ -568,6 +576,7 @@ module.exports = [
 			[x, y.z = 3] = [1, 2];`,
 		output: `
 			var assign, temp;
+
 			(assign = [1, 2], x = assign[0], temp = assign[1], temp = temp === void 0 ? 3 : temp, y.z = temp);`
 	},
 
@@ -577,6 +586,7 @@ module.exports = [
 			({x, y} = {x: 1});`,
 		output: `
 			var assign;
+
 			((assign = {x: 1}, x = assign.x, y = assign.y));`
 	},
 
@@ -587,6 +597,7 @@ module.exports = [
 			({x, y: z} = {x: 1});`,
 		output: `
 			var assign;
+
 			((assign = {x: 1}, x = assign.x, z = assign.y));`
 	},
 
@@ -596,6 +607,7 @@ module.exports = [
 			({x, y: {z}} = {x: 1});`,
 		output: `
 			var assign;
+
 			((assign = {x: 1}, x = assign.x, z = assign.y.z));`
 	},
 
@@ -606,6 +618,7 @@ module.exports = [
 			({x, y = 4} = {x: 1});`,
 		output: `
 			var assign;
+
 			((assign = {x: 1}, x = assign.x, y = assign.y, y = y === void 0 ? 4 : y));`
 	},
 
@@ -615,6 +628,7 @@ module.exports = [
 			({x, y: {z, q}} = {x: 1});`,
 		output: `
 			var assign, obj;
+
 			((assign = {x: 1}, x = assign.x, obj = assign.y, z = obj.z, q = obj.q));`
 	},
 
@@ -624,6 +638,7 @@ module.exports = [
 			({x, y: {z}} = {x: 1});`,
 		output: `
 			var assign;
+
 			((assign = {x: 1}, x = assign.x, z = assign.y.z));`
 	},
 
@@ -635,8 +650,9 @@ module.exports = [
 				baz();
 			}`,
 		output: `
-			foo();
 			var assign;
+
+			foo();
 			if ( bar((assign = [1, 2], x = assign[0], y = assign[1], assign)) ) {
 				baz();
 			}`
@@ -651,6 +667,7 @@ module.exports = [
 		output: `
 			function foo() {
 				var assign;
+
 				(assign = [1, 2], x = assign[0], y = assign[1]);
 			}`
 	},
@@ -728,15 +745,17 @@ module.exports = [
 			console.log( [ a, b ] = c );
 		`,
 		output: `
+			var assign;
+
 			var Point = function Point () {};
 
 			Point.prototype.set = function set ( array ) {
-				var assign;
-					return (assign = array, this.x = assign[0], this.y = assign[1], assign);
+					var assign;
+
+				return (assign = array, this.x = assign[0], this.y = assign[1], assign);
 			};
 
 			var a, b, c = [ 1, 2, 3 ];
-			var assign;
 			console.log( (assign = c, a = assign[0], b = assign[1], assign) );
 		`
 	},
@@ -755,15 +774,17 @@ module.exports = [
 			[ a, b ] = c;
 		`,
 		output: `
+			var assign;
+
 			var Point = function Point () {};
 
 			Point.prototype.set = function set ( array ) {
-				var assign;
-					(assign = array, this.x = assign[0], this.y = assign[1]);
+					var assign;
+
+				(assign = array, this.x = assign[0], this.y = assign[1]);
 			};
 
 			var a, b, c = [ 1, 2, 3 ];
-			var assign;
 			(assign = c, a = assign[0], b = assign[1]);
 		`
 	},
@@ -782,5 +803,24 @@ module.exports = [
 			var a = ref.a;
 			var b = ref.b;
 		`
+	},
+
+	{
+		description: 'destructures within block scope',
+
+		input: `
+			if (true) {
+				let [[a, b]] = [[1, 2]];
+			}
+		`,
+
+		output: `
+			if (true) {
+				var ref = [[1, 2]];
+				var ref_0 = ref[0];
+				var a = ref_0[0];
+				var b = ref_0[1];
+			}
+		`
 	}
 ];
diff --git a/test/samples/exponentiation-operator.js b/test/samples/exponentiation-operator.js
index 813ca0d..7e75853 100644
--- a/test/samples/exponentiation-operator.js
+++ b/test/samples/exponentiation-operator.js
@@ -59,7 +59,9 @@ module.exports = [
 			foo.bar.baz **= y;`,
 
 		output: `
-			var object = foo.bar;
+			var object;
+
+			object = foo.bar;
 			object.baz = Math.pow( object.baz, y );`
 	},
 
@@ -82,7 +84,9 @@ module.exports = [
 			foo[ bar() ] **= y;`,
 
 		output: `
-			var property = bar();
+			var property;
+
+			property = bar();
 			foo[property] = Math.pow( foo[property], y );`
 	},
 
@@ -94,8 +98,10 @@ module.exports = [
 			foo[ bar() ][ baz() ] **= y;`,
 
 		output: `
-			var object = foo[ bar() ];
-			var property = baz();
+			var property, object;
+
+			object = foo[ bar() ];
+			property = baz();
 			object[property] = Math.pow( object[property], y );`
 	},
 
@@ -107,7 +113,8 @@ module.exports = [
 			var baz = 1, lolwut = foo[ bar() ][ baz * 2 ] **= y;`,
 
 		output: `
-			var object, property;
+			var property, object;
+
 			var baz = 1, lolwut = ( object = foo[ bar() ], property = baz * 2, object[property] = Math.pow( object[property], y ) );`
 	},
 
@@ -120,6 +127,7 @@ module.exports = [
 
 		output: `
 			var property;
+
 			var baz = 1, lolwut = ( property = bar(), foo[property] = Math.pow( foo[property], y ) );`
 	},
 
@@ -171,6 +179,7 @@ module.exports = [
 		`,
 		output: `
 			var property;
+
 			x=( property = bar(), a[property]=Math.pow( a[property], 2 ) );
 		`
 	}
diff --git a/test/samples/loops.js b/test/samples/loops.js
index 7f96945..6219326 100644
--- a/test/samples/loops.js
+++ b/test/samples/loops.js
@@ -543,7 +543,67 @@ module.exports = [
 				return { start: 10, end: 20 };
 			}
 
-			for ( var ref = range(), i = ref.start, end = ref.end === undefined ? 100 : ref.end; i < end; i += 1 ) {
+			for ( var ref = range(), i = ref.start, end = ref.end, end = end === void 0 ? 100 : end; i < end; i += 1 ) {
+				console.log( i );
+			}`
+	},
+
+	{
+		description:
+			'nested destructuring in variable declaration in for loop head',
+
+		input: `
+			for ( var { start: { a, b } } = range(); i < end; i += 1 ) {
+				console.log( i );
+			}`,
+
+		output: `
+			for ( var ref = range(), ref_start = ref.start, a = ref_start.a, b = ref_start.b; i < end; i += 1 ) {
+				console.log( i );
+			}`
+	},
+
+	{
+		description:
+			'nested array destructuring in variable declaration in for loop head',
+
+		input: `
+			for ( var [[x, z], y] = [1, 2]; i < end; i += 1 ) {
+				console.log( i );
+			}`,
+
+		output: `
+			for ( var ref = [1, 2], ref_0 = ref[0], x = ref_0[0], z = ref_0[1], y = ref[1]; i < end; i += 1 ) {
+				console.log( i );
+			}`
+	},
+
+	{
+		description:
+			'array destructuring with default value in variable declaration in for loop head',
+
+		input: `
+			for ( var [x, y = 4, z] = [1, 2, 3]; i < end; i += 1 ) {
+				console.log( i );
+			}`,
+
+		output: `
+			for ( var ref = [1, 2, 3], x = ref[0], y = ref[1], y = y === void 0 ? 4 : y, z = ref[2]; i < end; i += 1 ) {
+				console.log( i );
+			}`
+	},
+
+	{
+		description:
+			'object destructuring with default value in variable declaration in for loop head',
+
+		input: `
+			for ( var {x, y = 4, z} = {}; i < end; i += 1 ) {
+				console.log( i );
+			}`,
+
+		output: `
+			for ( var ref = {}, x = ref.x, y = ref.y, y = y === void 0 ? 4 : y, z = ref.z; i < end; i += 1 ) {
 				console.log( i );
 			}`
 	},
diff --git a/test/samples/object-rest-spread.js b/test/samples/object-rest-spread.js
index 603a1f9..7c1c9ef 100644
--- a/test/samples/object-rest-spread.js
+++ b/test/samples/object-rest-spread.js
@@ -160,28 +160,50 @@ module.exports = [
 	{
 		description: 'transpiles rest properties',
 		input: `var {a, ...b} = c`,
-		output: `var a = c.a;
-var rest = {}; for (var n in c) if(["a"].indexOf(n) === -1) rest[n] = c[n];
+		output: `function objectWithoutProperties (obj, keys) { var target = {}; for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k) && exclude.indexOf(k) === -1) target[k] = obj[k]; return target; }
+var a = c.a;
+var rest = objectWithoutProperties( c, ["a"] );
 var b = rest;`
 	},
 
 	{
 		description: 'transpiles rest properties in arguments',
-		input: `(function({x, ...y}) {})`,
-		output: `(function(ref) {
-	var x = ref.x;
-	var rest = {}; for (var n in ref) if(["x"].indexOf(n) === -1) rest[n] = ref[n];
-	var y = rest;
-})`
+		input: `
+			"use strict";
+			function objectWithoutProperties({x, ...y}) {}`,
+		output: `
+			"use strict";
+			function objectWithoutProperties$1 (obj, keys) { var target = {}; for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k) && exclude.indexOf(k) === -1) target[k] = obj[k]; return target; }
+function objectWithoutProperties(ref) {
+				var x = ref.x;
+				var rest = objectWithoutProperties$1( ref, ["x"] );
+				var y = rest;
+}`
 	},
 
 	{
 		description: 'transpiles rest properties in arrow function arguments',
 		input: `(({x, ...y}) => {})`,
-		output: `(function (ref) {
+		output: `function objectWithoutProperties (obj, keys) { var target = {}; for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k) && exclude.indexOf(k) === -1) target[k] = obj[k]; return target; }
+(function (ref) {
 	var x = ref.x;
-	var rest = {}; for (var n in ref) if(["x"].indexOf(n) === -1) rest[n] = ref[n];
+	var rest = objectWithoutProperties( ref, ["x"] );
 	var y = rest;
 })`
+	},
+
+	{
+	description: 'transpiles rest properties in for loop heads',
+	input: `for( var {a, ...b} = c;; ) {}`,
+	output: `function objectWithoutProperties (obj, keys) { var target = {}; for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k) && exclude.indexOf(k) === -1) target[k] = obj[k]; return target; }
+for( var a = c.a, rest = objectWithoutProperties( c, ["a"] ), b = rest;; ) {}`
+	},
+
+	{
+	description: 'transpiles trivial rest properties in for loop heads',
+	input: `for( var {...b} = c;; ) {}`,
+	output: `function objectWithoutProperties (obj, keys) { var target = {}; for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k) && exclude.indexOf(k) === -1) target[k] = obj[k]; return target; }
+for( var rest = objectWithoutProperties( c, [] ), b = rest;; ) {}`
 	}
+
 ];
diff --git a/test/samples/spread-operator.js b/test/samples/spread-operator.js
index d831eea..b5fc434 100644
--- a/test/samples/spread-operator.js
+++ b/test/samples/spread-operator.js
@@ -38,8 +38,9 @@ module.exports = [
 			( foo || bar ).baz( ...values );`,
 
 		output: `
-			(ref = ( foo || bar )).baz.apply( ref, values );
-			var ref;`
+			var ref;
+
+			(ref = ( foo || bar )).baz.apply( ref, values );`
 	},
 
 	{
@@ -51,8 +52,9 @@ module.exports = [
 			}`,
 		output: `
 			function a( args ) {
-				return (ref = this).go.apply( ref, args );
 				var ref;
+
+				return (ref = this).go.apply( ref, args );
 			}`
 	},
 
@@ -84,16 +86,18 @@ module.exports = [
 				while ( len-- ) args[ len ] = arguments[ len ];
 
 				return Domain.run(function () {
-					return (ref = this$1).go.apply(ref, args);
 					var ref;
+
+					return (ref = this$1).go.apply(ref, args);
 				});
 			}
 			function bar(args) {
 				var this$1 = this;
 
 				return Domain.run(function () {
-					return (ref = this$1).go.apply(ref, args);
 					var ref;
+
+					return (ref = this$1).go.apply(ref, args);
 				});
 			}
 			function baz() {
@@ -101,8 +105,9 @@ module.exports = [
 				var this$1 = this;
 
 				return Domain.run(function () {
-					return (ref = this$1).go.apply(ref, arguments$1);
 					var ref;
+
+					return (ref = this$1).go.apply(ref, arguments$1);
 				});
 			}
 		`
@@ -170,11 +175,12 @@ module.exports = [
 			process( result );`,
 
 		output: `
+			var ref$1;
+
 			var result;
 			if ( ref )
 				{ result = (ref$1 = expr()).baz.apply( ref$1, values ); }
-			process( result );
-			var ref$1;`
+			process( result );`
 	},
 
 	{
@@ -190,12 +196,12 @@ module.exports = [
 			}`,
 		output: `
 			function foo() {
+				var ref$1, ref$2;
+
 				stuff();
 				if ( ref )
 					{ return (ref$1 = expr()).baz.apply( ref$1, values ); }
 				return (ref$2 = (up || down)).bar.apply( ref$2, values );
-				var ref$1;
-				var ref$2;
 			}`
 	},
 
@@ -211,12 +217,12 @@ module.exports = [
 			}`,
 		output: `
 			function ref() {
+				var ref, ref$2;
+
 				stuff();
 				if ( ref$1 )
 					{ return (ref = expr()).baz.apply( ref, [ a ].concat( values, [(ref$2 = (up || down)).bar.apply( ref$2, [ c ].concat( values, [d] ) )] ) ); }
 				return other();
-				var ref;
-				var ref$2;
 			}`
 	},
 
@@ -243,9 +249,9 @@ module.exports = [
 				prepare: function prepare() {
 					var i = arguments.length, argsArray = Array(i);
 					while ( i-- ) argsArray[i] = arguments[i];
+					var ref;
 
 					return (ref = this.add).bind.apply(ref, [ this ].concat( argsArray ))
-					var ref;
 				}
 			}`
 	},
@@ -526,8 +532,11 @@ module.exports = [
 		output: `
 			function foo (x) {
 				if ( x )
-					{ return function (ref) { return (ref$1 = (bar || baz)).Test.apply( ref$1, [ ref ].concat( x ) )
-						var ref$1;; }; }
+					{ return function (ref) {
+						var ref$1;
+
+						return (ref$1 = (bar || baz)).Test.apply( ref$1, [ ref ].concat( x ) );
+						}; }
 			}
 		`
 	},
@@ -569,8 +578,11 @@ module.exports = [
 				while ( i-- ) argsArray[i] = arguments[i];
 
 				if ( x )
-					{ return function (ref) { return (ref$1 = (bar || baz)).Test.apply( ref$1, [ ref ].concat( arguments$1 ) )
-						var ref$1;; }; }
+					{ return function (ref) {
+						var ref$1;
+
+						return (ref$1 = (bar || baz)).Test.apply( ref$1, [ ref ].concat( arguments$1 ) );
+						}; }
 			}
 		`
 	}
diff --git a/test/samples/template-strings.js b/test/samples/template-strings.js
index 6b69b50..5a65ef6 100644
--- a/test/samples/template-strings.js
+++ b/test/samples/template-strings.js
@@ -41,7 +41,7 @@ module.exports = [
 			'transpiles tagged template literals with `transforms.dangerousTaggedTemplateString = true`',
 		options: { transforms: { dangerousTaggedTemplateString: true } },
 		input: 'var str = x`y${(() => 42)()}`;',
-		output: `var str = x(["y", ""], (function () { return 42; })());`
+		output: `var templateObject = Object.freeze(["y", ""]);\nvar str = x(templateObject, (function () { return 42; })());`
 	},
 
 	{
@@ -49,7 +49,21 @@ module.exports = [
 			'transpiles tagged template literals with `transforms.dangerousTaggedTemplateString = true`',
 		options: { transforms: { dangerousTaggedTemplateString: true } },
 		input: 'var str = x`${(() => 42)()}y`;',
-		output: `var str = x(["", "y"], (function () { return 42; })());`
+		output: `var templateObject = Object.freeze(["", "y"]);\nvar str = x(templateObject, (function () { return 42; })());`
+	},
+
+	{
+		description: 'reuses quasi array for identical tagged template strings',
+		options: { transforms: { dangerousTaggedTemplateString: true } },
+		input: 'x`a${a}b`, x`a${b}b`, x`b${c}a`',
+		output: `var templateObject$1 = Object.freeze(["b", "a"]);\nvar templateObject = Object.freeze(["a", "b"]);\nx(templateObject, a), x(templateObject, b), x(templateObject$1, c)`
+	},
+
+	{
+		description: 'reuses quasi array for identical tagged template strings in strict mode',
+		options: { transforms: { dangerousTaggedTemplateString: true } },
+		input: '"use strict";\nx`a${a}b`, x`a${b}b`, x`b${c}a`',
+		output: `"use strict";\nvar templateObject$1 = Object.freeze(["b", "a"]);\nvar templateObject = Object.freeze(["a", "b"]);\nx(templateObject, a), x(templateObject, b), x(templateObject$1, c)`
 	},
 
 	{
diff --git a/test/samples/trailing-function-commas.js b/test/samples/trailing-function-commas.js
index 67cc793..597efe0 100644
--- a/test/samples/trailing-function-commas.js
+++ b/test/samples/trailing-function-commas.js
@@ -64,5 +64,15 @@ module.exports = [
 				var b = ref[0];
 				var c = ref$1.c;
 })`
+	},
+
+	{
+		description: 'strips trailing commas in new expression arguments',
+
+		input: `
+			new f(a,)`,
+
+		output: `
+			new f(a)`
 	}
 ];