Repository: Rich-Harris/buble
Pull request #179 (2019-01-16 10:30:55)

Inline spread elements where possible
My use case that leads me to desire this is code conditional on compile-time constants, e.g. `{ …, ...FEATURE ? { … } : { … } }` (with Rollup’s tree-shaking resolving the FEATURE ternary before it gets to Bublé). I first implemented what amounts to this change in Terser (https://github.com/terser-js/terser/pull/224), but then realised that I was running Bublé before Terser, and so Terser never actually *got* the spreads to inline. The next idea was running Terser, then Bublé, then Terser again… yuck. Then I decided that this optimisation really belonged in Bublé anyway: generating less code is good!

Implementation notes:

The ObjectExpression implementation was straightforward and I implemented it in situ.

Then the ArrayExpression, CallExpression and NewExpression one: at first I performed the spread inlining in spread()’s while loop, and that seemed to work well, but I got scared of how ArrayExpression and CallExpression both handled their “single element” cases specially, and that they did not appear to be optional inasmuch as removing the special-casing yielded exceptions in the test suite; so I extracted that
into a separate function. That ended up pleasing me more as well, with clearly cut semantics and a smaller diff inside the *Expression files, as they no longer needed to change their single-element special cases to *not* activate on a SpreadElement containing an ArrayElement.

chris-morgan: Force push as I remembered I had forgotten to add tests about trailing comma behaviour on object spread. I think I’m finished now. Certainly am for the day.

Patch:
diff --git a/package-lock.json b/package-lock.json
index 9a0df93..f43a62a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,22 +17,19 @@
       "dev": true
     },
     "acorn": {
-      "version": "6.0.4",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.4.tgz",
-      "integrity": "sha512-VY4i5EKSKkofY2I+6QLTbTTN/UvEQPCo6eiwzzSaSWfpaDhOmStMCMod6wmuPciNq+XS0faCglFu2lHZpdHUtg==",
-      "dev": true
+      "version": "6.0.5",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.5.tgz",
+      "integrity": "sha512-i33Zgp3XWtmZBMNvCr4azvOFeWVw1Rk6p3hfi3LUDvIFraOMywb1kAtrbi+med14m4Xfpqm3zRZMT+c0FNE7kg=="
     },
     "acorn-dynamic-import": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz",
-      "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==",
-      "dev": true
+      "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw=="
     },
     "acorn-jsx": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.0.tgz",
-      "integrity": "sha512-XkB50fn0MURDyww9+UYL3c1yLbOBz0ZFvrdYlGB8l+Ije1oSC75qAqrzSPjYQbdnQUzhlUGNKuesryAv0gxZOg==",
-      "dev": true
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz",
+      "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg=="
     },
     "ajv": {
       "version": "5.5.2",
@@ -209,11 +206,11 @@
       "integrity": "sha512-9kViM6nJA1Q548Jrd06x0geh+BG2ru2+RMDkIHHgJY/8AcyCs34lTHwra9BX7YdPrZXd5aarkpr/SY8bmPgPdg==",
       "dev": true,
       "requires": {
-        "chalk": "2.4.1",
+        "chalk": "2.4.2",
         "magic-string": "0.25.1",
         "minimist": "1.2.0",
         "os-homedir": "1.0.2",
-        "regexpu-core": "4.2.0",
+        "regexpu-core": "4.4.0",
         "vlq": "1.0.0"
       }
     },
@@ -245,9 +242,9 @@
       "dev": true
     },
     "chalk": {
-      "version": "2.4.1",
-      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
-      "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+      "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
       "requires": {
         "ansi-styles": "3.2.1",
         "escape-string-regexp": "1.0.5",
@@ -374,7 +371,7 @@
         "object-assign": "4.1.1",
         "pify": "2.3.0",
         "pinkie-promise": "2.0.1",
-        "rimraf": "2.6.2"
+        "rimraf": "2.6.3"
       }
     },
     "diff": {
@@ -405,7 +402,7 @@
       "requires": {
         "ajv": "5.5.2",
         "babel-code-frame": "6.26.0",
-        "chalk": "2.4.1",
+        "chalk": "2.4.2",
         "concat-stream": "1.6.2",
         "cross-spawn": "5.1.0",
         "debug": "3.2.5",
@@ -798,7 +795,7 @@
       "dev": true,
       "requires": {
         "ansi-escapes": "3.1.0",
-        "chalk": "2.4.1",
+        "chalk": "2.4.2",
         "cli-cursor": "2.1.0",
         "cli-width": "2.2.0",
         "external-editor": "2.2.0",
@@ -1395,27 +1392,27 @@
       "dev": true
     },
     "regexpu-core": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.2.0.tgz",
-      "integrity": "sha512-Z835VSnJJ46CNBttalHD/dB+Sj2ezmY6Xp38npwU87peK6mqOzOpV8eYktdkLTEkzzD+JsTcxd84ozd8I14+rw==",
+      "version": "4.4.0",
+      "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.4.0.tgz",
+      "integrity": "sha512-eDDWElbwwI3K0Lo6CqbQbA6FwgtCz4kYTarrri1okfkRLZAqstU+B3voZBCjg8Fl6iq0gXrJG6MvRgLthfvgOA==",
       "requires": {
         "regenerate": "1.4.0",
         "regenerate-unicode-properties": "7.0.0",
-        "regjsgen": "0.4.0",
-        "regjsparser": "0.3.0",
+        "regjsgen": "0.5.0",
+        "regjsparser": "0.6.0",
         "unicode-match-property-ecmascript": "1.0.4",
         "unicode-match-property-value-ecmascript": "1.0.2"
       }
     },
     "regjsgen": {
-      "version": "0.4.0",
-      "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.4.0.tgz",
-      "integrity": "sha512-X51Lte1gCYUdlwhF28+2YMO0U6WeN0GLpgpA7LK7mbdDnkQYiwvEpmpe0F/cv5L14EbxgrdayAG3JETBv0dbXA=="
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz",
+      "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA=="
     },
     "regjsparser": {
-      "version": "0.3.0",
-      "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.3.0.tgz",
-      "integrity": "sha512-zza72oZBBHzt64G7DxdqrOo/30bhHkwMUoT0WqfGu98XLd7N+1tsy5MJ96Bk4MD0y74n629RhmrGW6XlnLLwCA==",
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz",
+      "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==",
       "requires": {
         "jsesc": "0.5.0"
       }
@@ -1474,9 +1471,9 @@
       }
     },
     "rimraf": {
-      "version": "2.6.2",
-      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
-      "integrity": "sha1-LtgVDSShbqhlHm1u8PR8QVjOejY=",
+      "version": "2.6.3",
+      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
+      "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
       "dev": true,
       "requires": {
         "glob": "7.1.3"
@@ -1493,9 +1490,9 @@
       }
     },
     "rollup-plugin-buble": {
-      "version": "0.19.4",
-      "resolved": "https://registry.npmjs.org/rollup-plugin-buble/-/rollup-plugin-buble-0.19.4.tgz",
-      "integrity": "sha512-mahvTRn9mUVKEUyA5QbrfdybOH7tipHRe4zkKZjEGMB3YSaLW95kHFSB4Vdt7BofyG9r7zMCCmAsEqZKkRaN6A==",
+      "version": "0.19.6",
+      "resolved": "https://registry.npmjs.org/rollup-plugin-buble/-/rollup-plugin-buble-0.19.6.tgz",
+      "integrity": "sha512-El5Fut4/wEO17ZN/n9BZvqd7DXXB2WbJr/DKvr89LXChC/cHllE0XwiUDeAalrTkgr0WrnyLDTCQvEv+cGywWQ==",
       "dev": true,
       "requires": {
         "buble": "0.19.6",
@@ -1640,21 +1637,13 @@
       "dev": true
     },
     "source-map-support": {
-      "version": "0.5.9",
-      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz",
-      "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==",
+      "version": "0.5.10",
+      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.10.tgz",
+      "integrity": "sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ==",
       "dev": true,
       "requires": {
         "buffer-from": "1.1.1",
         "source-map": "0.6.1"
-      },
-      "dependencies": {
-        "source-map": {
-          "version": "0.6.1",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-          "dev": true
-        }
       }
     },
     "sourcemap-codec": {
@@ -1726,14 +1715,14 @@
       "requires": {
         "ajv": "5.5.2",
         "ajv-keywords": "2.1.1",
-        "chalk": "2.4.1",
+        "chalk": "2.4.2",
         "lodash": "4.17.11",
         "slice-ansi": "1.0.0",
         "string-width": "2.1.1"
       }
     },
     "test262": {
-      "version": "git+https://github.com/tc39/test262.git#238c88d4a084d9928372954e2fec54af2c951281",
+      "version": "git+https://github.com/tc39/test262.git#4f1155c566a222238fd86f179c6635ecb4c289bb",
       "dev": true
     },
     "test262-parser": {
diff --git a/package.json b/package.json
index ddbf651..918c9d1 100644
--- a/package.json
+++ b/package.json
@@ -53,29 +53,29 @@
   },
   "homepage": "https://github.com/Rich-Harris/buble#README",
   "devDependencies": {
-    "acorn": "^6.0.4",
-    "acorn-dynamic-import": "^4.0.0",
-    "acorn-jsx": "^5.0.0",
     "console-group": "^0.3.3",
     "eslint": "^4.19.1",
     "glob": "^7.1.3",
     "mocha": "^5.2.0",
-    "rimraf": "^2.5.2",
+    "rimraf": "^2.6.3",
     "rollup": "0.66.0",
-    "rollup-plugin-buble": "^0.19.4",
+    "rollup-plugin-buble": "^0.19.6",
     "rollup-plugin-commonjs": "=9.1.8",
     "rollup-plugin-json": "^3.1.0",
     "rollup-plugin-node-resolve": "^3.4.0",
     "source-map": "^0.6.1",
-    "source-map-support": "^0.5.9",
-    "test262": "git+https://github.com/tc39/test262.git#238c88d4a084d9928372954e2fec54af2c951281",
+    "source-map-support": "^0.5.10",
+    "test262": "git+https://github.com/tc39/test262.git#4f1155c566a222238fd86f179c6635ecb4c289bb",
     "test262-stream": "^1.2.0"
   },
   "dependencies": {
-    "chalk": "^2.4.1",
+    "acorn": "^6.0.5",
+    "acorn-dynamic-import": "^4.0.0",
+    "acorn-jsx": "^5.0.1",
+    "chalk": "^2.4.2",
     "magic-string": "^0.25.1",
     "minimist": "^1.2.0",
     "os-homedir": "^1.0.1",
-    "regexpu-core": "^4.2.0"
+    "regexpu-core": "^4.4.0"
   }
 }
