Repository: Rich-Harris/buble (js)

Issue:
support trailing commas in function parameter lists
Here is an [example](http://buble.surge.sh/#function%20foo(%0A%20%20%20%20param1%2C%0A%20%20%20%20param2%2C%0A)%20%7B%7D).

I hope [this](http://2ality.com/2015/11/trailing-comma-parameters.html) is not difficult to implement.

Pull request description:
Transpile trailing function commas
Closes #50.

Patch:
diff --git a/src/index.js b/src/index.js
index dbcaba3..f10d560 100644
--- a/src/index.js
+++ b/src/index.js
@@ -15,8 +15,8 @@ const dangerousTransforms = ['dangerousTaggedTemplateString', 'dangerousForOf'];
 export function target(target) {
 	const targets = Object.keys(target);
 	let bitmask = targets.length
-		? 0b1111111111111111111111111111111
-		: 0b1000000000000000000000000000000;
+		? 0b11111111111111111111111111111111
+		: 0b01000000000000000000000000000000;
 
 	Object.keys(target).forEach(environment => {
 		const versions = matrix[environment];
diff --git a/src/program/types/ArrowFunctionExpression.js b/src/program/types/ArrowFunctionExpression.js
index b5ae942..e6915d9 100644
--- a/src/program/types/ArrowFunctionExpression.js
+++ b/src/program/types/ArrowFunctionExpression.js
@@ -33,6 +33,16 @@ export default class ArrowFunctionExpression extends Node {
 		} else {
 			super.transpile(code, transforms);
 		}
+		if (transforms.trailingFunctionCommas && this.params.length) {
+			let c = this.params[this.params.length - 1].end
+			while (code.original[c] !== ')' && c < this.body.start) {
+				if (code.original[c] === ',') {
+					code.remove(c, c + 1);
+					break;
+				}
+				++c;
+			}
+		}
 	}
 
 	// Returns whether any transforms that will happen use `arguments`
diff --git a/src/program/types/CallExpression.js b/src/program/types/CallExpression.js
index a37a6f8..32c1a79 100644
--- a/src/program/types/CallExpression.js
+++ b/src/program/types/CallExpression.js
@@ -101,6 +101,17 @@ export default class CallExpression extends Node {
 			}
 		}
 
+		if (transforms.trailingFunctionCommas && this.arguments.length) {
+			let c = this.arguments[this.arguments.length - 1].end
+			while (code.original[c] !== ')' && c < this.end) {
+				if (code.original[c] === ',') {
+					code.remove(c, c + 1);
+					break;
+				}
+				++c;
+			}
+		}
+
 		super.transpile(code, transforms);
 	}
 }
diff --git a/src/program/types/FunctionDeclaration.js b/src/program/types/FunctionDeclaration.js
index 1a2cbcf..a6d8d24 100644
--- a/src/program/types/FunctionDeclaration.js
+++ b/src/program/types/FunctionDeclaration.js
@@ -12,4 +12,18 @@ export default class FunctionDeclaration extends Node {
 		this.findScope(true).addDeclaration(this.id, 'function');
 		super.initialise(transforms);
 	}
+
+	transpile(code, transforms) {
+		super.transpile(code, transforms);
+		if (transforms.trailingFunctionCommas && this.params.length) {
+			let c = this.params[this.params.length - 1].end
+			while (code.original[c] !== ')' && c < this.body.start) {
+				if (code.original[c] === ',') {
+					code.remove(c, c + 1);
+					break;
+				}
+				++c;
+			}
+		}
+	}
 }
diff --git a/src/program/types/FunctionExpression.js b/src/program/types/FunctionExpression.js
index ebaebc9..027e1cb 100644
--- a/src/program/types/FunctionExpression.js
+++ b/src/program/types/FunctionExpression.js
@@ -63,4 +63,18 @@ export default class FunctionExpression extends Node {
 			}
 		}
 	}
+
+	transpile(code, transforms) {
+		super.transpile(code, transforms);
+		if (transforms.trailingFunctionCommas && this.params.length) {
+			let c = this.params[this.params.length - 1].end
+			while (code.original[c] !== ')' && c < this.body.start) {
+				if (code.original[c] === ',') {
+					code.remove(c, c + 1);
+					break;
+				}
+				++c;
+			}
+		}
+	}
 }
