Repository: Rich-Harris/buble (js)

Issue:
[bug] `q.a = class {}` resolves to undefined
Here is an [example](http://buble.surge.sh/#const%20q%20%3D%20%7B%7D%3B%0A%0Aq.a%20%3D%20class%20%7B%0A%20%20c%20()%20%7B%7D%0A%7D).

---

input

```js

const q = {};

q.a = class {
  c () {}
}

```

output

```js

var q = {};

q.a = (function () {
  function undefined () {}

  undefined.prototype.c = function c () {};

  return undefined;
}())

```

---

This way it is working fine.

```js

const q = {};

Object.assign(q, {a: class {
	c () {}
}})

```


Pull request:
Better support for anonymous classes
Closes #33.

Patch:
diff --git a/src/program/types/ClassDeclaration.js b/src/program/types/ClassDeclaration.js
index dbd9b2b..ddca8e7 100644
--- a/src/program/types/ClassDeclaration.js
+++ b/src/program/types/ClassDeclaration.js
@@ -3,8 +3,12 @@ import deindent from '../../utils/deindent.js';
 
 export default class ClassDeclaration extends Node {
 	initialise(transforms) {
-		this.name = this.id.name;
-		this.findScope(true).addDeclaration(this.id, 'class');
+		if (this.id) {
+			this.name = this.id.name;
+			this.findScope(true).addDeclaration(this.id, 'class');
+		} else {
+			this.name = this.findScope(true).createIdentifier("defaultExport");
+		}
 
 		super.initialise(transforms);
 	}
@@ -21,21 +25,26 @@ export default class ClassDeclaration extends Node {
 
 			// if this is an export default statement, we have to move the export to
 			// after the declaration, because `export default var Foo = ...` is illegal
-			const syntheticDefaultExport =
-				this.parent.type === 'ExportDefaultDeclaration'
-					? `\n\n${i0}export default ${this.id.name};`
-					: '';
+			const isExportDefaultDeclaration = this.parent.type === 'ExportDefaultDeclaration';
 
-			if (syntheticDefaultExport) code.remove(this.parent.start, this.start);
+			if (isExportDefaultDeclaration) {
+				code.remove(this.parent.start, this.start);
+			}
 
-			code.overwrite(this.start, this.id.start, 'var ');
+			let c = this.start
+			if (this.id) {
+				code.overwrite(c, this.id.start, 'var ');
+				c = this.id.end
+			} else {
+				code.prependLeft(c, `var ${this.name}`);
+			}
 
 			if (this.superClass) {
 				if (this.superClass.end === this.body.start) {
-					code.remove(this.id.end, this.superClass.start);
-					code.appendLeft(this.id.end, ` = (function (${superName}) {\n${i1}`);
+					code.remove(c, this.superClass.start);
+					code.appendLeft(c, ` = (function (${superName}) {\n${i1}`);
 				} else {
-					code.overwrite(this.id.end, this.superClass.start, ' = ');
+					code.overwrite(c, this.superClass.start, ' = ');
 					code.overwrite(
 						this.superClass.end,
 						this.body.start,
@@ -43,15 +52,19 @@ export default class ClassDeclaration extends Node {
 					);
 				}
 			} else {
-				if (this.id.end === this.body.start) {
-					code.appendLeft(this.id.end, ' = ');
+				if (c === this.body.start) {
+					code.appendLeft(c, ' = ');
 				} else {
-					code.overwrite(this.id.end, this.body.start, ' = ');
+					code.overwrite(c, this.body.start, ' = ');
 				}
 			}
 
 			this.body.transpile(code, transforms, !!this.superClass, superName);
 
+			const syntheticDefaultExport =
+				isExportDefaultDeclaration
+					? `\n\n${i0}export default ${this.name};`
+					: '';
 			if (this.superClass) {
 				code.appendLeft(this.end, `\n\n${i1}return ${this.name};\n${i0}}(`);
 				code.move(this.superClass.start, this.superClass.end, this.end);
diff --git a/src/program/types/ClassExpression.js b/src/program/types/ClassExpression.js
index e1454c9..5c1f5f5 100644
--- a/src/program/types/ClassExpression.js
+++ b/src/program/types/ClassExpression.js
@@ -2,13 +2,17 @@ import Node from '../Node.js';
 
 export default class ClassExpression extends Node {
 	initialise(transforms) {
-		this.name = this.id
+		this.name = ( this.id
 			? this.id.name
 			: this.parent.type === 'VariableDeclarator'
 				? this.parent.id.name
-				: this.parent.type === 'AssignmentExpression'
-					? this.parent.left.name
-					: this.findScope(true).createIdentifier('anonymous');
+				: this.parent.type !== 'AssignmentExpression'
+					? null
+					: this.parent.left.type === 'Identifier'
+						? this.parent.left.name
+						: this.parent.left.type === 'MemberExpression'
+							? this.parent.left.property.name
+							: null ) || this.findScope(true).createIdentifier('anonymous');
 
 		super.initialise(transforms);
 	}


Test patch:
diff --git a/test/samples/classes.js b/test/samples/classes.js
index 31f886f..704de9b 100644
--- a/test/samples/classes.js
+++ b/test/samples/classes.js
@@ -366,6 +366,29 @@ module.exports = [
 			}());`
 	},
 
+	{
+		description:
+			'transpiles an anonymous class expression that is assigned to a property',
+
+		input: `
+			const q = {};
+
+			q.a = class {
+				c () {}
+			};`,
+
+		output: `
+			var q = {};
+
+			q.a = (function () {
+				function a () {}
+
+				a.prototype.c = function c () {};
+
+				return a;
+			}());`
+	},
+
 	{
 		description: 'allows constructor to be in middle of body',
 
diff --git a/test/samples/modules.js b/test/samples/modules.js
index 772fc45..8e5538f 100644
--- a/test/samples/modules.js
+++ b/test/samples/modules.js
@@ -31,5 +31,33 @@ module.exports = [
 		options: { transforms: { modules: false } },
 		input: `import 'foo'; export { foo };`,
 		output: `import 'foo'; export { foo };`
+	},
+
+	{
+		description:
+			'Supports anonymous classes as default export',
+		options: { transforms: { modules: false } },
+		input: `
+			export default class {
+				constructor() {
+					foo()
+				}
+
+				a() {
+					bar()
+				}
+			}
+		`,
+		output: `
+			var defaultExport = function defaultExport() {
+				foo()
+			};
+
+			defaultExport.prototype.a = function a () {
+				bar()
+			};
+
+			export default defaultExport;
+		`
 	}
 ];