Repository: Rich-Harris/buble (js)

Issue:
Invalid output when returning a object with a computed property key
When a object that uses a computed property key (`{[key]: value}`) is returned in a function, the output is invalid.

Here's an [example](http://buble.surge.sh/#const%20deepifyKeys%20%3D%20(key%2C%20val)%20%3D%3E%20%7B%20%20%20%20%0A%20%20return%20%7B%20%5Bkey%5D%3A%20val%20%7D%0A%7D):

input:
```
const deepifyKeys = (key, val) => {    
  return { [key]: val }
}
```

output:
```
var deepifyKeys = function (key, val) {    
  return ( obj = {}
  var obj;, obj[key] = val, obj )
}
```

A more complex example with the actual code that hit this bug can be found [here](http://buble.surge.sh/#const%20deepifyKeys%20%3D%20(obj)%20%3D%3E%20mapObject(obj%2C%0A%20%20(%5Bkey%2C%20val%5D)%20%3D%3E%20%7B%0A%20%20%20%20const%20dashIndex%20%3D%20key.indexOf('-')%0A%20%20%20%20if%20(dashIndex%20%3E%20-1)%20%7B%0A%20%20%20%20%20%20return%20%7B%0A%20%20%20%20%20%20%20%20%5Bkey.slice(0%2C%20dashIndex)%5D%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%5Bkey.slice(dashIndex%20%2B%201)%5D%3A%20val%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%20%20%20%20return%20%7B%20%5Bkey%5D%3A%20val%20%7D%0A%20%20%7D%0A)).

Pull request description:
Move ObjectExpresssion helper declaration to top of scope
Closes #51. I implemented a new method `declareIdentifier` on `Block`s so that they can handle declaration of variables themselves.

Patch:
diff --git a/src/program/BlockStatement.js b/src/program/BlockStatement.js
index f2a4351..94a25b8 100644
--- a/src/program/BlockStatement.js
+++ b/src/program/BlockStatement.js
@@ -30,6 +30,7 @@ export default class BlockStatement extends Node {
 		this.thisAlias = null;
 		this.argumentsAlias = null;
 		this.defaultParameters = [];
+		this.createdDeclarations = [];
 
 		// normally the scope gets created here, during initialisation,
 		// but in some cases (e.g. `for` statements), we need to create
@@ -166,6 +167,13 @@ export default class BlockStatement extends Node {
 
 		super.transpile(code, transforms);
 
+		if (this.createdDeclarations.length) {
+			introStatementGenerators.push((start, prefix, suffix) => {
+				const assignment = `${prefix}var ${this.createdDeclarations.join(', ')}${suffix}`;
+				code.appendLeft(start, assignment);
+			});
+		}
+
 		if (this.synthetic) {
 			if (this.parent.type === 'ArrowFunctionExpression') {
 				const expr = this.body[0];
@@ -203,6 +211,12 @@ 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/types/ObjectExpression.js b/src/program/types/ObjectExpression.js
index 9f62012..9afff13 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.findScope(true).createIdentifier('obj');
+					name = this.findLexicalBoundary().declareIdentifier('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.findScope(true).createIdentifier('obj');
+						name = this.findLexicalBoundary().declareIdentifier('obj');
 
 						const propId = name + (prop.computed ? '' : '.');
 						code.appendRight(prop.start, `( ${name} = {}, ${propId}`);
@@ -197,9 +197,6 @@ export default class ObjectExpression extends Node {
 						}
 						code.appendLeft(lastComputedProp.end, `, ${name} )`);
 
-						const statement = this.findNearest(/(?:Statement|Declaration)$/);
-						code.appendLeft(statement.end, `\n${i0}var ${name};`);
-
 						lastComputedProp = null;
 						name = null;
 					}
@@ -238,9 +235,6 @@ export default class ObjectExpression extends Node {
 
 			if (!isSimpleAssignment && name) {
 				code.appendLeft(lastComputedProp.end, `, ${name} )`);
-
-				const statement = this.findNearest(/(?:Statement|Declaration)$/);
-				code.appendLeft(statement.end, `\n${i0}var ${name};`);
 			}
 		}
 	}


Test patch:
diff --git a/test/samples/computed-properties.js b/test/samples/computed-properties.js
index 575a0d5..d68001d 100644
--- a/test/samples/computed-properties.js
+++ b/test/samples/computed-properties.js
@@ -104,8 +104,9 @@ module.exports = [
 			var a = 'foo', obj = { [a]: 'bar', x: 42 }, bar = obj.foo;`,
 
 		output: `
-			var a = 'foo', obj = ( obj$1 = {}, obj$1[a] = 'bar', obj$1.x = 42, obj$1 ), bar = obj.foo;
-			var obj$1;`
+			var obj$1;
+
+			var a = 'foo', obj = ( obj$1 = {}, obj$1[a] = 'bar', obj$1.x = 42, obj$1 ), bar = obj.foo;`
 	},
 
 	{
@@ -136,8 +137,9 @@ module.exports = [
 			call({ [a]: 5 });`,
 
 		output: `
-			call(( obj = {}, obj[a] = 5, obj ));
-			var obj;`
+			var obj;
+
+			call(( obj = {}, obj[a] = 5, obj ));`
 	},
 
 	{
@@ -225,8 +227,22 @@ module.exports = [
 			foo => bar({[x - y]: obj});
 		`,
 		output: `
-			!function(foo) { return bar(( obj$1 = {}, obj$1[x - y] = obj, obj$1 ))
-				var obj$1;; };
+			var obj$1;
+
+			!function(foo) { return bar(( obj$1 = {}, obj$1[x - y] = obj, obj$1 )); };
+		`
+	},
+
+	{
+		description: 'Supports nested computed properties (#51)',
+
+		input: `
+			(function () { return { [key]: { [key]: val } } })
+		`,
+		output: `
+			(function () {
+			var obj, obj$1;
+ return ( obj$1 = {}, obj$1[key] = ( obj = {}, obj[key] = val, obj ), obj$1 ) })
 		`
 	}
 ];
diff --git a/test/samples/destructuring.js b/test/samples/destructuring.js
index c2966f7..8ea25cb 100644
--- a/test/samples/destructuring.js
+++ b/test/samples/destructuring.js
@@ -684,11 +684,12 @@ module.exports = [
 			] = [ "ok" ];
 		`,
 		output: `
+			var obj;
+
 			var ref = [ "ok" ];
 			var a = ref[0]; if ( a === void 0 ) a = "A" + (baz() - 4);
 			var c = ref[2]; if ( c === void 0 ) c = (function (x) { return -x; });
 			var d = ref[3]; if ( d === void 0 ) d = (( obj = { r: 5 }, obj[h()] = i, obj ));
-			var obj;
 		`
 	},
 
@@ -704,11 +705,12 @@ module.exports = [
 			} = { b: 3 };
 		`,
 		output: `
+			var obj;
+
 			var ref = { b: 3 };
 			var a = ref.a; if ( a === void 0 ) a = "A" + (baz() - 4);
 			var c = ref.c; if ( c === void 0 ) c = (function (x) { return -x; });
 			var d = ref.d; if ( d === void 0 ) d = (( obj = { r: 5 }, obj[1 + 1] = 2, obj[h()] = i, obj ));
-			var obj;
 		`
 	},
 
@@ -774,10 +776,11 @@ module.exports = [
 		`,
 
 		output: `
+			var obj;
+
 			var ref = ( obj = {}, obj['a'] = 1, obj );
 			var a = ref.a;
 			var b = ref.b;
-			var obj;
 		`
 	}
 ];
diff --git a/test/samples/object-properties-no-named-function-expressions.js b/test/samples/object-properties-no-named-function-expressions.js
index 76109eb..0fb4931 100644
--- a/test/samples/object-properties-no-named-function-expressions.js
+++ b/test/samples/object-properties-no-named-function-expressions.js
@@ -116,12 +116,12 @@ module.exports = [
 				console.log(JSON.stringify({['bar']:3}));
 		`,
 		output: `
+			var obj, obj$1;
+
 			if (1)
-				{ console.log(JSON.stringify(( obj = {}, obj['com'+'puted'] = 1, obj['foo'] = 2, obj )));
-					var obj; }
+				{ console.log(JSON.stringify(( obj = {}, obj['com'+'puted'] = 1, obj['foo'] = 2, obj ))); }
 			else
-				{ console.log(JSON.stringify(( obj$1 = {}, obj$1['bar'] = 3, obj$1 )));
-			var obj$1; }
+				{ console.log(JSON.stringify(( obj$1 = {}, obj$1['bar'] = 3, obj$1 ))); }
 		`
 	}
 ];
diff --git a/test/samples/object-properties.js b/test/samples/object-properties.js
index 46711d4..c43427f 100644
--- a/test/samples/object-properties.js
+++ b/test/samples/object-properties.js
@@ -104,12 +104,12 @@ module.exports = [
 				console.log(JSON.stringify({['bar']:3}));
 		`,
 		output: `
+			var obj, obj$1;
+
 			if (1)
-				{ console.log(JSON.stringify(( obj = {}, obj['com'+'puted'] = 1, obj['foo'] = 2, obj )));
-					var obj; }
+				{ console.log(JSON.stringify(( obj = {}, obj['com'+'puted'] = 1, obj['foo'] = 2, obj ))); }
 			else
-				{ console.log(JSON.stringify(( obj$1 = {}, obj$1['bar'] = 3, obj$1 )));
-			var obj$1; }
+				{ console.log(JSON.stringify(( obj$1 = {}, obj$1['bar'] = 3, obj$1 ))); }
 		`
 	},
 
diff --git a/test/samples/object-rest-spread.js b/test/samples/object-rest-spread.js
index 8223da2..603a1f9 100644
--- a/test/samples/object-rest-spread.js
+++ b/test/samples/object-rest-spread.js
@@ -62,12 +62,11 @@ module.exports = [
 			var a12 = { ...b, [c]:3, d:4 };
 		`,
 		output: `
+			var obj, obj$1, obj$2, obj$3, obj$4, obj$5, obj$6, obj$7, obj$8;
+
 			var a0 = Object.assign(( obj = {}, obj[ x ] = true, obj ), y);
-			var obj;
 			var a1 = Object.assign(( obj$1 = {}, obj$1[ w ] = 0, obj$1[ x ] = true, obj$1 ), y);
-			var obj$1;
 			var a2 = Object.assign(( obj$2 = { v: v }, obj$2[ w ] = 0, obj$2[ x ] = true, obj$2 ), y);
-			var obj$2;
 			var a3 = {};
 			a3[ w ] = 0;
 			a3[ x ] = true;
@@ -76,24 +75,18 @@ module.exports = [
 			a4[ x ] = true;
 			a4.y = y;
 			var a5 = Object.assign(( obj$3 = { k : 9 }, obj$3[ x ] = true, obj$3 ), y);
-			var obj$3;
 			var a6 = Object.assign({}, y, ( obj$4 = {}, obj$4[ x ] = true, obj$4 ));
-			var obj$4;
 			var a7 = Object.assign({}, y, ( obj$5 = {}, obj$5[ w ] = 0, obj$5[ x ] = true, obj$5 ));
-			var obj$5;
 			var a8 = Object.assign({ k : 9 }, y, ( obj$6 = {}, obj$6[ x ] = true, obj$6 ));
-			var obj$6;
 			var a9 = {};
 			a9[ x ] = true;
 			a9[ y ] = false;
 			a9[ z ] = 9;
 			var a10 = Object.assign(( obj$7 = {}, obj$7[ x ] = true, obj$7 ), y, {p: p}, q);
-			var obj$7;
 			var a11 = { x: x };
 			a11[c] = 9;
 			a11.y = y;
 			var a12 = Object.assign({}, b, ( obj$8 = {}, obj$8[c] = 3, obj$8 ), {d:4});
-			var obj$8;
 		`
 	},
 
@@ -119,32 +112,21 @@ module.exports = [
 			f12({ ...b, [c]:3, d:4 });
 		`,
 		output: `
+			var obj, obj$1, obj$2, obj$3, obj$4, obj$5, obj$6, obj$7, obj$8, obj$9, obj$10, obj$11, obj$12;
+
 			f0( Object.assign(( obj = {}, obj[ x ] = true, obj ), y) );
-			var obj;
 			f1( Object.assign(( obj$1 = {}, obj$1[ w ] = 0, obj$1[ x ] = true, obj$1 ), y) );
-			var obj$1;
 			f2( Object.assign(( obj$2 = { v: v }, obj$2[ w ] = 0, obj$2[ x ] = true, obj$2 ), y) );
-			var obj$2;
 			f3( ( obj$3 = {}, obj$3[ w ] = 0, obj$3[ x ] = true, obj$3 ) );
-			var obj$3;
 			f4( ( obj$4 = {}, obj$4[ w ] = 0, obj$4[ x ] = true, obj$4.y = y, obj$4 ) );
-			var obj$4;
 			f5( Object.assign(( obj$5 = { k : 9 }, obj$5[ x ] = true, obj$5 ), y) );
-			var obj$5;
 			f6( Object.assign({}, y, ( obj$6 = {}, obj$6[ x ] = true, obj$6 )) );
-			var obj$6;
 			f7( Object.assign({}, y, ( obj$7 = {}, obj$7[ w ] = 0, obj$7[ x ] = true, obj$7 )) );
-			var obj$7;
 			f8( Object.assign({ k : 9 }, y, ( obj$8 = {}, obj$8[ x ] = true, obj$8 )) );
-			var obj$8;
 			f9( ( obj$9 = {}, obj$9[ x ] = true, obj$9[ y ] = false, obj$9[ z ] = 9, obj$9 ) );
-			var obj$9;
 			f10( Object.assign(( obj$10 = {}, obj$10[ x ] = true, obj$10 ), y, {p: p}, q) );
-			var obj$10;
 			f11( ( obj$11 = { x: x }, obj$11[c] = 9, obj$11.y = y, obj$11 ) );
-			var obj$11;
 			f12(Object.assign({}, b, ( obj$12 = {}, obj$12[c] = 3, obj$12 ), {d:4}));
-			var obj$12;
 		`
 	},