diff --git a/src/support.js b/src/support.js
index e84d1ca..7b48b92 100644
--- a/src/support.js
+++ b/src/support.js
@@ -1,39 +1,39 @@
 export const matrix = {
 	chrome: {
-		    48: 0b1001111011111100111110101111101,
-		    49: 0b1001111111111100111111111111111,
-		    50: 0b1011111111111100111111111111111,
-		    51: 0b1011111111111100111111111111111,
-		    52: 0b1111111111111100111111111111111
+		    48: 0b01001111011111100111110101111101,
+		    49: 0b01001111111111100111111111111111,
+		    50: 0b01011111111111100111111111111111,
+		    51: 0b01011111111111100111111111111111,
+		    52: 0b01111111111111100111111111111111
 	},
 	firefox: {
-		    43: 0b1000111111101100000110111011101,
-		    44: 0b1000111111101100000110111011101,
-		    45: 0b1000111111101100000110111011101,
-		    46: 0b1010111111111100000110111011101,
-		    47: 0b1010111111111100111111111011111,
-		    48: 0b1010111111111100111111111011111
+		    43: 0b01000111111101100000110111011101,
+		    44: 0b01000111111101100000110111011101,
+		    45: 0b01000111111101100000110111011101,
+		    46: 0b01010111111111100000110111011101,
+		    47: 0b01010111111111100111111111011111,
+		    48: 0b01010111111111100111111111011111
 	},
 	safari: {
-		     8: 0b1000000000000000000000000000000,
-		     9: 0b1001111001101100000011101011110
+		     8: 0b01000000000000000000000000000000,
+		     9: 0b01001111001101100000011101011110
 	},
 	ie: {
-		     8: 0b0000000000000000000000000000000,
-		     9: 0b1000000000000000000000000000000,
-		    10: 0b1000000000000000000000000000000,
-		    11: 0b1000000000000000111000001100000
+		     8: 0b00000000000000000000000000000000,
+		     9: 0b01000000000000000000000000000000,
+		    10: 0b01000000000000000000000000000000,
+		    11: 0b01000000000000000111000001100000
 	},
 	edge: {
-		    12: 0b1011110110111100011010001011101,
-		    13: 0b1011111110111100011110001011111
+		    12: 0b01011110110111100011010001011101,
+		    13: 0b01011111110111100011110001011111
 	},
 	node: {
-		'0.10': 0b1000000000101000000000001000000,
-		'0.12': 0b1000001000101000000010001000100,
-		     4: 0b1001111000111100111111001111111,
-		     5: 0b1001111000111100111111001111111,
-		     6: 0b1011111111111100111111111111111
+		'0.10': 0b01000000000101000000000001000000,
+		'0.12': 0b01000001000101000000010001000100,
+		     4: 0b01001111000111100111111001111111,
+		     5: 0b01001111000111100111111001111111,
+		     6: 0b01011111111111100111111111111111
 	}
 };
 
@@ -73,5 +73,7 @@ export const features = [
 
 	// additional transforms, not from
 	// https://featuretests.io
-	'reservedProperties'
+	'reservedProperties',
+
+	'trailingFunctionCommas'
 ];


Test patch:
diff --git a/test/samples/trailing-function-commas.js b/test/samples/trailing-function-commas.js
new file mode 100644
index 0000000..67cc793
--- /dev/null
+++ b/test/samples/trailing-function-commas.js
@@ -0,0 +1,68 @@
+module.exports = [
+	{
+		description: 'strips trailing commas in call arguments',
+
+		input: `
+			f(a,)`,
+
+		output: `
+			f(a)`
+	},
+
+	{
+		description: 'strips trailing commas in function expression arguments',
+
+		input: `
+			let f = function (a,) {}`,
+
+		output: `
+			var f = function (a) {}`
+	},
+
+	{
+		description: 'strips trailing commas in normal function declaration arguments',
+
+		input: `
+			function f(a,) {}`,
+
+		output: `
+			function f(a) {}`
+	},
+
+	{
+		description: 'strips trailing commas in method arguments',
+
+		input: `
+			class A {
+				f(a,) {}
+			}`,
+
+		output: `
+			var A = function A () {};
+
+			A.prototype.f = function f (a) {};`
+	},
+
+	{
+		description: 'strips trailing commas in arrow function declaration arguments',
+
+		input: `
+			((a,) => {})`,
+
+		output: `
+			(function (a) {})`
+	},
+
+	{
+		description: 'strips trailing commas after destructured argument in arrow function declaration arguments',
+
+		input: `
+			((a,[b],{c},) => {})`,
+
+		output: `
+			(function (a,ref,ref$1) {
+				var b = ref[0];
+				var c = ref$1.c;
+})`
+	}
+];