diff --git a/rollup.create-config.js b/rollup.create-config.js
index 3b860b4..e35ddd9 100644
--- a/rollup.create-config.js
+++ b/rollup.create-config.js
@@ -9,7 +9,7 @@ const ensureArray = maybeArr => Array.isArray(maybeArr) ? maybeArr : [maybeArr];
 const createConfig = (opts) => {
 	opts = opts || {};
 	const browser = opts.browser || false;
-	const external = opts.external || Object.keys(pkg.dependencies || {}).filter(dep => !dep.match(/^acorn/));
+	const external = opts.external || Object.keys(pkg.dependencies || {});
 	const output = ensureArray(opts.output);
 
 	return {
@@ -26,8 +26,8 @@ const createConfig = (opts) => {
 				target: !browser ? { node: 4 } : null,
 				include: [
 					'src/**',
-					'node_modules/acorn-jsx/**',
 					// For unpkg build
+					'node_modules/acorn-jsx/**',
 					'node_modules/regexpu-core/**',
 					'node_modules/unicode-match-property-ecmascript/**',
 					'node_modules/unicode-match-property-value-ecmascript/**',
diff --git a/src/program/types/ArrayExpression.js b/src/program/types/ArrayExpression.js
index bf363bd..6565f59 100644
--- a/src/program/types/ArrayExpression.js
+++ b/src/program/types/ArrayExpression.js
@@ -1,5 +1,5 @@
 import Node from '../Node.js';
-import spread, { isArguments } from '../../utils/spread.js';
+import spread, { isArguments, inlineSpreads } from '../../utils/spread.js';
 
 export default class ArrayExpression extends Node {
 	initialise(transforms) {
@@ -26,6 +26,7 @@ export default class ArrayExpression extends Node {
 		super.transpile(code, transforms);
 
 		if (transforms.spreadRest) {
+			inlineSpreads(code, this, this.elements);
 			// erase trailing comma after last array element if not an array hole
 			if (this.elements.length) {
 				const lastElement = this.elements[this.elements.length - 1];
diff --git a/src/program/types/CallExpression.js b/src/program/types/CallExpression.js
index 3671f52..1e0eee4 100644
--- a/src/program/types/CallExpression.js
+++ b/src/program/types/CallExpression.js
@@ -1,5 +1,5 @@
 import Node from '../Node.js';
-import spread, { isArguments } from '../../utils/spread.js';
+import spread, { isArguments, inlineSpreads } from '../../utils/spread.js';
 import removeTrailingComma from '../../utils/removeTrailingComma.js';
 
 export default class CallExpression extends Node {
@@ -20,6 +20,11 @@ export default class CallExpression extends Node {
 	}
 
 	transpile(code, transforms) {
+		if (transforms.spreadRest && this.arguments.length) {
+			inlineSpreads(code, this, this.arguments);
+			// this.arguments.length may have changed, must retest.
+		}
+
 		if (transforms.spreadRest && this.arguments.length) {
 			let hasSpreadElements = false;
 			let context;
diff --git a/src/program/types/NewExpression.js b/src/program/types/NewExpression.js
index ccf9b8d..747749d 100644
--- a/src/program/types/NewExpression.js
+++ b/src/program/types/NewExpression.js
@@ -1,5 +1,5 @@
 import Node from '../Node.js';
-import spread, { isArguments } from '../../utils/spread.js';
+import spread, { isArguments, inlineSpreads } from '../../utils/spread.js';
 import removeTrailingComma from '../../utils/removeTrailingComma.js';
 
 export default class NewExpression extends Node {
@@ -23,6 +23,11 @@ export default class NewExpression extends Node {
 	transpile(code, transforms) {
 		super.transpile(code, transforms);
 
+		if (transforms.spreadRest && this.arguments.length) {
+			inlineSpreads(code, this, this.arguments);
+			// this.arguments.length may have changed, must retest.
+		}
+
 		if (transforms.spreadRest && this.arguments.length) {
 			const firstArgument = this.arguments[0];
 			const isNew = true;
diff --git a/src/program/types/ObjectExpression.js b/src/program/types/ObjectExpression.js
index 06a09fa..151de7e 100644
--- a/src/program/types/ObjectExpression.js
+++ b/src/program/types/ObjectExpression.js
@@ -14,8 +14,35 @@ export default class ObjectExpression extends Node {
 		for (let i = 0; i < this.properties.length; ++i) {
 			const prop = this.properties[i];
 			if (prop.type === 'SpreadElement') {
-				spreadPropertyCount += 1;
-				if (firstSpreadProperty === null) firstSpreadProperty = i;
+				// First see if we can inline the spread, to save needing objectAssign.
+				const argument = prop.argument;
+				if (
+					argument.type === 'ObjectExpression' || (
+						argument.type === 'Literal' &&
+						typeof argument.value !== 'string'
+					)
+				) {
+					if (argument.type === 'ObjectExpression' && argument.properties.length > 0) {
+						// Strip the `...{` and the `}` with a possible trailing comma before it,
+						// leaving just the possible trailing comma after it.
+						code.remove(prop.start, argument.properties[0].start);
+						code.remove(argument.properties[argument.properties.length - 1].end, prop.end);
+						this.properties.splice(i, 1, ...argument.properties);
+						i--;
+					} else {
+						// An empty object, boolean, null, undefined, number or regexp (but NOT
+						// string) will spread to nothing, so just remove the element altogether,
+						// including a possible trailing comma.
+						code.remove(prop.start, i === this.properties.length - 1
+							? prop.end
+							: this.properties[i + 1].start);
+						this.properties.splice(i, 1);
+						i--;
+					}
+				} else {
+					spreadPropertyCount += 1;
+					if (firstSpreadProperty === null) firstSpreadProperty = i;
+				}
 			} else if (prop.computed && transforms.computedProperty) {
 				computedPropertyCount += 1;
 				if (firstComputedProperty === null) firstComputedProperty = i;
diff --git a/src/utils/spread.js b/src/utils/spread.js
index ce2a10c..bfca294 100644
--- a/src/utils/spread.js
+++ b/src/utils/spread.js
@@ -2,6 +2,59 @@ export function isArguments(node) {
 	return node.type === 'Identifier' && node.name === 'arguments';
 }
 
+export function inlineSpreads(
+	code,
+	node,
+	elements
+) {
+	let i = elements.length;
+
+	while (i--) {
+		const element = elements[i];
+		if (!element || element.type !== 'SpreadElement') {
+			continue;
+		}
+		const argument = element.argument;
+		if (argument.type !== 'ArrayExpression') {
+			continue;
+		}
+		const subelements = argument.elements;
+		if (subelements.some(subelement => subelement === null)) {
+			// Not even going to try inlining spread arrays with holes.
+			// It's a lot of work (got to be VERY careful in comma counting for
+			// ArrayExpression, and turn blanks into undefined for
+			// CallExpression and NewExpression), and probably literally no one
+			// would ever benefit from it.
+			continue;
+		}
+		// We can inline it: drop the `...[` and `]` and sort out any commas.
+		const isLast = i === elements.length - 1;
+		if (subelements.length === 0) {
+			code.remove(
+				isLast && i !== 0
+					? elements[i - 1].end  // Take the previous comma too
+					: element.start,
+				isLast
+					? node.end - 1  // Must remove trailing comma; element.end wouldn’t
+					: elements[i + 1].start);
+		} else {
+			// Strip the `...[` and the `]` with a possible trailing comma before it,
+			// leaving just the possible trailing comma after it.
+			code.remove(element.start, subelements[0].start);
+			code.remove(
+				// Strip a possible trailing comma after the last element
+				subelements[subelements.length - 1].end,
+				// And also a possible trailing comma after the spread
+				isLast
+					? node.end - 1
+					: element.end
+			);
+		}
+		elements.splice(i, 1, ...subelements);
+		i += subelements.length;
+	}
+}
+
 export default function spread(
 	code,
 	elements,


Test patch:
diff --git a/scripts/test262-data.js b/scripts/test262-data.js
index cd8b914..7ea7034 100644
--- a/scripts/test262-data.js
+++ b/scripts/test262-data.js
@@ -156,43 +156,43 @@ exports.file_list = [
 
 	// buble bug: buble emits invalid code
 	{ level: 2, desc: "bubleBadArrowTranspiling", files: [
-		"language/expressions/assignment/dstr-array-elem-init-fn-name-arrow.js",
-		"language/expressions/assignment/dstr-obj-prop-elem-init-fn-name-arrow.js",
-		"language/expressions/assignment/dstr-obj-id-init-fn-name-arrow.js",
+		"language/expressions/assignment/dstr/array-elem-init-fn-name-arrow.js",
+		"language/expressions/assignment/dstr/obj-prop-elem-init-fn-name-arrow.js",
+		"language/expressions/assignment/dstr/obj-id-init-fn-name-arrow.js",
 		"language/expressions/object/fn-name-class.js",
 	]},
 
 	// buble bug: name for (arrow) function expressions wrong
 	{ level: 1, desc: "functionName", files: [
-		"language/statements/for/dstr-const-ary-ptrn-elem-id-init-fn-name-arrow.js",
-		"language/statements/for/dstr-const-obj-ptrn-id-init-fn-name-arrow.js",
-		"language/statements/for/dstr-const-obj-ptrn-id-init-fn-name-cover.js",
-		"language/statements/for/dstr-const-obj-ptrn-id-init-fn-name-fn.js",
-		"language/statements/for/dstr-let-ary-ptrn-elem-id-init-fn-name-arrow.js",
-		"language/statements/for/dstr-let-obj-ptrn-id-init-fn-name-arrow.js",
-		"language/statements/for/dstr-let-obj-ptrn-id-init-fn-name-cover.js",
-		"language/statements/for/dstr-let-obj-ptrn-id-init-fn-name-fn.js",
-		"language/statements/for/dstr-var-ary-ptrn-elem-id-init-fn-name-arrow.js",
-		"language/statements/for/dstr-var-ary-ptrn-elem-id-init-fn-name-cover.js",
-		"language/statements/for/dstr-var-ary-ptrn-elem-id-init-fn-name-fn.js",
-		"language/statements/for/dstr-var-obj-ptrn-id-init-fn-name-arrow.js",
-		"language/statements/for/dstr-var-obj-ptrn-id-init-fn-name-cover.js",
-		"language/statements/for/dstr-var-obj-ptrn-id-init-fn-name-fn.js",
-		"language/statements/for-of/dstr-array-elem-init-fn-name-arrow.js",
-		"language/statements/for-of/dstr-array-elem-init-fn-name-cover.js",
-		"language/statements/for-of/dstr-array-elem-init-fn-name-fn.js",
-		"language/statements/for-of/dstr-obj-id-init-fn-name-arrow.js",
-		"language/statements/for-of/dstr-obj-id-init-fn-name-cover.js",
-		"language/statements/for-of/dstr-obj-id-init-fn-name-fn.js",
-		"language/statements/for-of/dstr-obj-prop-elem-init-fn-name-arrow.js",
-		"language/statements/for-of/dstr-obj-prop-elem-init-fn-name-cover.js",
-		"language/statements/for-of/dstr-obj-prop-elem-init-fn-name-fn.js",
-		"language/expressions/assignment/dstr-obj-prop-elem-init-fn-name-cover.js",
-		"language/expressions/assignment/dstr-obj-prop-elem-init-fn-name-fn.js",
-		"language/expressions/assignment/dstr-obj-id-init-fn-name-cover.js",
-		"language/expressions/assignment/dstr-obj-id-init-fn-name-fn.js",
-		"language/expressions/assignment/dstr-array-elem-init-fn-name-cover.js",
-		"language/expressions/assignment/dstr-array-elem-init-fn-name-fn.js",
+		"language/statements/for/dstr/const-ary-ptrn-elem-id-init-fn-name-arrow.js",
+		"language/statements/for/dstr/const-obj-ptrn-id-init-fn-name-arrow.js",
+		"language/statements/for/dstr/const-obj-ptrn-id-init-fn-name-cover.js",
+		"language/statements/for/dstr/const-obj-ptrn-id-init-fn-name-fn.js",
+		"language/statements/for/dstr/let-ary-ptrn-elem-id-init-fn-name-arrow.js",
+		"language/statements/for/dstr/let-obj-ptrn-id-init-fn-name-arrow.js",
+		"language/statements/for/dstr/let-obj-ptrn-id-init-fn-name-cover.js",
+		"language/statements/for/dstr/let-obj-ptrn-id-init-fn-name-fn.js",
+		"language/statements/for/dstr/var-ary-ptrn-elem-id-init-fn-name-arrow.js",
+		"language/statements/for/dstr/var-ary-ptrn-elem-id-init-fn-name-cover.js",
+		"language/statements/for/dstr/var-ary-ptrn-elem-id-init-fn-name-fn.js",
+		"language/statements/for/dstr/var-obj-ptrn-id-init-fn-name-arrow.js",
+		"language/statements/for/dstr/var-obj-ptrn-id-init-fn-name-cover.js",
+		"language/statements/for/dstr/var-obj-ptrn-id-init-fn-name-fn.js",
+		"language/statements/for-of/dstr/array-elem-init-fn-name-arrow.js",
+		"language/statements/for-of/dstr/array-elem-init-fn-name-cover.js",
+		"language/statements/for-of/dstr/array-elem-init-fn-name-fn.js",
+		"language/statements/for-of/dstr/obj-id-init-fn-name-arrow.js",
+		"language/statements/for-of/dstr/obj-id-init-fn-name-cover.js",
+		"language/statements/for-of/dstr/obj-id-init-fn-name-fn.js",
+		"language/statements/for-of/dstr/obj-prop-elem-init-fn-name-arrow.js",
+		"language/statements/for-of/dstr/obj-prop-elem-init-fn-name-cover.js",
+		"language/statements/for-of/dstr/obj-prop-elem-init-fn-name-fn.js",
+		"language/expressions/assignment/dstr/obj-prop-elem-init-fn-name-cover.js",
+		"language/expressions/assignment/dstr/obj-prop-elem-init-fn-name-fn.js",
+		"language/expressions/assignment/dstr/obj-id-init-fn-name-cover.js",
+		"language/expressions/assignment/dstr/obj-id-init-fn-name-fn.js",
+		"language/expressions/assignment/dstr/array-elem-init-fn-name-cover.js",
+		"language/expressions/assignment/dstr/array-elem-init-fn-name-fn.js",
 		"language/expressions/class/name.js",
 	]},
 
@@ -432,7 +432,7 @@ exports.file_list = [
 		// Object.assign
 		"*spread-obj-getter-init.js",
 		// rest
-		"language/expressions/assignment/dstr-obj-rest-order.js",
+		"language/expressions/assignment/dstr/obj-rest-order.js",
 	]},
 
 	// buble bug: characters which are valid in template literal but not string literal need to be transpiled
@@ -506,11 +506,11 @@ exports.file_list = [
 	// 'var a = []; for (let [x] of []) a.push(() => x)'
 	// buble bug: scope with classes
 	{ level: 1, desc: "lexicalScopingWithLoopsAndClassesAndStuff", files: [
-		"language/statements/for-of/dstr-array-rest-nested-array-null.js",
-		"language/statements/for-of/dstr-array-rest-nested-obj-null.js",
-		"language/statements/for-of/dstr-array-rest-nested-obj-undefined-hole.js",
-		"language/statements/for-of/dstr-array-rest-nested-obj-undefined-own.js",
-		"language/statements/for-of/dstr-array-rest-nested-obj-undefined.js",
+		"language/statements/for-of/dstr/array-rest-nested-array-null.js",
+		"language/statements/for-of/dstr/array-rest-nested-obj-null.js",
+		"language/statements/for-of/dstr/array-rest-nested-obj-undefined-hole.js",
+		"language/statements/for-of/dstr/array-rest-nested-obj-undefined-own.js",
+		"language/statements/for-of/dstr/array-rest-nested-obj-undefined.js",
 
 		"language/expressions/class/scope-name-lex-open-heritage.js",
 		"language/expressions/class/scope-name-lex-open-no-heritage.js",
@@ -562,16 +562,16 @@ exports.file_list = [
 
 	// buble bug: complicated destructuring
 	{ level: 2, desc: "complicatedDestructuring", files: [
-		"language/statements/for-of/dstr-array-elem-iter-thrw-close-err.js",
-		"language/statements/for-of/dstr-array-elem-iter-thrw-close.js",
-		"language/statements/for-of/dstr-array-elem-trlg-iter-list-thrw-close-err.js",
-		"language/statements/for-of/dstr-array-elem-trlg-iter-list-thrw-close.js",
-		"language/statements/for-of/dstr-array-elem-trlg-iter-rest-thrw-close-err.js",
-		"language/statements/for-of/dstr-array-elem-trlg-iter-rest-thrw-close.js",
-		"language/statements/for-of/dstr-array-rest-iter-thrw-close-err.js",
-		"language/statements/for-of/dstr-array-rest-iter-thrw-close.js",
-		"language/statements/for-of/dstr-array-rest-lref-err.js",
-		"language/statements/for-of/dstr-array-rest-nested-array-iter-thrw-close-skip.js",
+		"language/statements/for-of/dstr/array-elem-iter-thrw-close-err.js",
+		"language/statements/for-of/dstr/array-elem-iter-thrw-close.js",
+		"language/statements/for-of/dstr/array-elem-trlg-iter-list-thrw-close-err.js",
+		"language/statements/for-of/dstr/array-elem-trlg-iter-list-thrw-close.js",
+		"language/statements/for-of/dstr/array-elem-trlg-iter-rest-thrw-close-err.js",
+		"language/statements/for-of/dstr/array-elem-trlg-iter-rest-thrw-close.js",
+		"language/statements/for-of/dstr/array-rest-iter-thrw-close-err.js",
+		"language/statements/for-of/dstr/array-rest-iter-thrw-close.js",
+		"language/statements/for-of/dstr/array-rest-lref-err.js",
+		"language/statements/for-of/dstr/array-rest-nested-array-iter-thrw-close-skip.js",
 	]},
 
 	// buble bug: does not close over let in for head
@@ -601,20 +601,20 @@ exports.file_list = [
 	]},
 
 	{ level: 2, desc: "destructuringArrayElidedElements", files: [
-		"*assignment/dstr-array-elision-*"
+		"*assignment/dstr/array-elision-*"
 	]},
 
 	{ level: 1, desc: "destructuringArrayElidedElements", files: [
-		"*dstr-array-(elision|empty)-(iter|val-(bool|null|num|symbol|undef))*"
+		"*dstr/array-(elision|empty)-(iter|val-(bool|null|num|symbol|undef))*"
 	]},
 
 	{ level: 1, desc: "destructuringNullUndefined", files: [
-		"language/expressions/assignment/dstr-obj-rest-val-null.js",
-		"language/expressions/assignment/dstr-obj-rest-val-undefined.js",
+		"language/expressions/assignment/dstr/obj-rest-val-null.js",
+		"language/expressions/assignment/dstr/obj-rest-val-undefined.js",
 	]},
 
 	{ level: 2, desc: "destructuringEmptyVal", files: [
-		"*assignment/dstr-(array|obj)-empty*"
+		"*assignment/dstr/(array|obj)-empty*"
 	]},
 
 	{ level: 4, desc: "generators", files: [
@@ -637,39 +637,39 @@ exports.file_list = [
 		"annexB/built-ins/RegExp/RegExp-invalid-control-escape-character-class.js",
 		"language/expressions/array/spread-err-mult-err-expr-throws.js",
 		"language/expressions/array/spread-err-sngl-err-expr-throws.js",
-		"*(ary-elision-init|ary-elision-iter|ary-empty-init|elision-exhausted|elision-step-err|ptrn-elision|dstr-(meth-|const-|let-|var-)?(static-)?(dflt-)?ary-ptrn(-rest-ary)?-empty|rest-ary-elision|rest-ary-empty|rest-id-elision-next-err|rest-id-iter-step-err).js",
+		"*(ary-elision-init|ary-elision-iter|ary-empty-init|elision-exhausted|elision-step-err|ptrn-elision|dstr/(meth-|const-|let-|var-)?(static-)?(dflt-)?ary-ptrn(-rest-ary)?-empty|rest-ary-elision|rest-ary-empty|rest-id-elision-next-err|rest-id-iter-step-err).js",
 		"*for*(elision|id)-iter-close.js",
-		"language/statements/for/dstr-let-ary-init-iter-close.js",
-		"language/statements/for/dstr-const-ary-init-iter-close.js",
+		"language/statements/for/dstr/let-ary-init-iter-close.js",
+		"language/statements/for/dstr/const-ary-init-iter-close.js",
 		"language/statements/for-await-of/async-func-dstr-const-async-ary-init-iter-close.js",
 		"language/statements/for-await-of/async-func-dstr-var-async-ary-init-iter-close.js",
 		"language/statements/for-await-of/async-func-dstr-let-async-ary-init-iter-close.js",
-		"language/expressions/assignment/dstr-array-elem-init-yield-expr.js",
-		"language/expressions/assignment/dstr-array-elem-iter-rtrn-close-err.js",
-		"language/expressions/assignment/dstr-array-elem-iter-rtrn-close-null.js",
-		"language/expressions/assignment/dstr-array-elem-iter-rtrn-close.js",
-		"language/expressions/assignment/dstr-array-elem-nested-array-yield-expr.js",
-		"language/expressions/assignment/dstr-array-elem-nested-obj-yield-expr.js",
-		"language/expressions/assignment/dstr-array-elem-target-yield-expr.js",
-		"language/expressions/assignment/dstr-array-elem-trlg-iter-list-rtrn-close-null.js",
-		"language/expressions/assignment/dstr-array-elem-trlg-iter-list-rtrn-close-err.js",
-		"language/expressions/assignment/dstr-array-elem-trlg-iter-list-rtrn-close.js",
-		"language/expressions/assignment/dstr-array-elem-trlg-iter-rest-rtrn-close-err.js",
-		"language/expressions/assignment/dstr-array-elem-trlg-iter-rest-rtrn-close-null.js",
-		"language/expressions/assignment/dstr-array-elem-trlg-iter-rest-rtrn-close.js",
-		"language/expressions/assignment/dstr-array-iteration.js",
-		"language/expressions/assignment/dstr-array-rest-iter-rtrn-close-err.js",
-		"language/expressions/assignment/dstr-array-rest-iter-rtrn-close-null.js",
-		"language/expressions/assignment/dstr-array-rest-iter-rtrn-close.js",
-		"language/expressions/assignment/dstr-array-rest-iteration.js",
-		"language/expressions/assignment/dstr-array-rest-nested-array-yield-expr.js",
-		"language/expressions/assignment/dstr-array-rest-nested-obj-yield-expr.js",
-		"language/expressions/assignment/dstr-array-rest-yield-expr.js",
-		"language/expressions/assignment/dstr-obj-id-init-yield-expr.js",
-		"language/expressions/assignment/dstr-obj-prop-elem-init-yield-expr.js",
-		"language/expressions/assignment/dstr-obj-prop-elem-target-yield-expr.js",
-		"language/expressions/assignment/dstr-obj-prop-nested-array-yield-expr.js",
-		"language/expressions/assignment/dstr-obj-prop-nested-obj-yield-expr.js",
+		"language/expressions/assignment/dstr/array-elem-init-yield-expr.js",
+		"language/expressions/assignment/dstr/array-elem-iter-rtrn-close-err.js",
+		"language/expressions/assignment/dstr/array-elem-iter-rtrn-close-null.js",
+		"language/expressions/assignment/dstr/array-elem-iter-rtrn-close.js",
+		"language/expressions/assignment/dstr/array-elem-nested-array-yield-expr.js",
+		"language/expressions/assignment/dstr/array-elem-nested-obj-yield-expr.js",
+		"language/expressions/assignment/dstr/array-elem-target-yield-expr.js",
+		"language/expressions/assignment/dstr/array-elem-trlg-iter-list-rtrn-close-null.js",
+		"language/expressions/assignment/dstr/array-elem-trlg-iter-list-rtrn-close-err.js",
+		"language/expressions/assignment/dstr/array-elem-trlg-iter-list-rtrn-close.js",
+		"language/expressions/assignment/dstr/array-elem-trlg-iter-rest-rtrn-close-err.js",
+		"language/expressions/assignment/dstr/array-elem-trlg-iter-rest-rtrn-close-null.js",
+		"language/expressions/assignment/dstr/array-elem-trlg-iter-rest-rtrn-close.js",
+		"language/expressions/assignment/dstr/array-iteration.js",
+		"language/expressions/assignment/dstr/array-rest-iter-rtrn-close-err.js",
+		"language/expressions/assignment/dstr/array-rest-iter-rtrn-close-null.js",
+		"language/expressions/assignment/dstr/array-rest-iter-rtrn-close.js",
+		"language/expressions/assignment/dstr/array-rest-iteration.js",
+		"language/expressions/assignment/dstr/array-rest-nested-array-yield-expr.js",
+		"language/expressions/assignment/dstr/array-rest-nested-obj-yield-expr.js",
+		"language/expressions/assignment/dstr/array-rest-yield-expr.js",
+		"language/expressions/assignment/dstr/obj-id-init-yield-expr.js",
+		"language/expressions/assignment/dstr/obj-prop-elem-init-yield-expr.js",
+		"language/expressions/assignment/dstr/obj-prop-elem-target-yield-expr.js",
+		"language/expressions/assignment/dstr/obj-prop-nested-array-yield-expr.js",
+		"language/expressions/assignment/dstr/obj-prop-nested-obj-yield-expr.js",
 		"language/expressions/call/spread-err-mult-err-expr-throws.js",
 		"language/expressions/call/spread-err-sngl-err-expr-throws.js",
 		"language/expressions/new/spread-err-mult-err-expr-throws.js",
@@ -751,32 +751,32 @@ exports.file_list = [
 		"language/statements/for-of/continue-label-from-try.js",
 		"language/statements/for-of/continue-label.js",
 		"language/statements/for-of/continue.js",
-		"language/statements/for-of/dstr-array-elem-init-yield-expr.js",
-		"language/statements/for-of/dstr-array-elem-iter-rtrn-close-err.js",
-		"language/statements/for-of/dstr-array-elem-iter-rtrn-close-null.js",
-		"language/statements/for-of/dstr-array-elem-iter-rtrn-close.js",
-		"language/statements/for-of/dstr-array-elem-nested-array-yield-expr.js",
-		"language/statements/for-of/dstr-array-elem-nested-obj-yield-expr.js",
-		"language/statements/for-of/dstr-array-elem-target-yield-expr.js",
-		"language/statements/for-of/dstr-array-elem-trlg-iter-list-rtrn-close-err.js",
-		"language/statements/for-of/dstr-array-elem-trlg-iter-list-rtrn-close-null.js",
-		"language/statements/for-of/dstr-array-elem-trlg-iter-list-rtrn-close.js",
-		"language/statements/for-of/dstr-array-elem-trlg-iter-rest-rtrn-close-err.js",
-		"language/statements/for-of/dstr-array-elem-trlg-iter-rest-rtrn-close-null.js",
-		"language/statements/for-of/dstr-array-elem-trlg-iter-rest-rtrn-close.js",
-		"language/statements/for-of/dstr-array-iteration.js",
-		"language/statements/for-of/dstr-array-rest-iter-rtrn-close-err.js",
-		"language/statements/for-of/dstr-array-rest-iter-rtrn-close-null.js",
-		"language/statements/for-of/dstr-array-rest-iter-rtrn-close.js",
-		"language/statements/for-of/dstr-array-rest-iteration.js",
-		"language/statements/for-of/dstr-array-rest-nested-array-yield-expr.js",
-		"language/statements/for-of/dstr-array-rest-nested-obj-yield-expr.js",
-		"language/statements/for-of/dstr-array-rest-yield-expr.js",
-		"language/statements/for-of/dstr-obj-id-init-yield-expr.js",
-		"language/statements/for-of/dstr-obj-prop-elem-init-yield-expr.js",
-		"language/statements/for-of/dstr-obj-prop-elem-target-yield-expr.js",
-		"language/statements/for-of/dstr-obj-prop-nested-array-yield-expr.js",
-		"language/statements/for-of/dstr-obj-prop-nested-obj-yield-expr.js",
+		"language/statements/for-of/dstr/array-elem-init-yield-expr.js",
+		"language/statements/for-of/dstr/array-elem-iter-rtrn-close-err.js",
+		"language/statements/for-of/dstr/array-elem-iter-rtrn-close-null.js",
+		"language/statements/for-of/dstr/array-elem-iter-rtrn-close.js",
+		"language/statements/for-of/dstr/array-elem-nested-array-yield-expr.js",
+		"language/statements/for-of/dstr/array-elem-nested-obj-yield-expr.js",
+		"language/statements/for-of/dstr/array-elem-target-yield-expr.js",
+		"language/statements/for-of/dstr/array-elem-trlg-iter-list-rtrn-close-err.js",
+		"language/statements/for-of/dstr/array-elem-trlg-iter-list-rtrn-close-null.js",
+		"language/statements/for-of/dstr/array-elem-trlg-iter-list-rtrn-close.js",
+		"language/statements/for-of/dstr/array-elem-trlg-iter-rest-rtrn-close-err.js",
+		"language/statements/for-of/dstr/array-elem-trlg-iter-rest-rtrn-close-null.js",
+		"language/statements/for-of/dstr/array-elem-trlg-iter-rest-rtrn-close.js",
+		"language/statements/for-of/dstr/array-iteration.js",
+		"language/statements/for-of/dstr/array-rest-iter-rtrn-close-err.js",
+		"language/statements/for-of/dstr/array-rest-iter-rtrn-close-null.js",
+		"language/statements/for-of/dstr/array-rest-iter-rtrn-close.js",
+		"language/statements/for-of/dstr/array-rest-iteration.js",
+		"language/statements/for-of/dstr/array-rest-nested-array-yield-expr.js",
+		"language/statements/for-of/dstr/array-rest-nested-obj-yield-expr.js",
+		"language/statements/for-of/dstr/array-rest-yield-expr.js",
+		"language/statements/for-of/dstr/obj-id-init-yield-expr.js",
+		"language/statements/for-of/dstr/obj-prop-elem-init-yield-expr.js",
+		"language/statements/for-of/dstr/obj-prop-elem-target-yield-expr.js",
+		"language/statements/for-of/dstr/obj-prop-nested-array-yield-expr.js",
+		"language/statements/for-of/dstr/obj-prop-nested-obj-yield-expr.js",
 		"language/statements/for-of/nested.js",
 		"language/statements/for-of/return-from-catch.js",
 		"language/statements/for-of/return-from-finally.js",
@@ -834,24 +834,24 @@ exports.file_list = [
 
 	// buble bug: cannot transpile static method called name
 	{ level: 1, desc: "staticName", files: [
-		"*/(dstr-(array-elem|(|const-|dflt-|let-|meth-|meth-dflt-|meth-static-|meth-static-dflt-)(ary-ptrn-elem|obj-ptrn)-id|obj-id|obj-prop-elem|var-ary-ptrn-elem-id|var-obj-ptrn-id)-init-)?fn-name-class.js",
+		"*/(dstr/(array-elem|(|const-|dflt-|let-|meth-|meth-dflt-|meth-static-|meth-static-dflt-)(ary-ptrn-elem|obj-ptrn)-id|obj-id|obj-prop-elem|var-ary-ptrn-elem-id|var-obj-ptrn-id)-init-)?fn-name-class.js",
 	]},
 
 	{ level: 1, desc: "spreadForOfIterators", files: [
 		"annexB/language/statements/for-of/iterator-close-return-emulates-undefined-throws-when-called.js",
-		"language/expressions/assignment/dstr-array-rest-lref-err.js",
+		"language/expressions/assignment/dstr/array-rest-lref-err.js",
 		"language/statements/for-of/head-expr-to-obj.js",
 		"language/statements/for-of/head-expr-primitive-iterator-method.js",
 		"language/statements/for-of/head-expr-obj-iterator-method.js",
-		"language/statements/for-of/dstr-obj-empty-undef.js",
-		"language/statements/for-of/dstr-obj-empty-null.js",
+		"language/statements/for-of/dstr/obj-empty-undef.js",
+		"language/statements/for-of/dstr/obj-empty-null.js",
 		"built-ins/ArrayIteratorPrototype/next/detach-typedarray-in-progress.js",
 		"language/statements/for-of/Array.prototype.(entries|keys|Symbol.iterator).js",
 		"language/statements/for-of/body-dstr-assign-error.js",
 		"*call-spread-err-(mult|sngl)-err-i*",
 		"*call-spread-(mult|sngl)-iter*",
 		"language/statements/class/constructor-inferred-observable-iteration.js",
-		"*dstr-(array|(const-|dflt-|let-|meth-|meth-dflt-|meth-static-|meth-static-dflt-|var-)?ary)-((elem-(|trlg-)|elision-|empty-|rest-(elision-)?|*-)iter-(val-err|step-err|close|get-err|(list-|rest-)?(nrml-|thrw-|)close(-err|-null|-skip|)|abpt)|rest-lref).js",
+		"*dstr/(array|(const-|dflt-|let-|meth-|meth-dflt-|meth-static-|meth-static-dflt-|var-)?ary)-((elem-(|trlg-)|elision-|empty-|rest-(elision-)?|*-)iter-(val-err|step-err|close|get-err|(list-|rest-)?(nrml-|thrw-|)close(-err|-null|-skip|)|abpt)|rest-lref).js",
 		"language/statements/for-of/generic-iterable.js",
 		"language/statements/for-of/iterator-*",
 		"*spread-err-(mult|sngl)-err-it*",
@@ -862,8 +862,8 @@ exports.file_list = [
 	// buble bug: emits code with invalid runtime behavior for rest properties in for-of loop heads
 	// https://github.com/Rich-Harris/buble/issues/110
 	{ level: 1, desc: "invalidRestPropertiesForOf", files: [
-		"*/for-of/dstr-obj-rest-(c|d|e|n|o|p|s|t|v)*",
-		"*/for-of/dstr-obj-rest-getter.js",
+		"*/for-of/dstr/obj-rest-(c|d|e|n|o|p|s|t|v)*",
+		"*/for-of/dstr/obj-rest-getter.js",
 	]},
 
 	// buble bug: subclassing of builtins does not work
@@ -952,6 +952,9 @@ exports.skip_list = [
 	]},
 
 	// Runtime behaviour
+	{ desc: 'runtimeRegexp', files: [
+		'built-ins/RegExp/duplicate-flags.js',
+	] },
 	{ desc: "tco", files: [ "language/(expressions/tagged-template|statements/for)/tco-*" ] },
 	{ desc: "runtimeBigInt", files: [
 		"built-ins/BigInt/asIntN/bigint-tobigint-errors.js",
@@ -1099,4 +1102,7 @@ exports.skip_list = [
 	{ desc: "node4Bug_arrayLength", files: [ "built-ins/Array/prototype/*integer-limit*" ] },
 
 	{ desc: "node4Bug_anonFunctionName", files: [ "*fn-name-(arrow|cover|fn).js" ] },
+
+	// Node < 11
+	{ desc: "nodeBug_unstableSort", files: [ "built-ins/Array/prototype/sort/stability-(11|513|2048)*" ] },
 ].map(i => ({ desc: i.desc, pattern: mapFilePatterns(i.files) }));
diff --git a/test/samples/computed-properties.js b/test/samples/computed-properties.js
index e0c55ed..b0955d3 100644
--- a/test/samples/computed-properties.js
+++ b/test/samples/computed-properties.js
@@ -238,7 +238,7 @@ module.exports = [
 			let a = {
 				[foo] (x, y) {
 					return {
-						...{abc: '123'}
+						...c
 					};
 				},
 			};
@@ -246,7 +246,7 @@ module.exports = [
 		output: `
 			var a = {};
 			a[foo] = function (x, y) {
-					return Object.assign({}, {abc: '123'});
+					return Object.assign({}, c);
 				};
 		`
 	},
diff --git a/test/samples/object-rest-spread.js b/test/samples/object-rest-spread.js
index abc319e..6716d61 100644
--- a/test/samples/object-rest-spread.js
+++ b/test/samples/object-rest-spread.js
@@ -277,6 +277,61 @@ for( var a = c.a, rest = objectWithoutProperties( c, ["a"] ), b = rest;; ) {}`
 	input: `for( var {...b} = c;; ) {}`,
 	output: `function objectWithoutProperties (obj, exclude) { 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;; ) {}`
-	}
+	},
+
+	{
+		description: 'inlines object spread with one object',
+		input: `var obj = {...{a: 1}};`,
+		output: `var obj = {a: 1};`
+	},
+
+	{
+		description: 'inlines object spread with two objects',
+		input: `var obj = {...{a: 1}, ...{b: 2}};`,
+		output: `var obj = {a: 1, b: 2};`
+	},
 
+	{
+		description: 'inlines object spread with regular keys in between',
+		input: `var obj = { ...{a: 1}, b: 2, c: 3 };`,
+		output: `var obj = { a: 1, b: 2, c: 3 };`
+	},
+
+	{
+		description: 'inlines object spread mixed',
+		input: `var obj = { ...{a: 1}, b: 2, ...{c: 3}, e};`,
+		output: `var obj = { a: 1, b: 2, c: 3, e: e};`
+	},
+
+	{
+		description: 'inlines object spread very mixed',
+		options: {
+			objectAssign: 'Object.assign'
+		},
+		input: `var obj = { ...{a: 1}, b: 2, ...c, e};`,
+		output: `var obj = Object.assign({}, {a: 1, b: 2}, c, {e: e});`
+	},
+
+	{
+		description: 'inlines object spread without extraneous trailing commas',
+		options: {
+			objectAssign: 'Object.assign'
+		},
+		input: `
+			var obj = { ...{a: 1,}, b: 2, ...{c: 3,}, ...d, e, ...{f: 6,},};
+			obj = { a: 1, b: 2, };
+			obj = { a: 1, ...{b: 2} };
+			obj = { a: 1, ...{b: 2,} };
+			obj = { a: 1, ...{b: 2}, };
+			obj = { a: 1, ...{b: 2,}, };
+		`,
+		output: `
+			var obj = Object.assign({}, {a: 1, b: 2, c: 3}, d, {e: e, f: 6});
+			obj = { a: 1, b: 2, };
+			obj = { a: 1, b: 2 };
+			obj = { a: 1, b: 2 };
+			obj = { a: 1, b: 2, };
+			obj = { a: 1, b: 2, };
+		`
+	}
 ];
diff --git a/test/samples/spread-operator.js b/test/samples/spread-operator.js
index 6cda801..3adb3d8 100644
--- a/test/samples/spread-operator.js
+++ b/test/samples/spread-operator.js
@@ -435,6 +435,25 @@ module.exports = [
 				console.log(JSON.stringify(this.a));
 			}
 			var obj = { Test };
+			var a = [1, 2];
+			var b = [3, 4];
+			var c = [7, 8];
+
+			new Test(...a);
+			new obj.Test(...a);
+			new (null || obj).Test(...a);
+
+			new Test(0, ...a);
+			new obj.Test(0, ...a);
+			new (null || obj).Test(0, ...a);
+
+			new Test(...a, ...b, 5);
+			new obj.Test(...a, ...b, 5);
+			new (null || obj).Test(...a, ...b, 5);
+
+			new Test(...a, new Test(...c), ...b, 5);
+			new obj.Test(...a, new Test(...c), ...b, 5);
+			new (null || obj).Test(...a, new Test(...c), ...b, 5);
 
 			new Test(...[1, 2]);
 			new obj.Test(...[1, 2]);
@@ -471,22 +490,41 @@ module.exports = [
 				console.log(JSON.stringify(this.a));
 			}
 			var obj = { Test: Test };
+			var a = [1, 2];
+			var b = [3, 4];
+			var c = [7, 8];
+
+			new (Function.prototype.bind.apply( Test, [ null ].concat( a) ));
+			new (Function.prototype.bind.apply( obj.Test, [ null ].concat( a) ));
+			new (Function.prototype.bind.apply( (null || obj).Test, [ null ].concat( a) ));
+
+			new (Function.prototype.bind.apply( Test, [ null ].concat( [0], a) ));
+			new (Function.prototype.bind.apply( obj.Test, [ null ].concat( [0], a) ));
+			new (Function.prototype.bind.apply( (null || obj).Test, [ null ].concat( [0], a) ));
 
-			new (Function.prototype.bind.apply( Test, [ null ].concat( [1, 2]) ));
-			new (Function.prototype.bind.apply( obj.Test, [ null ].concat( [1, 2]) ));
-			new (Function.prototype.bind.apply( (null || obj).Test, [ null ].concat( [1, 2]) ));
+			new (Function.prototype.bind.apply( Test, [ null ].concat( a, b, [5]) ));
+			new (Function.prototype.bind.apply( obj.Test, [ null ].concat( a, b, [5]) ));
+			new (Function.prototype.bind.apply( (null || obj).Test, [ null ].concat( a, b, [5]) ));
 
-			new (Function.prototype.bind.apply( Test, [ null ].concat( [0], [1, 2]) ));
-			new (Function.prototype.bind.apply( obj.Test, [ null ].concat( [0], [1, 2]) ));
-			new (Function.prototype.bind.apply( (null || obj).Test, [ null ].concat( [0], [1, 2]) ));
+			new (Function.prototype.bind.apply( Test, [ null ].concat( a, [new (Function.prototype.bind.apply( Test, [ null ].concat( c) ))], b, [5]) ));
+			new (Function.prototype.bind.apply( obj.Test, [ null ].concat( a, [new (Function.prototype.bind.apply( Test, [ null ].concat( c) ))], b, [5]) ));
+			new (Function.prototype.bind.apply( (null || obj).Test, [ null ].concat( a, [new (Function.prototype.bind.apply( Test, [ null ].concat( c) ))], b, [5]) ));
 
-			new (Function.prototype.bind.apply( Test, [ null ].concat( [1, 2], [3, 4], [5]) ));
-			new (Function.prototype.bind.apply( obj.Test, [ null ].concat( [1, 2], [3, 4], [5]) ));
-			new (Function.prototype.bind.apply( (null || obj).Test, [ null ].concat( [1, 2], [3, 4], [5]) ));
+			new Test(1, 2);
+			new obj.Test(1, 2);
+			new (null || obj).Test(1, 2);
 
-			new (Function.prototype.bind.apply( Test, [ null ].concat( [1, 2], [new (Function.prototype.bind.apply( Test, [ null ].concat( [7, 8]) ))], [3, 4], [5]) ));
-			new (Function.prototype.bind.apply( obj.Test, [ null ].concat( [1, 2], [new (Function.prototype.bind.apply( Test, [ null ].concat( [7, 8]) ))], [3, 4], [5]) ));
-			new (Function.prototype.bind.apply( (null || obj).Test, [ null ].concat( [1, 2], [new (Function.prototype.bind.apply( Test, [ null ].concat( [7, 8]) ))], [3, 4], [5]) ));
+			new Test(0, 1, 2);
+			new obj.Test(0, 1, 2);
+			new (null || obj).Test(0, 1, 2);
+
+			new Test(1, 2, 3, 4, 5);
+			new obj.Test(1, 2, 3, 4, 5);
+			new (null || obj).Test(1, 2, 3, 4, 5);
+
+			new Test(1, 2, new Test(7, 8), 3, 4, 5);
+			new obj.Test(1, 2, new Test(7, 8), 3, 4, 5);
+			new (null || obj).Test(1, 2, new Test(7, 8), 3, 4, 5);
 
 			(function () {
 				var i = arguments.length, argsArray = Array(i);
@@ -617,5 +655,231 @@ module.exports = [
 		input: `new X(...A, () => 'B')`,
 
 		output: `new (Function.prototype.bind.apply( X, [ null ].concat( A, [function () { return 'B'; }]) ))`
-	}
+	},
+
+	{
+		description: 'inlines unreasonably deep spreads',
+		input: `
+			[...[...[...[1, ...[...[...[2, 3]], 4]]]]];
+			f(...[...[...[1, ...[...[...[2, 3]], 4]]]]);
+			new f(...[...[...[1, ...[...[...[2, 3]], 4]]]]);
+		`,
+		output: `
+			[1, 2, 3, 4];
+			f(1, 2, 3, 4);
+			new f(1, 2, 3, 4);
+		`
+	},
+
+	{
+		description: 'does not (yet) inline spread arrays with holes',
+		input: `
+			[...[,]];
+			f(...[,]);
+			new f(...[,]);
+		`,
+		output: `
+			[].concat( [,] );
+			f.apply(void 0, [,]);
+			new (Function.prototype.bind.apply( f, [ null ].concat( [,]) ));
+		`
+	},
+
+	{
+		description: 'inlines array spreads without extraneous trailing commas',
+		input: `
+			[...[]];
+			[...[],];
+			[...[x]];
+			[...[x,]];
+			[...[x, y]];
+			[...[x, y,]];
+			[...[x, y],];
+			[...[x, y,],];
+
+			[w, ...[]];
+			[w, ...[],];
+			[w, ...[x]];
+			[w, ...[x,]];
+			[w, ...[x, y]];
+			[w, ...[x, y,]];
+			[w, ...[x, y],];
+			[w, ...[x, y,],];
+
+			[...[], z];
+			[...[x], z];
+			[...[x,], z];
+			[...[x, y], z];
+			[...[x, y,], z];
+
+			[w, ...[], z];
+			[w, ...[x], z];
+			[w, ...[x,], z];
+			[w, ...[x, y], z];
+			[w, ...[x, y,], z];
+		`,
+		output: `
+			[];
+			[];
+			[x];
+			[x ];
+			[x, y];
+			[x, y ];
+			[x, y ];
+			[x, y ];
+
+			[w ];
+			[w ];
+			[w, x];
+			[w, x ];
+			[w, x, y];
+			[w, x, y ];
+			[w, x, y ];
+			[w, x, y ];
+
+			[z];
+			[x, z];
+			[x, z];
+			[x, y, z];
+			[x, y, z];
+
+			[w, z];
+			[w, x, z];
+			[w, x, z];
+			[w, x, y, z];
+			[w, x, y, z];
+		`
+	},
+
+	{
+		description: 'inlines call spreads without extraneous trailing commas',
+		input: `
+			f(...[]);
+			f(...[],);
+			f(...[x]);
+			f(...[x,]);
+			f(...[x, y]);
+			f(...[x, y,]);
+			f(...[x, y],);
+			f(...[x, y,],);
+
+			f(w, ...[]);
+			f(w, ...[],);
+			f(w, ...[x]);
+			f(w, ...[x,]);
+			f(w, ...[x, y]);
+			f(w, ...[x, y,]);
+			f(w, ...[x, y],);
+			f(w, ...[x, y,],);
+
+			f(...[], z);
+			f(...[x], z);
+			f(...[x,], z);
+			f(...[x, y], z);
+			f(...[x, y,], z);
+
+			f(w, ...[], z);
+			f(w, ...[x], z);
+			f(w, ...[x,], z);
+			f(w, ...[x, y], z);
+			f(w, ...[x, y,], z);
+		`,
+		output: `
+			f();
+			f();
+			f(x);
+			f(x);
+			f(x, y);
+			f(x, y);
+			f(x, y);
+			f(x, y);
+
+			f(w);
+			f(w);
+			f(w, x);
+			f(w, x);
+			f(w, x, y);
+			f(w, x, y);
+			f(w, x, y);
+			f(w, x, y);
+
+			f(z);
+			f(x, z);
+			f(x, z);
+			f(x, y, z);
+			f(x, y, z);
+
+			f(w, z);
+			f(w, x, z);
+			f(w, x, z);
+			f(w, x, y, z);
+			f(w, x, y, z);
+		`
+	},
+
+	{
+		description: 'inlines new call spreads without extraneous trailing commas',
+		input: `
+			new f(...[]);
+			new f(...[],);
+			new f(...[x]);
+			new f(...[x,]);
+			new f(...[x, y]);
+			new f(...[x, y,]);
+			new f(...[x, y],);
+			new f(...[x, y,],);
+
+			new f(w, ...[]);
+			new f(w, ...[],);
+			new f(w, ...[x]);
+			new f(w, ...[x,]);
+			new f(w, ...[x, y]);
+			new f(w, ...[x, y,]);
+			new f(w, ...[x, y],);
+			new f(w, ...[x, y,],);
+
+			new f(...[], z);
+			new f(...[x], z);
+			new f(...[x,], z);
+			new f(...[x, y], z);
+			new f(...[x, y,], z);
+
+			new f(w, ...[], z);
+			new f(w, ...[x], z);
+			new f(w, ...[x,], z);
+			new f(w, ...[x, y], z);
+			new f(w, ...[x, y,], z);
+		`,
+		output: `
+			new f();
+			new f();
+			new f(x);
+			new f(x);
+			new f(x, y);
+			new f(x, y);
+			new f(x, y);
+			new f(x, y);
+
+			new f(w);
+			new f(w);
+			new f(w, x);
+			new f(w, x);
+			new f(w, x, y);
+			new f(w, x, y);
+			new f(w, x, y);
+			new f(w, x, y);
+
+			new f(z);
+			new f(x, z);
+			new f(x, z);
+			new f(x, y, z);
+			new f(x, y, z);
+
+			new f(w, z);
+			new f(w, x, z);
+			new f(w, x, z);
+			new f(w, x, y, z);
+			new f(w, x, y, z);
+		`
+	},
 ];