diff --git a/src/language-js/parser-babel.js b/src/language-js/parser-babel.js
index 030a61d7e..588c6cab5 100644
--- a/src/language-js/parser-babel.js
+++ b/src/language-js/parser-babel.js
@@ -16,22 +16,16 @@ function babelOptions({ sourceType, extraPlugins = [] }) {
errorRecovery: true,
createParenthesizedExpressions: true,
plugins: [
+ // When adding a plugin, please add a test in `tests/js/babel-plugins`,
+ // To remove plugins, remove it here and run `yarn test tests/js/babel-plugins` to verify
+
"doExpressions",
- "objectRestSpread",
"classProperties",
"exportDefaultFrom",
- "exportNamespaceFrom",
- "asyncGenerators",
"functionBind",
"functionSent",
- "dynamicImport",
"numericSeparator",
- "importMeta",
- "optionalCatchBinding",
- "optionalChaining",
"classPrivateProperties",
- "nullishCoalescingOperator",
- "bigInt",
"throwExpressions",
"logicalAssignment",
"classPrivateMethods",
diff --git a/tests/js/babel-plugins/__snapshots__/jsfmt.spec.js.snap b/tests/js/babel-plugins/__snapshots__/jsfmt.spec.js.snap
new file mode 100644
index 000000000..914731348
--- /dev/null
+++ b/tests/js/babel-plugins/__snapshots__/jsfmt.spec.js.snap
@@ -0,0 +1,1529 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`async-generators.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-async-generator-functions
+
+async function* agf() {
+ await 1;
+ yield 2;
+}
+
+=====================================output=====================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-async-generator-functions
+
+async function* agf() {
+ await 1;
+ yield 2;
+}
+
+================================================================================
+`;
+
+exports[`bigint.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+// https://github.com/tc39/proposal-bigint
+
+const previousMaxSafe = BigInt(Number.MAX_SAFE_INTEGER);
+// ↪ 9007199254740991
+
+const maxPlusOne = previousMaxSafe + 1n;
+// ↪ 9007199254740992n
+
+const theFuture = previousMaxSafe + 2n;
+// ↪ 9007199254740993n, this works now!
+
+const multi = previousMaxSafe * 2n;
+// ↪ 18014398509481982n
+
+// \`–\` is not minus sign,
+// SIC https://github.com/tc39/proposal-bigint#operators
+// const subtr = multi – 10n;
+// ↪ 18014398509481972n
+
+const mod = multi % 10n;
+// ↪ 2n
+
+const bigN = 2n ** 54n;
+// ↪ 18014398509481984n
+
+bigN * -1n
+// ↪ –18014398509481984n
+
+0n === 0
+// ↪ false
+
+0n == 0
+// ↪ true
+
+1n < 2
+// ↪ true
+
+2n > 1
+// ↪ true
+
+2 > 2
+// ↪ false
+
+2n > 2
+// ↪ false
+
+2n >= 2
+// ↪ true
+
+const mixed = [4n, 6, -12n, 10, 4, 0, 0n];
+// ↪ [4n, 6, -12n, 10, 4, 0, 0n]
+
+mixed.sort();
+// ↪ [-12n, 0, 0n, 10, 4n, 4, 6]
+
+if (0n) {
+ console.log('Hello from the if!');
+} else {
+ console.log('Hello from the else!');
+}
+
+// ↪ "Hello from the else!"
+
+0n || 12n
+// ↪ 12n
+
+0n && 12n
+// ↪ 0n
+
+Boolean(0n)
+// ↪ false
+
+Boolean(12n)
+// ↪ true
+
+!12n
+// ↪ false
+
+!0n
+// ↪ true
+
+const view = new BigInt64Array(4);
+// ↪ [0n, 0n, 0n, 0n]
+view.length;
+// ↪ 4
+view[0];
+// ↪ 0n
+view[0] = 42n;
+view[0];
+// ↪ 42n
+
+// Highest possible BigInt value that can be represented as a
+// signed 64-bit integer.
+const max = 2n ** (64n - 1n) - 1n;
+view[0] = max;
+view[0];
+// ↪ 9_223_372_036_854_775_807n
+view[0] = max + 1n;
+view[0];
+// ↪ -9_223_372_036_854_775_808n
+// ^ negative because of overflow
+
+1n + 2
+// ↪ TypeError: Cannot mix BigInt and other types, use explicit conversions
+
+1n * 2
+// ↪ TypeError: Cannot mix BigInt and other types, use explicit conversions
+
++1n
+// ↪ TypeError: Cannot convert a BigInt value to a number
+
+Number(1n)
+// ↪ 1
+
+1n + '2'
+// ↪ "12"
+
+'2' + 1n
+// ↪ "21"
+
+const badPrecision = BigInt(9007199254740993);
+// ↪9007199254740992n
+
+const goodPrecision = BigInt('9007199254740993');
+// ↪9007199254740993n
+
+const alsoGoodPrecision = 9007199254740993n;
+// ↪9007199254740993n
+
+=====================================output=====================================
+// https://github.com/tc39/proposal-bigint
+
+const previousMaxSafe = BigInt(Number.MAX_SAFE_INTEGER);
+// ↪ 9007199254740991
+
+const maxPlusOne = previousMaxSafe + 1n;
+// ↪ 9007199254740992n
+
+const theFuture = previousMaxSafe + 2n;
+// ↪ 9007199254740993n, this works now!
+
+const multi = previousMaxSafe * 2n;
+// ↪ 18014398509481982n
+
+// \`–\` is not minus sign,
+// SIC https://github.com/tc39/proposal-bigint#operators
+// const subtr = multi – 10n;
+// ↪ 18014398509481972n
+
+const mod = multi % 10n;
+// ↪ 2n
+
+const bigN = 2n ** 54n;
+// ↪ 18014398509481984n
+
+bigN * -1n;
+// ↪ –18014398509481984n
+
+0n === 0;
+// ↪ false
+
+0n == 0;
+// ↪ true
+
+1n < 2;
+// ↪ true
+
+2n > 1;
+// ↪ true
+
+2 > 2;
+// ↪ false
+
+2n > 2;
+// ↪ false
+
+2n >= 2;
+// ↪ true
+
+const mixed = [4n, 6, -12n, 10, 4, 0, 0n];
+// ↪ [4n, 6, -12n, 10, 4, 0, 0n]
+
+mixed.sort();
+// ↪ [-12n, 0, 0n, 10, 4n, 4, 6]
+
+if (0n) {
+ console.log("Hello from the if!");
+} else {
+ console.log("Hello from the else!");
+}
+
+// ↪ "Hello from the else!"
+
+0n || 12n;
+// ↪ 12n
+
+0n && 12n;
+// ↪ 0n
+
+Boolean(0n);
+// ↪ false
+
+Boolean(12n);
+// ↪ true
+
+!12n;
+// ↪ false
+
+!0n;
+// ↪ true
+
+const view = new BigInt64Array(4);
+// ↪ [0n, 0n, 0n, 0n]
+view.length;
+// ↪ 4
+view[0];
+// ↪ 0n
+view[0] = 42n;
+view[0];
+// ↪ 42n
+
+// Highest possible BigInt value that can be represented as a
+// signed 64-bit integer.
+const max = 2n ** (64n - 1n) - 1n;
+view[0] = max;
+view[0];
+// ↪ 9_223_372_036_854_775_807n
+view[0] = max + 1n;
+view[0];
+// ↪ -9_223_372_036_854_775_808n
+// ^ negative because of overflow
+
+1n + 2;
+// ↪ TypeError: Cannot mix BigInt and other types, use explicit conversions
+
+1n * 2 +
+ // ↪ TypeError: Cannot mix BigInt and other types, use explicit conversions
+
+ 1n;
+// ↪ TypeError: Cannot convert a BigInt value to a number
+
+Number(1n);
+// ↪ 1
+
+1n + "2";
+// ↪ "12"
+
+"2" + 1n;
+// ↪ "21"
+
+const badPrecision = BigInt(9007199254740993);
+// ↪9007199254740992n
+
+const goodPrecision = BigInt("9007199254740993");
+// ↪9007199254740993n
+
+const alsoGoodPrecision = 9007199254740993n;
+// ↪9007199254740993n
+
+================================================================================
+`;
+
+exports[`class-properties.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-class-properties
+
+class Bork {
+ //Property initializer syntax
+ instanceProperty = "bork";
+ boundFunction = () => {
+ return this.instanceProperty;
+ };
+
+ //Static class properties
+ static staticProperty = "babelIsCool";
+ static staticFunction = function() {
+ return Bork.staticProperty;
+ };
+ }
+
+ let myBork = new Bork;
+
+ //Property initializers are not on the prototype.
+ console.log(myBork.__proto__.boundFunction); // > undefined
+
+ //Bound functions are bound to the class instance.
+ console.log(myBork.boundFunction.call(undefined)); // > "bork"
+
+ //Static function exists on the class.
+ console.log(Bork.staticFunction()); // > "babelIsCool"
+
+=====================================output=====================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-class-properties
+
+class Bork {
+ //Property initializer syntax
+ instanceProperty = "bork";
+ boundFunction = () => {
+ return this.instanceProperty;
+ };
+
+ //Static class properties
+ static staticProperty = "babelIsCool";
+ static staticFunction = function () {
+ return Bork.staticProperty;
+ };
+}
+
+let myBork = new Bork();
+
+//Property initializers are not on the prototype.
+console.log(myBork.__proto__.boundFunction); // > undefined
+
+//Bound functions are bound to the class instance.
+console.log(myBork.boundFunction.call(undefined)); // > "bork"
+
+//Static function exists on the class.
+console.log(Bork.staticFunction()); // > "babelIsCool"
+
+================================================================================
+`;
+
+exports[`decorators.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-decorators
+
+@annotation
+class MyClass { }
+
+function annotation(target) {
+ target.annotated = true;
+}
+
+@isTestable(true)
+class MyClass { }
+
+function isTestable(value) {
+ return function decorator(target) {
+ target.isTestable = value;
+ }
+}
+
+class C {
+ @enumerable(false)
+ method() { }
+}
+
+function enumerable(value) {
+ return function (target, key, descriptor) {
+ descriptor.enumerable = value;
+ return descriptor;
+ }
+}
+
+=====================================output=====================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-decorators
+
+@annotation
+class MyClass {}
+
+function annotation(target) {
+ target.annotated = true;
+}
+
+@isTestable(true)
+class MyClass {}
+
+function isTestable(value) {
+ return function decorator(target) {
+ target.isTestable = value;
+ };
+}
+
+class C {
+ @enumerable(false)
+ method() {}
+}
+
+function enumerable(value) {
+ return function (target, key, descriptor) {
+ descriptor.enumerable = value;
+ return descriptor;
+ };
+}
+
+================================================================================
+`;
+
+exports[`do-expressions.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-do-expressions
+
+let a = do {
+ if(x > 10) {
+ 'big';
+ } else {
+ 'small';
+ }
+};
+// is equivalent to:
+let a = x > 10 ? 'big' : 'small';
+
+=====================================output=====================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-do-expressions
+
+let a = do {
+ if (x > 10) {
+ ("big");
+ } else {
+ ("small");
+ }
+};
+// is equivalent to:
+let a = x > 10 ? "big" : "small";
+
+================================================================================
+`;
+
+exports[`dynamic-import.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+// https://babeljs.io/docs/en/babel-plugin-syntax-dynamic-import
+
+// There is no example code on babel website
+
+import('./prettier.mjs');
+import(prettier);
+import('./prettier.mjs').then(module => console.log(module));
+import(prettier).then(module => console.log(module));
+
+=====================================output=====================================
+// https://babeljs.io/docs/en/babel-plugin-syntax-dynamic-import
+
+// There is no example code on babel website
+
+import("./prettier.mjs");
+import(prettier);
+import("./prettier.mjs").then((module) => console.log(module));
+import(prettier).then((module) => console.log(module));
+
+================================================================================
+`;
+
+exports[`export-default-from.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-export-default-from
+
+
+export v from 'mod';
+
+=====================================output=====================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-export-default-from
+
+export v from "mod";
+
+================================================================================
+`;
+
+exports[`export-namespace-from.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-export-namespace-from
+
+export * as ns from 'mod';
+
+=====================================output=====================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-export-namespace-from
+
+export * as ns from "mod";
+
+================================================================================
+`;
+
+exports[`flow.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+// https://babeljs.io/docs/en/babel-preset-flow
+
+function foo(one: any, two: number, three?): string {}
+
+=====================================output=====================================
+// https://babeljs.io/docs/en/babel-preset-flow
+
+function foo(one: any, two: number, three?): string {}
+
+================================================================================
+`;
+
+exports[`function-bind.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-function-bind
+
+obj::func
+// is equivalent to:
+func.bind(obj)
+
+::obj.func
+// is equivalent to:
+obj.func.bind(obj)
+
+obj::func(val)
+// is equivalent to:
+func.call(obj, val)
+
+::obj.func(val)
+// is equivalent to:
+obj.func.call(obj, val)
+
+=====================================output=====================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-function-bind
+
+obj::func;
+// is equivalent to:
+func.bind(obj)::obj.func;
+// is equivalent to:
+obj.func.bind(obj);
+
+obj::func(val);
+// is equivalent to:
+func
+ .call(obj, val)
+
+ ::obj.func(val);
+// is equivalent to:
+obj.func.call(obj, val);
+
+================================================================================
+`;
+
+exports[`function-sent.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-function-sent
+
+function* generator() {
+ console.log("Sent", function.sent);
+ console.log("Yield", yield);
+}
+
+const iterator = generator();
+iterator.next(1); // Logs "Sent 1"
+iterator.next(2); // Logs "Yield 2"
+
+=====================================output=====================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-function-sent
+
+function* generator() {
+ console.log("Sent", function.sent);
+ console.log("Yield", yield);
+}
+
+const iterator = generator();
+iterator.next(1); // Logs "Sent 1"
+iterator.next(2); // Logs "Yield 2"
+
+================================================================================
+`;
+
+exports[`import-meta.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+// https://babeljs.io/docs/en/babel-plugin-syntax-import-meta
+
+// Enabled by default https://github.com/babel/babel/pull/11406
+
+// from https://github.com/tc39/proposal-import-meta
+
+(async () => {
+ const response = await fetch(new URL("../hamsters.jpg", import.meta.url));
+ const blob = await response.blob();
+
+ const size = import.meta.scriptElement.dataset.size || 300;
+
+ const image = new Image();
+ image.src = URL.createObjectURL(blob);
+ image.width = image.height = size;
+
+ document.body.appendChild(image);
+})();
+
+=====================================output=====================================
+// https://babeljs.io/docs/en/babel-plugin-syntax-import-meta
+
+// Enabled by default https://github.com/babel/babel/pull/11406
+
+// from https://github.com/tc39/proposal-import-meta
+
+(async () => {
+ const response = await fetch(new URL("../hamsters.jpg", import.meta.url));
+ const blob = await response.blob();
+
+ const size = import.meta.scriptElement.dataset.size || 300;
+
+ const image = new Image();
+ image.src = URL.createObjectURL(blob);
+ image.width = image.height = size;
+
+ document.body.appendChild(image);
+})();
+
+================================================================================
+`;
+
+exports[`jsx.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+// https://babeljs.io/docs/en/babel-plugin-syntax-jsx
+
+var profile =
+

+
{[user.firstName, user.lastName].join(' ')}
+
;
+
+=====================================output=====================================
+// https://babeljs.io/docs/en/babel-plugin-syntax-jsx
+
+var profile = (
+
+

+
{[user.firstName, user.lastName].join(" ")}
+
+);
+
+================================================================================
+`;
+
+exports[`logical-assignment-operators.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-logical-assignment-operators
+
+a ||= b;
+obj.a.b ||= c;
+
+a &&= b;
+obj.a.b &&= c;
+
+=====================================output=====================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-logical-assignment-operators
+
+a ||= b;
+obj.a.b ||= c;
+
+a &&= b;
+obj.a.b &&= c;
+
+================================================================================
+`;
+
+exports[`nullish-coalescing-operator.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-nullish-coalescing-operator
+
+var foo = object.foo ?? "default";
+
+=====================================output=====================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-nullish-coalescing-operator
+
+var foo = object.foo ?? "default";
+
+================================================================================
+`;
+
+exports[`numeric-separator.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-numeric-separator
+
+let budget = 1_000_000_000_000;
+
+// What is the value of \`budget\`? It's 1 trillion!
+//
+// Let's confirm:
+console.log(budget === 10 ** 12); // true
+
+let nibbles = 0b1010_0001_1000_0101;
+
+// Is bit 7 on? It sure is!
+// 0b1010_0001_1000_0101
+// ^
+//
+// We can double check:
+console.log(!!(nibbles & (1 << 7))); // true
+
+// Messages are sent as 24 bit values, but should be
+// treated as 3 distinct bytes:
+let message = 0xa0_b0_c0;
+
+// What's the value of the upper most byte? It's A0, or 160.
+// We can confirm that:
+let a = (message >> 16) & 0xff;
+console.log(a.toString(16), a); // a0, 160
+
+// What's the value of the middle byte? It's B0, or 176.
+// Let's just make sure...
+let b = (message >> 8) & 0xff;
+console.log(b.toString(16), b); // b0, 176
+
+// What's the value of the lower most byte? It's C0, or 192.
+// Again, let's prove that:
+let c = message & 0xff;
+console.log(c.toString(16), b); // c0, 192
+
+=====================================output=====================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-numeric-separator
+
+let budget = 1_000_000_000_000;
+
+// What is the value of \`budget\`? It's 1 trillion!
+//
+// Let's confirm:
+console.log(budget === 10 ** 12); // true
+
+let nibbles = 0b1010_0001_1000_0101;
+
+// Is bit 7 on? It sure is!
+// 0b1010_0001_1000_0101
+// ^
+//
+// We can double check:
+console.log(!!(nibbles & (1 << 7))); // true
+
+// Messages are sent as 24 bit values, but should be
+// treated as 3 distinct bytes:
+let message = 0xa0_b0_c0;
+
+// What's the value of the upper most byte? It's A0, or 160.
+// We can confirm that:
+let a = (message >> 16) & 0xff;
+console.log(a.toString(16), a); // a0, 160
+
+// What's the value of the middle byte? It's B0, or 176.
+// Let's just make sure...
+let b = (message >> 8) & 0xff;
+console.log(b.toString(16), b); // b0, 176
+
+// What's the value of the lower most byte? It's C0, or 192.
+// Again, let's prove that:
+let c = message & 0xff;
+console.log(c.toString(16), b); // c0, 192
+
+================================================================================
+`;
+
+exports[`object-rest-spread.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+// https://babeljs.io/docs/en/babel-plugin-transform-object-rest-spread
+
+let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
+console.log(x); // 1
+console.log(y); // 2
+console.log(z); // { a: 3, b: 4 }
+
+let n = { x, y, ...z };
+console.log(n); // { x: 1, y: 2, a: 3, b: 4 }
+
+=====================================output=====================================
+// https://babeljs.io/docs/en/babel-plugin-transform-object-rest-spread
+
+let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
+console.log(x); // 1
+console.log(y); // 2
+console.log(z); // { a: 3, b: 4 }
+
+let n = { x, y, ...z };
+console.log(n); // { x: 1, y: 2, a: 3, b: 4 }
+
+================================================================================
+`;
+
+exports[`optional-catch-binding.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-optional-catch-binding
+
+try {
+ throw 0;
+} catch {
+ doSomethingWhichDoesNotCareAboutTheValueThrown();
+}
+
+try {
+ throw 0;
+} catch {
+ doSomethingWhichDoesNotCareAboutTheValueThrown();
+} finally {
+ doSomeCleanup();
+}
+
+=====================================output=====================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-optional-catch-binding
+
+try {
+ throw 0;
+} catch {
+ doSomethingWhichDoesNotCareAboutTheValueThrown();
+}
+
+try {
+ throw 0;
+} catch {
+ doSomethingWhichDoesNotCareAboutTheValueThrown();
+} finally {
+ doSomeCleanup();
+}
+
+================================================================================
+`;
+
+exports[`optional-chaining.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-optional-chaining
+
+const obj = {
+ foo: {
+ bar: {
+ baz: 42,
+ },
+ },
+};
+
+const baz = obj?.foo?.bar?.baz; // 42
+
+const safe = obj?.qux?.baz; // undefined
+
+// Optional chaining and normal chaining can be intermixed
+obj?.foo.bar?.baz; // Only access \`foo\` if \`obj\` exists, and \`baz\` if
+ // \`bar\` exists
+
+// Example usage with bracket notation:
+obj?.['foo']?.bar?.baz // 42
+
+const obj = {
+ foo: {
+ bar: {
+ baz() {
+ return 42;
+ },
+ },
+ },
+};
+
+const baz = obj?.foo?.bar?.baz(); // 42
+
+const safe = obj?.qux?.baz(); // undefined
+const safe2 = obj?.foo.bar.qux?.(); // undefined
+
+const willThrow = obj?.foo.bar.qux(); // Error: not a function
+
+// Top function can be called directly, too.
+function test() {
+ return 42;
+}
+test?.(); // 42
+
+exists?.(); // undefined
+
+const obj = {
+ foo: {
+ bar: {
+ baz: class {
+ },
+ },
+ },
+};
+
+const baz = new obj?.foo?.bar?.baz(); // baz instance
+
+const safe = new obj?.qux?.baz(); // undefined
+const safe2 = new obj?.foo.bar.qux?.(); // undefined
+
+const willThrow = new obj?.foo.bar.qux(); // Error: not a constructor
+
+// Top classes can be called directly, too.
+class Test {
+}
+new Test?.(); // test instance
+
+new exists?.(); // undefined
+
+const obj = {
+ foo: {
+ bar: {}
+ },
+};
+
+const ret = delete obj?.foo?.bar?.baz; // true
+
+=====================================output=====================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-optional-chaining
+
+const obj = {
+ foo: {
+ bar: {
+ baz: 42,
+ },
+ },
+};
+
+const baz = obj?.foo?.bar?.baz; // 42
+
+const safe = obj?.qux?.baz; // undefined
+
+// Optional chaining and normal chaining can be intermixed
+obj?.foo.bar?.baz; // Only access \`foo\` if \`obj\` exists, and \`baz\` if
+// \`bar\` exists
+
+// Example usage with bracket notation:
+obj?.["foo"]?.bar?.baz; // 42
+
+const obj = {
+ foo: {
+ bar: {
+ baz() {
+ return 42;
+ },
+ },
+ },
+};
+
+const baz = obj?.foo?.bar?.baz(); // 42
+
+const safe = obj?.qux?.baz(); // undefined
+const safe2 = obj?.foo.bar.qux?.(); // undefined
+
+const willThrow = obj?.foo.bar.qux(); // Error: not a function
+
+// Top function can be called directly, too.
+function test() {
+ return 42;
+}
+test?.(); // 42
+
+exists?.(); // undefined
+
+const obj = {
+ foo: {
+ bar: {
+ baz: class {},
+ },
+ },
+};
+
+const baz = new (obj?.foo?.bar?.baz)(); // baz instance
+
+const safe = new (obj?.qux?.baz)(); // undefined
+const safe2 = new (obj?.foo.bar.qux)()?.(); // undefined
+
+const willThrow = new (obj?.foo.bar.qux)(); // Error: not a constructor
+
+// Top classes can be called directly, too.
+class Test {}
+new Test(); // test instance
+
+new exists(); // undefined
+
+const obj = {
+ foo: {
+ bar: {},
+ },
+};
+
+const ret = delete obj?.foo?.bar?.baz; // true
+
+================================================================================
+`;
+
+exports[`partial-application.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-partial-application
+
+function add(x, y) { return x + y; }
+
+const addOne = add(1, ?); // apply from the left
+addOne(2); // 3
+
+const addTen = add(?, 10); // apply from the right
+addTen(2); // 12
+
+let newScore = player.score
+ |> add(7, ?)
+ |> clamp(0, 100, ?); // shallow stack, the pipe to \`clamp\` is the same frame as the pipe to \`add\`.
+
+f(x, ?) // partial application from left
+f(?, x) // partial application from right
+f(?, x, ?) // partial application for any arg
+o.f(x, ?) // partial application from left
+o.f(?, x) // partial application from right
+o.f(?, x, ?) // partial application for any arg
+super.f(?) // partial application allowed for call on |SuperProperty|
+
+=====================================output=====================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-partial-application
+
+function add(x, y) {
+ return x + y;
+}
+
+const addOne = add(1, ?); // apply from the left
+addOne(2); // 3
+
+const addTen = add(?, 10); // apply from the right
+addTen(2); // 12
+
+let newScore = player.score |> add(7, ?) |> clamp(0, 100, ?); // shallow stack, the pipe to \`clamp\` is the same frame as the pipe to \`add\`.
+
+f(x, ?); // partial application from left
+f(?, x); // partial application from right
+f(?, x, ?); // partial application for any arg
+o.f(x, ?); // partial application from left
+o.f(?, x); // partial application from right
+o.f(?, x, ?); // partial application for any arg
+super.f(?); // partial application allowed for call on |SuperProperty|
+
+================================================================================
+`;
+
+exports[`pipeline-operator-fsharp.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-pipeline-operator
+// https://github.com/valtech-nyc/proposal-fsharp-pipelines
+
+promise
+ |> await
+ |> x => doubleSay(x, ', ')
+ |> capitalize
+ |> x => x + '!'
+ |> x => new User.Message(x)
+ |> x => stream.write(x)
+ |> await
+ |> console.log;
+
+const result = exclaim(capitalize(doubleSay("hello")));
+result //=> "Hello, hello!"
+
+const result = "hello"
+ |> doubleSay
+ |> capitalize
+ |> exclaim;
+
+result //=> "Hello, hello!"
+
+const person = { score: 25 };
+
+const newScore = person.score
+ |> double
+ |> n => add(7, n)
+ |> n => boundScore(0, 100, n);
+
+newScore //=> 57
+
+// As opposed to:
+let newScore = boundScore(0, 100, add(7, double(person.score)));
+
+=====================================output=====================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-pipeline-operator
+// https://github.com/valtech-nyc/proposal-fsharp-pipelines
+
+promise
+ |> await
+ |> (x) => doubleSay(x, ", ")
+ |> capitalize
+ |> (x) => x + "!"
+ |> (x) => new User.Message(x)
+ |> (x) => stream.write(x)
+ |> await
+ |> console.log;
+
+const result = exclaim(capitalize(doubleSay("hello")));
+result; //=> "Hello, hello!"
+
+const result = "hello" |> doubleSay |> capitalize |> exclaim;
+
+result; //=> "Hello, hello!"
+
+const person = { score: 25 };
+
+const newScore =
+ person.score |> double |> (n) => add(7, n) |> (n) => boundScore(0, 100, n);
+
+newScore; //=> 57
+
+// As opposed to:
+let newScore = boundScore(0, 100, add(7, double(person.score)));
+
+================================================================================
+`;
+
+exports[`pipeline-operator-minimal.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-pipeline-operator
+// https://github.com/tc39/proposal-pipeline-operator/
+
+let result = exclaim(capitalize(doubleSay("hello")));
+result //=> "Hello, hello!"
+
+let result = "hello"
+ |> doubleSay
+ |> capitalize
+ |> exclaim;
+
+result //=> "Hello, hello!"
+
+=====================================output=====================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-pipeline-operator
+// https://github.com/tc39/proposal-pipeline-operator/
+
+let result = exclaim(capitalize(doubleSay("hello")));
+result; //=> "Hello, hello!"
+
+let result = "hello" |> doubleSay |> capitalize |> exclaim;
+
+result; //=> "Hello, hello!"
+
+================================================================================
+`;
+
+exports[`pipeline-operator-smart.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-pipeline-operator
+// https://github.com/js-choi/proposal-smart-pipelines
+
+value
+|> await #
+|> doubleSay(#, ', ')
+|> capitalize // This is a function call.
+|> # + '!'
+|> new User.Message(#)
+|> await #
+|> console.log; // This is a method call.
+
+// (The # token isn't final; it might instead be @ or ? or %.)
+
+=====================================output=====================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-pipeline-operator
+// https://github.com/js-choi/proposal-smart-pipelines
+
+value
+|> await #
+|> doubleSay(#, ", ")
+|> capitalize // This is a function call.
+ |> # + "!"
+ |> new User.Message(#)
+ |> await #
+ |> console.log; // This is a method call.
+
+// (The # token isn't final; it might instead be @ or ? or %.)
+
+================================================================================
+`;
+
+exports[`private-fields-in-in.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+// https://github.com/tc39/proposal-private-fields-in-in
+
+class C {
+ #brand;
+
+ static isC(obj) {
+ try {
+ obj.#brand;
+ return true;
+ } catch {
+ return false;
+ }
+ }
+}
+
+class C {
+ #data = null; // populated later
+
+ get #getter() {
+ if (!this.#data) {
+ throw new Error('no data yet!');
+ }
+ return this.#data;
+ }
+
+ static isC(obj) {
+ try {
+ obj.#getter;
+ return true;
+ } catch {
+ return false; // oops! might have gotten here because \`#getter\` threw :-(
+ }
+ }
+}
+
+class C {
+ #brand;
+
+ #method() {}
+
+ get #getter() {}
+
+ static isC(obj) {
+ return #brand in obj && #method in obj && #getter in obj;
+ }
+}
+
+// Invalid https://github.com/tc39/proposal-private-fields-in-in#try-statement
+// class C {
+// #brand;
+
+// static isC(obj) {
+// return try obj.#brand;
+// }
+// }
+
+=====================================output=====================================
+// https://github.com/tc39/proposal-private-fields-in-in
+
+class C {
+ #brand;
+
+ static isC(obj) {
+ try {
+ obj.#brand;
+ return true;
+ } catch {
+ return false;
+ }
+ }
+}
+
+class C {
+ #data = null; // populated later
+
+ get #getter() {
+ if (!this.#data) {
+ throw new Error("no data yet!");
+ }
+ return this.#data;
+ }
+
+ static isC(obj) {
+ try {
+ obj.#getter;
+ return true;
+ } catch {
+ return false; // oops! might have gotten here because \`#getter\` threw :-(
+ }
+ }
+}
+
+class C {
+ #brand;
+
+ #method() {}
+
+ get #getter() {}
+
+ static isC(obj) {
+ return #brand in obj && #method in obj && #getter in obj;
+ }
+}
+
+// Invalid https://github.com/tc39/proposal-private-fields-in-in#try-statement
+// class C {
+// #brand;
+
+// static isC(obj) {
+// return try obj.#brand;
+// }
+// }
+
+================================================================================
+`;
+
+exports[`private-methods.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-private-methods
+
+// Test for \`classPrivateProperties\` and \`classPrivateMethods\`
+
+class Counter extends HTMLElement {
+ #xValue = 0;
+
+ get #x() { return this.#xValue; }
+ set #x(value) {
+ this.#xValue = value;
+ window.requestAnimationFrame(
+ this.#render.bind(this));
+ }
+
+ #clicked() {
+ this.#x++;
+ }
+}
+
+=====================================output=====================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-private-methods
+
+// Test for \`classPrivateProperties\` and \`classPrivateMethods\`
+
+class Counter extends HTMLElement {
+ #xValue = 0;
+
+ get #x() {
+ return this.#xValue;
+ }
+ set #x(value) {
+ this.#xValue = value;
+ window.requestAnimationFrame(this.#render.bind(this));
+ }
+
+ #clicked() {
+ this.#x++;
+ }
+}
+
+================================================================================
+`;
+
+exports[`throw-expressions.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-throw-expressions
+
+function test(param = throw new Error('required!')) {
+ const test = param === true || throw new Error('Falsy!');
+}
+
+=====================================output=====================================
+// https://babeljs.io/docs/en/babel-plugin-proposal-throw-expressions
+
+function test(param = throw new Error("required!")) {
+ const test = param === true || throw new Error("Falsy!");
+}
+
+================================================================================
+`;
+
+exports[`typescript.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+// https://babeljs.io/docs/en/babel-preset-typescript
+
+const x: number = 0;
+
+=====================================output=====================================
+// https://babeljs.io/docs/en/babel-preset-typescript
+
+const x: number = 0;
+
+================================================================================
+`;
+
+exports[`v8intrinsic.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+// https://github.com/babel/babel/pull/10148
+
+%DebugPrint(foo);
+
+
+// Invalid code https://github.com/JLHwung/babel/blob/c1a3cbfd65e08b7013fd6f8c62add8cb10b4b169/packages/babel-parser/test/fixtures/v8intrinsic/_errors/in-bind-expression/options.json
+// ::%DebugPrint(null)
+
+// Invalid code https://github.com/JLHwung/babel/blob/c1a3cbfd65e08b7013fd6f8c62add8cb10b4b169/packages/babel-parser/test/fixtures/v8intrinsic/_errors/in-member-expression/options.json
+// a.%DebugPrint();
+
+// Invalid code https://github.com/JLHwung/babel/blob/c1a3cbfd65e08b7013fd6f8c62add8cb10b4b169/packages/babel-parser/test/fixtures/v8intrinsic/_errors/not-in-call-expression/options.json
+// const i = %DebugPrint;
+// i(foo);
+
+// https://github.com/JLHwung/babel/blob/c1a3cbfd65e08b7013fd6f8c62add8cb10b4b169/packages/babel-parser/test/fixtures/v8intrinsic/_errors/not-in-call-expression/options.json
+// %DebugPrint?.(null)
+
+new %DebugPrint(null);
+
+function *foo() {
+ yield %StringParseInt("42", 10)
+}
+
+foo%bar()
+
+=====================================output=====================================
+// https://github.com/babel/babel/pull/10148
+
+%DebugPrint(foo);
+
+// Invalid code https://github.com/JLHwung/babel/blob/c1a3cbfd65e08b7013fd6f8c62add8cb10b4b169/packages/babel-parser/test/fixtures/v8intrinsic/_errors/in-bind-expression/options.json
+// ::%DebugPrint(null)
+
+// Invalid code https://github.com/JLHwung/babel/blob/c1a3cbfd65e08b7013fd6f8c62add8cb10b4b169/packages/babel-parser/test/fixtures/v8intrinsic/_errors/in-member-expression/options.json
+// a.%DebugPrint();
+
+// Invalid code https://github.com/JLHwung/babel/blob/c1a3cbfd65e08b7013fd6f8c62add8cb10b4b169/packages/babel-parser/test/fixtures/v8intrinsic/_errors/not-in-call-expression/options.json
+// const i = %DebugPrint;
+// i(foo);
+
+// https://github.com/JLHwung/babel/blob/c1a3cbfd65e08b7013fd6f8c62add8cb10b4b169/packages/babel-parser/test/fixtures/v8intrinsic/_errors/not-in-call-expression/options.json
+// %DebugPrint?.(null)
+
+new %DebugPrint(null);
+
+function* foo() {
+ yield %StringParseInt("42", 10);
+}
+
+foo % bar();
+
+================================================================================
+`;
diff --git a/tests/js/babel-plugins/async-generators.js b/tests/js/babel-plugins/async-generators.js
new file mode 100644
index 000000000..d3fc2cdac
--- /dev/null
+++ b/tests/js/babel-plugins/async-generators.js
@@ -0,0 +1,6 @@
+// https://babeljs.io/docs/en/babel-plugin-proposal-async-generator-functions
+
+async function* agf() {
+ await 1;
+ yield 2;
+}
diff --git a/tests/js/babel-plugins/bigint.js b/tests/js/babel-plugins/bigint.js
new file mode 100644
index 000000000..4f410f310
--- /dev/null
+++ b/tests/js/babel-plugins/bigint.js
@@ -0,0 +1,128 @@
+// https://github.com/tc39/proposal-bigint
+
+const previousMaxSafe = BigInt(Number.MAX_SAFE_INTEGER);
+// ↪ 9007199254740991
+
+const maxPlusOne = previousMaxSafe + 1n;
+// ↪ 9007199254740992n
+
+const theFuture = previousMaxSafe + 2n;
+// ↪ 9007199254740993n, this works now!
+
+const multi = previousMaxSafe * 2n;
+// ↪ 18014398509481982n
+
+// `–` is not minus sign,
+// SIC https://github.com/tc39/proposal-bigint#operators
+// const subtr = multi – 10n;
+// ↪ 18014398509481972n
+
+const mod = multi % 10n;
+// ↪ 2n
+
+const bigN = 2n ** 54n;
+// ↪ 18014398509481984n
+
+bigN * -1n
+// ↪ –18014398509481984n
+
+0n === 0
+// ↪ false
+
+0n == 0
+// ↪ true
+
+1n < 2
+// ↪ true
+
+2n > 1
+// ↪ true
+
+2 > 2
+// ↪ false
+
+2n > 2
+// ↪ false
+
+2n >= 2
+// ↪ true
+
+const mixed = [4n, 6, -12n, 10, 4, 0, 0n];
+// ↪ [4n, 6, -12n, 10, 4, 0, 0n]
+
+mixed.sort();
+// ↪ [-12n, 0, 0n, 10, 4n, 4, 6]
+
+if (0n) {
+ console.log('Hello from the if!');
+} else {
+ console.log('Hello from the else!');
+}
+
+// ↪ "Hello from the else!"
+
+0n || 12n
+// ↪ 12n
+
+0n && 12n
+// ↪ 0n
+
+Boolean(0n)
+// ↪ false
+
+Boolean(12n)
+// ↪ true
+
+!12n
+// ↪ false
+
+!0n
+// ↪ true
+
+const view = new BigInt64Array(4);
+// ↪ [0n, 0n, 0n, 0n]
+view.length;
+// ↪ 4
+view[0];
+// ↪ 0n
+view[0] = 42n;
+view[0];
+// ↪ 42n
+
+// Highest possible BigInt value that can be represented as a
+// signed 64-bit integer.
+const max = 2n ** (64n - 1n) - 1n;
+view[0] = max;
+view[0];
+// ↪ 9_223_372_036_854_775_807n
+view[0] = max + 1n;
+view[0];
+// ↪ -9_223_372_036_854_775_808n
+// ^ negative because of overflow
+
+1n + 2
+// ↪ TypeError: Cannot mix BigInt and other types, use explicit conversions
+
+1n * 2
+// ↪ TypeError: Cannot mix BigInt and other types, use explicit conversions
+
++1n
+// ↪ TypeError: Cannot convert a BigInt value to a number
+
+Number(1n)
+// ↪ 1
+
+1n + '2'
+// ↪ "12"
+
+'2' + 1n
+// ↪ "21"
+
+const badPrecision = BigInt(9007199254740993);
+// ↪9007199254740992n
+
+const goodPrecision = BigInt('9007199254740993');
+// ↪9007199254740993n
+
+const alsoGoodPrecision = 9007199254740993n;
+// ↪9007199254740993n
diff --git a/tests/js/babel-plugins/class-properties.js b/tests/js/babel-plugins/class-properties.js
new file mode 100644
index 000000000..916c975a6
--- /dev/null
+++ b/tests/js/babel-plugins/class-properties.js
@@ -0,0 +1,26 @@
+// https://babeljs.io/docs/en/babel-plugin-proposal-class-properties
+
+class Bork {
+ //Property initializer syntax
+ instanceProperty = "bork";
+ boundFunction = () => {
+ return this.instanceProperty;
+ };
+
+ //Static class properties
+ static staticProperty = "babelIsCool";
+ static staticFunction = function() {
+ return Bork.staticProperty;
+ };
+ }
+
+ let myBork = new Bork;
+
+ //Property initializers are not on the prototype.
+ console.log(myBork.__proto__.boundFunction); // > undefined
+
+ //Bound functions are bound to the class instance.
+ console.log(myBork.boundFunction.call(undefined)); // > "bork"
+
+ //Static function exists on the class.
+ console.log(Bork.staticFunction()); // > "babelIsCool"
diff --git a/tests/js/babel-plugins/decorators.js b/tests/js/babel-plugins/decorators.js
new file mode 100644
index 000000000..03744072c
--- /dev/null
+++ b/tests/js/babel-plugins/decorators.js
@@ -0,0 +1,29 @@
+// https://babeljs.io/docs/en/babel-plugin-proposal-decorators
+
+@annotation
+class MyClass { }
+
+function annotation(target) {
+ target.annotated = true;
+}
+
+@isTestable(true)
+class MyClass { }
+
+function isTestable(value) {
+ return function decorator(target) {
+ target.isTestable = value;
+ }
+}
+
+class C {
+ @enumerable(false)
+ method() { }
+}
+
+function enumerable(value) {
+ return function (target, key, descriptor) {
+ descriptor.enumerable = value;
+ return descriptor;
+ }
+}
diff --git a/tests/js/babel-plugins/do-expressions.js b/tests/js/babel-plugins/do-expressions.js
new file mode 100644
index 000000000..5a139893d
--- /dev/null
+++ b/tests/js/babel-plugins/do-expressions.js
@@ -0,0 +1,11 @@
+// https://babeljs.io/docs/en/babel-plugin-proposal-do-expressions
+
+let a = do {
+ if(x > 10) {
+ 'big';
+ } else {
+ 'small';
+ }
+};
+// is equivalent to:
+let a = x > 10 ? 'big' : 'small';
diff --git a/tests/js/babel-plugins/dynamic-import.js b/tests/js/babel-plugins/dynamic-import.js
new file mode 100644
index 000000000..bd361096d
--- /dev/null
+++ b/tests/js/babel-plugins/dynamic-import.js
@@ -0,0 +1,8 @@
+// https://babeljs.io/docs/en/babel-plugin-syntax-dynamic-import
+
+// There is no example code on babel website
+
+import('./prettier.mjs');
+import(prettier);
+import('./prettier.mjs').then(module => console.log(module));
+import(prettier).then(module => console.log(module));
diff --git a/tests/js/babel-plugins/export-default-from.js b/tests/js/babel-plugins/export-default-from.js
new file mode 100644
index 000000000..67a17a8e4
--- /dev/null
+++ b/tests/js/babel-plugins/export-default-from.js
@@ -0,0 +1,4 @@
+// https://babeljs.io/docs/en/babel-plugin-proposal-export-default-from
+
+
+export v from 'mod';
diff --git a/tests/js/babel-plugins/export-namespace-from.js b/tests/js/babel-plugins/export-namespace-from.js
new file mode 100644
index 000000000..9c06dcd5b
--- /dev/null
+++ b/tests/js/babel-plugins/export-namespace-from.js
@@ -0,0 +1,3 @@
+// https://babeljs.io/docs/en/babel-plugin-proposal-export-namespace-from
+
+export * as ns from 'mod';
diff --git a/tests/js/babel-plugins/flow.js b/tests/js/babel-plugins/flow.js
new file mode 100644
index 000000000..44c0e3305
--- /dev/null
+++ b/tests/js/babel-plugins/flow.js
@@ -0,0 +1,3 @@
+// https://babeljs.io/docs/en/babel-preset-flow
+
+function foo(one: any, two: number, three?): string {}
diff --git a/tests/js/babel-plugins/function-bind.js b/tests/js/babel-plugins/function-bind.js
new file mode 100644
index 000000000..41075578e
--- /dev/null
+++ b/tests/js/babel-plugins/function-bind.js
@@ -0,0 +1,17 @@
+// https://babeljs.io/docs/en/babel-plugin-proposal-function-bind
+
+obj::func
+// is equivalent to:
+func.bind(obj)
+
+::obj.func
+// is equivalent to:
+obj.func.bind(obj)
+
+obj::func(val)
+// is equivalent to:
+func.call(obj, val)
+
+::obj.func(val)
+// is equivalent to:
+obj.func.call(obj, val)
diff --git a/tests/js/babel-plugins/function-sent.js b/tests/js/babel-plugins/function-sent.js
new file mode 100644
index 000000000..f7fc8e3d0
--- /dev/null
+++ b/tests/js/babel-plugins/function-sent.js
@@ -0,0 +1,10 @@
+// https://babeljs.io/docs/en/babel-plugin-proposal-function-sent
+
+function* generator() {
+ console.log("Sent", function.sent);
+ console.log("Yield", yield);
+}
+
+const iterator = generator();
+iterator.next(1); // Logs "Sent 1"
+iterator.next(2); // Logs "Yield 2"
diff --git a/tests/js/babel-plugins/import-meta.js b/tests/js/babel-plugins/import-meta.js
new file mode 100644
index 000000000..3ef8f5a96
--- /dev/null
+++ b/tests/js/babel-plugins/import-meta.js
@@ -0,0 +1,18 @@
+// https://babeljs.io/docs/en/babel-plugin-syntax-import-meta
+
+// Enabled by default https://github.com/babel/babel/pull/11406
+
+// from https://github.com/tc39/proposal-import-meta
+
+(async () => {
+ const response = await fetch(new URL("../hamsters.jpg", import.meta.url));
+ const blob = await response.blob();
+
+ const size = import.meta.scriptElement.dataset.size || 300;
+
+ const image = new Image();
+ image.src = URL.createObjectURL(blob);
+ image.width = image.height = size;
+
+ document.body.appendChild(image);
+})();
diff --git a/tests/js/babel-plugins/jsfmt.spec.js b/tests/js/babel-plugins/jsfmt.spec.js
new file mode 100644
index 000000000..0fa97049b
--- /dev/null
+++ b/tests/js/babel-plugins/jsfmt.spec.js
@@ -0,0 +1,4 @@
+// Only testing babel parsing
+// Do not add extra parsers here
+
+run_spec(__dirname, ["babel", "babel-ts", "babel-flow"]);
diff --git a/tests/js/babel-plugins/jsx.js b/tests/js/babel-plugins/jsx.js
new file mode 100644
index 000000000..f5a6d9ef0
--- /dev/null
+++ b/tests/js/babel-plugins/jsx.js
@@ -0,0 +1,6 @@
+// https://babeljs.io/docs/en/babel-plugin-syntax-jsx
+
+var profile =
+

+
{[user.firstName, user.lastName].join(' ')}
+
;
diff --git a/tests/js/babel-plugins/logical-assignment-operators.js b/tests/js/babel-plugins/logical-assignment-operators.js
new file mode 100644
index 000000000..43e332171
--- /dev/null
+++ b/tests/js/babel-plugins/logical-assignment-operators.js
@@ -0,0 +1,7 @@
+// https://babeljs.io/docs/en/babel-plugin-proposal-logical-assignment-operators
+
+a ||= b;
+obj.a.b ||= c;
+
+a &&= b;
+obj.a.b &&= c;
diff --git a/tests/js/babel-plugins/nullish-coalescing-operator.js b/tests/js/babel-plugins/nullish-coalescing-operator.js
new file mode 100644
index 000000000..b786659b2
--- /dev/null
+++ b/tests/js/babel-plugins/nullish-coalescing-operator.js
@@ -0,0 +1,3 @@
+// https://babeljs.io/docs/en/babel-plugin-proposal-nullish-coalescing-operator
+
+var foo = object.foo ?? "default";
diff --git a/tests/js/babel-plugins/numeric-separator.js b/tests/js/babel-plugins/numeric-separator.js
new file mode 100644
index 000000000..3ea80118c
--- /dev/null
+++ b/tests/js/babel-plugins/numeric-separator.js
@@ -0,0 +1,36 @@
+// https://babeljs.io/docs/en/babel-plugin-proposal-numeric-separator
+
+let budget = 1_000_000_000_000;
+
+// What is the value of `budget`? It's 1 trillion!
+//
+// Let's confirm:
+console.log(budget === 10 ** 12); // true
+
+let nibbles = 0b1010_0001_1000_0101;
+
+// Is bit 7 on? It sure is!
+// 0b1010_0001_1000_0101
+// ^
+//
+// We can double check:
+console.log(!!(nibbles & (1 << 7))); // true
+
+// Messages are sent as 24 bit values, but should be
+// treated as 3 distinct bytes:
+let message = 0xa0_b0_c0;
+
+// What's the value of the upper most byte? It's A0, or 160.
+// We can confirm that:
+let a = (message >> 16) & 0xff;
+console.log(a.toString(16), a); // a0, 160
+
+// What's the value of the middle byte? It's B0, or 176.
+// Let's just make sure...
+let b = (message >> 8) & 0xff;
+console.log(b.toString(16), b); // b0, 176
+
+// What's the value of the lower most byte? It's C0, or 192.
+// Again, let's prove that:
+let c = message & 0xff;
+console.log(c.toString(16), b); // c0, 192
diff --git a/tests/js/babel-plugins/object-rest-spread.js b/tests/js/babel-plugins/object-rest-spread.js
new file mode 100644
index 000000000..6e9ffbff0
--- /dev/null
+++ b/tests/js/babel-plugins/object-rest-spread.js
@@ -0,0 +1,9 @@
+// https://babeljs.io/docs/en/babel-plugin-transform-object-rest-spread
+
+let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
+console.log(x); // 1
+console.log(y); // 2
+console.log(z); // { a: 3, b: 4 }
+
+let n = { x, y, ...z };
+console.log(n); // { x: 1, y: 2, a: 3, b: 4 }
diff --git a/tests/js/babel-plugins/optional-catch-binding.js b/tests/js/babel-plugins/optional-catch-binding.js
new file mode 100644
index 000000000..1327104c7
--- /dev/null
+++ b/tests/js/babel-plugins/optional-catch-binding.js
@@ -0,0 +1,15 @@
+// https://babeljs.io/docs/en/babel-plugin-proposal-optional-catch-binding
+
+try {
+ throw 0;
+} catch {
+ doSomethingWhichDoesNotCareAboutTheValueThrown();
+}
+
+try {
+ throw 0;
+} catch {
+ doSomethingWhichDoesNotCareAboutTheValueThrown();
+} finally {
+ doSomeCleanup();
+}
diff --git a/tests/js/babel-plugins/optional-chaining.js b/tests/js/babel-plugins/optional-chaining.js
new file mode 100644
index 000000000..c80d98d69
--- /dev/null
+++ b/tests/js/babel-plugins/optional-chaining.js
@@ -0,0 +1,76 @@
+// https://babeljs.io/docs/en/babel-plugin-proposal-optional-chaining
+
+const obj = {
+ foo: {
+ bar: {
+ baz: 42,
+ },
+ },
+};
+
+const baz = obj?.foo?.bar?.baz; // 42
+
+const safe = obj?.qux?.baz; // undefined
+
+// Optional chaining and normal chaining can be intermixed
+obj?.foo.bar?.baz; // Only access `foo` if `obj` exists, and `baz` if
+ // `bar` exists
+
+// Example usage with bracket notation:
+obj?.['foo']?.bar?.baz // 42
+
+const obj = {
+ foo: {
+ bar: {
+ baz() {
+ return 42;
+ },
+ },
+ },
+};
+
+const baz = obj?.foo?.bar?.baz(); // 42
+
+const safe = obj?.qux?.baz(); // undefined
+const safe2 = obj?.foo.bar.qux?.(); // undefined
+
+const willThrow = obj?.foo.bar.qux(); // Error: not a function
+
+// Top function can be called directly, too.
+function test() {
+ return 42;
+}
+test?.(); // 42
+
+exists?.(); // undefined
+
+const obj = {
+ foo: {
+ bar: {
+ baz: class {
+ },
+ },
+ },
+};
+
+const baz = new obj?.foo?.bar?.baz(); // baz instance
+
+const safe = new obj?.qux?.baz(); // undefined
+const safe2 = new obj?.foo.bar.qux?.(); // undefined
+
+const willThrow = new obj?.foo.bar.qux(); // Error: not a constructor
+
+// Top classes can be called directly, too.
+class Test {
+}
+new Test?.(); // test instance
+
+new exists?.(); // undefined
+
+const obj = {
+ foo: {
+ bar: {}
+ },
+};
+
+const ret = delete obj?.foo?.bar?.baz; // true
diff --git a/tests/js/babel-plugins/partial-application.js b/tests/js/babel-plugins/partial-application.js
new file mode 100644
index 000000000..6aaee6003
--- /dev/null
+++ b/tests/js/babel-plugins/partial-application.js
@@ -0,0 +1,21 @@
+// https://babeljs.io/docs/en/babel-plugin-proposal-partial-application
+
+function add(x, y) { return x + y; }
+
+const addOne = add(1, ?); // apply from the left
+addOne(2); // 3
+
+const addTen = add(?, 10); // apply from the right
+addTen(2); // 12
+
+let newScore = player.score
+ |> add(7, ?)
+ |> clamp(0, 100, ?); // shallow stack, the pipe to `clamp` is the same frame as the pipe to `add`.
+
+f(x, ?) // partial application from left
+f(?, x) // partial application from right
+f(?, x, ?) // partial application for any arg
+o.f(x, ?) // partial application from left
+o.f(?, x) // partial application from right
+o.f(?, x, ?) // partial application for any arg
+super.f(?) // partial application allowed for call on |SuperProperty|
diff --git a/tests/js/babel-plugins/pipeline-operator-fsharp.js b/tests/js/babel-plugins/pipeline-operator-fsharp.js
new file mode 100644
index 000000000..66a798372
--- /dev/null
+++ b/tests/js/babel-plugins/pipeline-operator-fsharp.js
@@ -0,0 +1,34 @@
+// https://babeljs.io/docs/en/babel-plugin-proposal-pipeline-operator
+// https://github.com/valtech-nyc/proposal-fsharp-pipelines
+
+promise
+ |> await
+ |> x => doubleSay(x, ', ')
+ |> capitalize
+ |> x => x + '!'
+ |> x => new User.Message(x)
+ |> x => stream.write(x)
+ |> await
+ |> console.log;
+
+const result = exclaim(capitalize(doubleSay("hello")));
+result //=> "Hello, hello!"
+
+const result = "hello"
+ |> doubleSay
+ |> capitalize
+ |> exclaim;
+
+result //=> "Hello, hello!"
+
+const person = { score: 25 };
+
+const newScore = person.score
+ |> double
+ |> n => add(7, n)
+ |> n => boundScore(0, 100, n);
+
+newScore //=> 57
+
+// As opposed to:
+let newScore = boundScore(0, 100, add(7, double(person.score)));
diff --git a/tests/js/babel-plugins/pipeline-operator-minimal.js b/tests/js/babel-plugins/pipeline-operator-minimal.js
new file mode 100644
index 000000000..e15c9d95c
--- /dev/null
+++ b/tests/js/babel-plugins/pipeline-operator-minimal.js
@@ -0,0 +1,12 @@
+// https://babeljs.io/docs/en/babel-plugin-proposal-pipeline-operator
+// https://github.com/tc39/proposal-pipeline-operator/
+
+let result = exclaim(capitalize(doubleSay("hello")));
+result //=> "Hello, hello!"
+
+let result = "hello"
+ |> doubleSay
+ |> capitalize
+ |> exclaim;
+
+result //=> "Hello, hello!"
diff --git a/tests/js/babel-plugins/pipeline-operator-smart.js b/tests/js/babel-plugins/pipeline-operator-smart.js
new file mode 100644
index 000000000..f28849169
--- /dev/null
+++ b/tests/js/babel-plugins/pipeline-operator-smart.js
@@ -0,0 +1,13 @@
+// https://babeljs.io/docs/en/babel-plugin-proposal-pipeline-operator
+// https://github.com/js-choi/proposal-smart-pipelines
+
+value
+|> await #
+|> doubleSay(#, ', ')
+|> capitalize // This is a function call.
+|> # + '!'
+|> new User.Message(#)
+|> await #
+|> console.log; // This is a method call.
+
+// (The # token isn't final; it might instead be @ or ? or %.)
diff --git a/tests/js/babel-plugins/private-fields-in-in.js b/tests/js/babel-plugins/private-fields-in-in.js
new file mode 100644
index 000000000..aba8e5df9
--- /dev/null
+++ b/tests/js/babel-plugins/private-fields-in-in.js
@@ -0,0 +1,55 @@
+// https://github.com/tc39/proposal-private-fields-in-in
+
+class C {
+ #brand;
+
+ static isC(obj) {
+ try {
+ obj.#brand;
+ return true;
+ } catch {
+ return false;
+ }
+ }
+}
+
+class C {
+ #data = null; // populated later
+
+ get #getter() {
+ if (!this.#data) {
+ throw new Error('no data yet!');
+ }
+ return this.#data;
+ }
+
+ static isC(obj) {
+ try {
+ obj.#getter;
+ return true;
+ } catch {
+ return false; // oops! might have gotten here because `#getter` threw :-(
+ }
+ }
+}
+
+class C {
+ #brand;
+
+ #method() {}
+
+ get #getter() {}
+
+ static isC(obj) {
+ return #brand in obj && #method in obj && #getter in obj;
+ }
+}
+
+// Invalid https://github.com/tc39/proposal-private-fields-in-in#try-statement
+// class C {
+// #brand;
+
+// static isC(obj) {
+// return try obj.#brand;
+// }
+// }
diff --git a/tests/js/babel-plugins/private-methods.js b/tests/js/babel-plugins/private-methods.js
new file mode 100644
index 000000000..e1486a9fb
--- /dev/null
+++ b/tests/js/babel-plugins/private-methods.js
@@ -0,0 +1,18 @@
+// https://babeljs.io/docs/en/babel-plugin-proposal-private-methods
+
+// Test for `classPrivateProperties` and `classPrivateMethods`
+
+class Counter extends HTMLElement {
+ #xValue = 0;
+
+ get #x() { return this.#xValue; }
+ set #x(value) {
+ this.#xValue = value;
+ window.requestAnimationFrame(
+ this.#render.bind(this));
+ }
+
+ #clicked() {
+ this.#x++;
+ }
+}
diff --git a/tests/js/babel-plugins/throw-expressions.js b/tests/js/babel-plugins/throw-expressions.js
new file mode 100644
index 000000000..3cded2284
--- /dev/null
+++ b/tests/js/babel-plugins/throw-expressions.js
@@ -0,0 +1,5 @@
+// https://babeljs.io/docs/en/babel-plugin-proposal-throw-expressions
+
+function test(param = throw new Error('required!')) {
+ const test = param === true || throw new Error('Falsy!');
+}
diff --git a/tests/js/babel-plugins/typescript.js b/tests/js/babel-plugins/typescript.js
new file mode 100644
index 000000000..7fe7124f3
--- /dev/null
+++ b/tests/js/babel-plugins/typescript.js
@@ -0,0 +1,3 @@
+// https://babeljs.io/docs/en/babel-preset-typescript
+
+const x: number = 0;
diff --git a/tests/js/babel-plugins/v8intrinsic.js b/tests/js/babel-plugins/v8intrinsic.js
new file mode 100644
index 000000000..600086f28
--- /dev/null
+++ b/tests/js/babel-plugins/v8intrinsic.js
@@ -0,0 +1,25 @@
+// https://github.com/babel/babel/pull/10148
+
+%DebugPrint(foo);
+
+
+// Invalid code https://github.com/JLHwung/babel/blob/c1a3cbfd65e08b7013fd6f8c62add8cb10b4b169/packages/babel-parser/test/fixtures/v8intrinsic/_errors/in-bind-expression/options.json
+// ::%DebugPrint(null)
+
+// Invalid code https://github.com/JLHwung/babel/blob/c1a3cbfd65e08b7013fd6f8c62add8cb10b4b169/packages/babel-parser/test/fixtures/v8intrinsic/_errors/in-member-expression/options.json
+// a.%DebugPrint();
+
+// Invalid code https://github.com/JLHwung/babel/blob/c1a3cbfd65e08b7013fd6f8c62add8cb10b4b169/packages/babel-parser/test/fixtures/v8intrinsic/_errors/not-in-call-expression/options.json
+// const i = %DebugPrint;
+// i(foo);
+
+// https://github.com/JLHwung/babel/blob/c1a3cbfd65e08b7013fd6f8c62add8cb10b4b169/packages/babel-parser/test/fixtures/v8intrinsic/_errors/not-in-call-expression/options.json
+// %DebugPrint?.(null)
+
+new %DebugPrint(null);
+
+function *foo() {
+ yield %StringParseInt("42", 10)
+}
+
+foo%bar()
diff --git a/tests/misc/errors/js/__snapshots__/jsfmt.spec.js.snap b/tests/misc/errors/js/__snapshots__/jsfmt.spec.js.snap
index 8b11758f0..463aec104 100644
--- a/tests/misc/errors/js/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/misc/errors/js/__snapshots__/jsfmt.spec.js.snap
@@ -22,6 +22,52 @@ exports[`partial-template-strings.js error test 1`] = `
9 | Diagnostics.name_not_found(\\"foo\\"); // \\"'foo' not found.\\""
`;
+exports[`snippet: #0 error test 1`] = `
+"Binding should be performed on object property. (1:3)
+> 1 | ::%DebugPrint(null)
+ | ^"
+`;
+
+exports[`snippet: #1 error test 1`] = `
+"Unexpected token (1:3)
+> 1 | a.%DebugPrint();
+ | ^"
+`;
+
+exports[`snippet: #2 error test 1`] = `
+"Unexpected token (1:11)
+> 1 | const i = %DebugPrint;
+ | ^"
+`;
+
+exports[`snippet: #3 error test 1`] = `
+"Unexpected token (1:7)
+> 1 | f(x + ?)
+ | ^"
+`;
+
+exports[`snippet: #4 error test 1`] = `
+"Unexpected token (1:5)
+> 1 | x + ?
+ | ^"
+`;
+
+exports[`snippet: #5 error test 1`] = `
+"Unexpected token (1:1)
+> 1 | ?.f()
+ | ^"
+`;
+
+exports[`snippet: #6 error test 1`] = `
+"Unexpected token (5:12)
+ 3 |
+ 4 | static isC(obj) {
+> 5 | return try obj.#brand;
+ | ^
+ 6 | }
+ 7 | }"
+`;
+
exports[`static-import-source-should-not-has-extra-token.js error test 1`] = `
"Unexpected token (4:16)
2 | // be parenthesized, that accidentally break #8016
diff --git a/tests/misc/errors/js/jsfmt.spec.js b/tests/misc/errors/js/jsfmt.spec.js
index 8382eddeb..bba1febe0 100644
--- a/tests/misc/errors/js/jsfmt.spec.js
+++ b/tests/misc/errors/js/jsfmt.spec.js
@@ -1 +1,33 @@
-run_spec(__dirname, ["babel"]);
+const { outdent } = require("outdent");
+
+run_spec(
+ {
+ dirname: __dirname,
+ snippets: [
+ // v8intrinsic
+ "::%DebugPrint(null)",
+ "a.%DebugPrint();",
+ "const i = %DebugPrint;",
+
+ // partialApplication
+ // https://babeljs.io/docs/en/babel-plugin-proposal-partial-application#invalid-usage
+ "f(x + ?)", // `?` not in top-level Arguments of call
+ "x + ?", // `?` not in top-level Arguments of call
+ "?.f()", // `?` not in top-level Arguments of call
+ // "new f(?)", // `?` not supported in `new`
+ // "super(?)", // `?` not supported in |SuperCall|
+
+ // https://github.com/tc39/proposal-private-fields-in-in#try-statement
+ outdent`
+ class C {
+ #brand;
+
+ static isC(obj) {
+ return try obj.#brand;
+ }
+ }
+ `,
+ ],
+ },
+ ["babel"]
+);