diff --git a/changelog_unreleased/javascript/14077.md b/changelog_unreleased/javascript/14077.md
new file mode 100644
index 000000000000..eb48a23b8dcd
--- /dev/null
+++ b/changelog_unreleased/javascript/14077.md
@@ -0,0 +1,19 @@
+#### Add parentheses to head of `ExpressionStatement` instead of the whole statement (#14077 by @fisker)
+
+
+```jsx
+// Input
+({}).toString.call(foo) === "[object Array]"
+ ? foo.forEach(iterateArray)
+ : iterateObject(foo);
+
+// Prettier stable
+({}.toString.call(foo) === "[object Array]"
+ ? foo.forEach(iterateArray)
+ : iterateObject(foo));
+
+// Prettier main
+({}).toString.call(foo.forEach) === "[object Array]"
+ ? foo.forEach(iterateArray)
+ : iterateObject(foo);
+```
diff --git a/src/language-js/needs-parens.js b/src/language-js/needs-parens.js
index 02b8bf7ac1a6..a915287dab68 100644
--- a/src/language-js/needs-parens.js
+++ b/src/language-js/needs-parens.js
@@ -128,6 +128,26 @@ function needsParens(path, options) {
return false;
}
+ if (
+ node.type === "ObjectExpression" ||
+ node.type === "FunctionExpression" ||
+ node.type === "ClassExpression" ||
+ node.type === "DoExpression"
+ ) {
+ const expression = path.findAncestor(
+ (node) => node.type === "ExpressionStatement"
+ )?.expression;
+ if (
+ expression &&
+ startsWithNoLookaheadToken(
+ expression,
+ (leftmostNode) => leftmostNode === node
+ )
+ ) {
+ return true;
+ }
+ }
+
switch (parent.type) {
case "ParenthesizedExpression":
return false;
@@ -201,21 +221,6 @@ function needsParens(path, options) {
}
break;
}
- case "ExpressionStatement": {
- if (
- startsWithNoLookaheadToken(
- node,
- (node) =>
- node.type === "ObjectExpression" ||
- node.type === "FunctionExpression" ||
- node.type === "ClassExpression" ||
- node.type === "DoExpression"
- )
- ) {
- return true;
- }
- break;
- }
case "ArrowFunctionExpression": {
if (
name === "body" &&
diff --git a/tests/format/flow-repo/arith/__snapshots__/jsfmt.spec.js.snap b/tests/format/flow-repo/arith/__snapshots__/jsfmt.spec.js.snap
index eaf8052c2b70..c6aaea5aa68f 100644
--- a/tests/format/flow-repo/arith/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/flow-repo/arith/__snapshots__/jsfmt.spec.js.snap
@@ -168,7 +168,7 @@ let tests = [
x + ""; // error
"" + x; // error
x + {}; // error
- ({} + x); // error
+ ({}) + x; // error
},
// when one side is a string or number and the other is invalid, we
@@ -344,10 +344,10 @@ let tests = [
"foo" < 1; // error
"foo" < "bar";
1 < { foo: 1 }; // error
-({ foo: 1 } < 1); // error
-({ foo: 1 } < { foo: 1 }); // error
+({ foo: 1 }) < 1; // error
+({ foo: 1 }) < { foo: 1 }; // error
"foo" < { foo: 1 }; // error
-({ foo: 1 } < "foo"); // error
+({ foo: 1 }) < "foo"; // error
var x = (null: ?number);
1 < x; // 2 errors: null !~> number; undefined !~> number
diff --git a/tests/format/flow-repo/binary/__snapshots__/jsfmt.spec.js.snap b/tests/format/flow-repo/binary/__snapshots__/jsfmt.spec.js.snap
index c971794e144c..13ec952db4ea 100644
--- a/tests/format/flow-repo/binary/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/flow-repo/binary/__snapshots__/jsfmt.spec.js.snap
@@ -98,7 +98,7 @@ let tests = [
function () {
null in {}; // error
void 0 in {}; // error
- ({} in {}); // error
+ ({}) in {}; // error
[] in {}; // error
false in []; // error
},
diff --git a/tests/format/flow-repo/object-method/__snapshots__/jsfmt.spec.js.snap b/tests/format/flow-repo/object-method/__snapshots__/jsfmt.spec.js.snap
index b0743465ac67..299f77c497b8 100644
--- a/tests/format/flow-repo/object-method/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/flow-repo/object-method/__snapshots__/jsfmt.spec.js.snap
@@ -160,7 +160,7 @@ function foo() {
function bar(f: () => void) {
f(); // passing global object as \`this\`
- ({ f }.f()); // passing container object as \`this\`
+ ({ f }).f(); // passing container object as \`this\`
}
bar(foo); // error, since \`this\` is used non-trivially in \`foo\`
diff --git a/tests/format/js/classes/__snapshots__/jsfmt.spec.js.snap b/tests/format/js/classes/__snapshots__/jsfmt.spec.js.snap
index 0587ffe17f9e..ec8ab049c7b2 100644
--- a/tests/format/js/classes/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/js/classes/__snapshots__/jsfmt.spec.js.snap
@@ -111,10 +111,10 @@ printWidth: 80
(class a extends b {}) + 1;
=====================================output=====================================
-(class {} + 1);
-(class a {} + 1);
-(class extends b {} + 1);
-(class a extends b {} + 1);
+(class {}) + 1;
+(class a {}) + 1;
+(class extends b {}) + 1;
+(class a extends b {}) + 1;
================================================================================
`;
@@ -128,7 +128,7 @@ printWidth: 80
(class {})(class {});
=====================================output=====================================
-(class {}(class {}));
+(class {})(class {});
================================================================================
`;
@@ -225,8 +225,8 @@ printWidth: 80
(class {}).a;
=====================================output=====================================
-(class {}[1]);
-(class {}.a);
+(class {})[1];
+(class {}).a;
================================================================================
`;
@@ -356,7 +356,7 @@ printWidth: 80
if (1) (class {}) ? 1 : 2;
=====================================output=====================================
-if (1) (class {} ? 1 : 2);
+if (1) (class {}) ? 1 : 2;
================================================================================
`;
diff --git a/tests/format/js/decorators/class-expression/__snapshots__/jsfmt.spec.js.snap b/tests/format/js/decorators/class-expression/__snapshots__/jsfmt.spec.js.snap
index b92b25fadf48..e12bdedd8818 100644
--- a/tests/format/js/decorators/class-expression/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/js/decorators/class-expression/__snapshots__/jsfmt.spec.js.snap
@@ -387,14 +387,14 @@ semi: false
(@deco class {}).name;
=====================================output=====================================
-;((
+;(
@deco
class Foo {}
-).name)
-;((
+).name
+;(
@deco
class {}
-).name)
+).name
================================================================================
`;
@@ -409,14 +409,14 @@ printWidth: 80
(@deco class {}).name;
=====================================output=====================================
-((
+(
@deco
class Foo {}
-).name);
-((
+).name;
+(
@deco
class {}
-).name);
+).name;
================================================================================
`;
diff --git a/tests/format/js/do/__snapshots__/jsfmt.spec.js.snap b/tests/format/js/do/__snapshots__/jsfmt.spec.js.snap
index e975afc7e768..c45caccc8275 100644
--- a/tests/format/js/do/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/js/do/__snapshots__/jsfmt.spec.js.snap
@@ -252,7 +252,7 @@ function foo() {
}
(do {});
-(do {} + 1);
+(do {}) + 1;
1 + do {};
() => do {};
diff --git a/tests/format/js/function/__snapshots__/jsfmt.spec.js.snap b/tests/format/js/function/__snapshots__/jsfmt.spec.js.snap
index eceb788eb601..880984ffee97 100644
--- a/tests/format/js/function/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/js/function/__snapshots__/jsfmt.spec.js.snap
@@ -19,7 +19,7 @@ a + function() {};
new function() {};
=====================================output=====================================
-(function () {}.length);
+(function () {}).length;
typeof function () {};
export default (function () {})();
(function () {})()\`\`;
@@ -27,7 +27,7 @@ export default (function () {})();
new (function () {})();
(function () {});
a = function f() {} || b;
-(function () {} && a);
+(function () {}) && a;
a + function () {};
new (function () {})();
diff --git a/tests/format/js/method-chain/__snapshots__/jsfmt.spec.js.snap b/tests/format/js/method-chain/__snapshots__/jsfmt.spec.js.snap
index d62714180449..0fb85afc2c6c 100644
--- a/tests/format/js/method-chain/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/js/method-chain/__snapshots__/jsfmt.spec.js.snap
@@ -1522,8 +1522,8 @@ method()
["abc"]((x) => x)
[abc]((x) => x);
-({}.a().b());
-({}.a().b());
+({}).a().b();
+({}).a().b();
================================================================================
`;
diff --git a/tests/format/js/no-semi/__snapshots__/jsfmt.spec.js.snap b/tests/format/js/no-semi/__snapshots__/jsfmt.spec.js.snap
index 806961a62f05..0029a99c7b8e 100644
--- a/tests/format/js/no-semi/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/js/no-semi/__snapshots__/jsfmt.spec.js.snap
@@ -727,9 +727,9 @@ x
x
;(() => {})()
x
-;({ a: 1 }.entries())
+;({ a: 1 }).entries()
x
-;({ a: 1 }.entries())
+;({ a: 1 }).entries()
x
;
x
@@ -910,9 +910,9 @@ x;
x;
(() => {})();
x;
-({ a: 1 }.entries());
+({ a: 1 }).entries();
x;
-({ a: 1 }.entries());
+({ a: 1 }).entries();
x;
;
x;
diff --git a/tests/format/js/objects/__snapshots__/jsfmt.spec.js.snap b/tests/format/js/objects/__snapshots__/jsfmt.spec.js.snap
index 0ebca7556d93..0964f4242127 100644
--- a/tests/format/js/objects/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/js/objects/__snapshots__/jsfmt.spec.js.snap
@@ -129,12 +129,12 @@ const a3 = {
=====================================output=====================================
() => ({}\`\`);
-({}\`\`);
+({})\`\`;
a = () => ({}.x);
-({} && a, b);
-({}::b, 0);
-({}::b()\`\`[""].c++ && 0 ? 0 : 0, 0);
-({}(), 0);
+({}) && a, b;
+({})::b, 0;
+({})::b()\`\`[""].c++ && 0 ? 0 : 0, 0;
+({})(), 0;
({} = 0);
({} = 0), 1;
diff --git a/tests/format/js/optional-chaining/__snapshots__/jsfmt.spec.js.snap b/tests/format/js/optional-chaining/__snapshots__/jsfmt.spec.js.snap
index 04f80f3b84fe..c1c21992f5f0 100644
--- a/tests/format/js/optional-chaining/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/js/optional-chaining/__snapshots__/jsfmt.spec.js.snap
@@ -172,8 +172,8 @@ a = () => ({}?.b() && a);
(x) => ({}?.().b);
(x) => ({}?.b());
(x) => ({}?.b.b);
-({}?.a().b());
-({ a: 1 }?.entries());
+({})?.a().b();
+({ a: 1 })?.entries();
new (foo?.bar)();
new (foo?.bar())();
diff --git a/tests/format/js/template/__snapshots__/jsfmt.spec.js.snap b/tests/format/js/template/__snapshots__/jsfmt.spec.js.snap
index 00b56b604d4c..4837c1522f82 100644
--- a/tests/format/js/template/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/js/template/__snapshots__/jsfmt.spec.js.snap
@@ -391,7 +391,7 @@ async function f() {
b()\`\`;
// "ClassExpression"
-(class {}\`\`);
+(class {})\`\`;
// "ConditionalExpression"
(b ? c : d)\`\`;
@@ -409,7 +409,7 @@ b.c\`\`;
new B()\`\`;
// "ObjectExpression"
-({}\`\`);
+({})\`\`;
// "SequenceExpression"
(b, c)\`\`;
diff --git a/tests/format/typescript/as/__snapshots__/jsfmt.spec.js.snap b/tests/format/typescript/as/__snapshots__/jsfmt.spec.js.snap
index 27a91f18a2e4..116898b60be7 100644
--- a/tests/format/typescript/as/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/typescript/as/__snapshots__/jsfmt.spec.js.snap
@@ -81,14 +81,14 @@ export default class Column extends (RcTable.Column as React.ComponentClass<
>) {}
export const MobxTypedForm = class extends (Form as { new (): any }) {};
export abstract class MobxTypedForm1 extends (Form as { new (): any }) {}
-({} as {});
+({}) as {};
function* g() {
const test = (yield "foo") as number;
}
async function g1() {
const test = (await "foo") as number;
}
-({} as X);
+({}) as X;
() => ({} as X);
const state = JSON.stringify({
next: window.location.href,
diff --git a/tests/format/typescript/non-null/__snapshots__/jsfmt.spec.js.snap b/tests/format/typescript/non-null/__snapshots__/jsfmt.spec.js.snap
index 0a5b720109e7..69c079c618be 100644
--- a/tests/format/typescript/non-null/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/typescript/non-null/__snapshots__/jsfmt.spec.js.snap
@@ -35,9 +35,9 @@ const f = ((a) => {
log(a);
})!;
-if (a) ({ a, ...b }.a()!.c());
+if (a) ({ a, ...b }).a()!.c();
-(function () {}!());
+(function () {})!();
class a extends ({}!) {}
diff --git a/tests/format/typescript/satisfies-operators/__snapshots__/jsfmt.spec.js.snap b/tests/format/typescript/satisfies-operators/__snapshots__/jsfmt.spec.js.snap
index e86017c94b54..18ab6ef363fe 100644
--- a/tests/format/typescript/satisfies-operators/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/typescript/satisfies-operators/__snapshots__/jsfmt.spec.js.snap
@@ -282,7 +282,7 @@ let obj: { f(s: string): void } & Record = {
g(s) {},
} satisfies { g(s: string): void } & Record
-;({ f(x) {} } satisfies { f(s: string): void })
+;({ f(x) {} }) satisfies { f(s: string): void }
const car = {
start() {},
@@ -354,7 +354,7 @@ let obj: { f(s: string): void } & Record = {
g(s) {},
} satisfies { g(s: string): void } & Record;
-({ f(x) {} } satisfies { f(s: string): void });
+({ f(x) {} }) satisfies { f(s: string): void };
const car = {
start() {},
@@ -742,8 +742,8 @@ foo satisfies unknown as Bar;
foo as unknown satisfies Bar;
=====================================output=====================================
-;({} satisfies {})
-;({} satisfies X)
+;({}) satisfies {}
+;({}) satisfies X
;() => ({} satisfies X)
this.isTabActionBar((e.target || e.srcElement) satisfies HTMLElement)
@@ -798,8 +798,8 @@ foo satisfies unknown as Bar;
foo as unknown satisfies Bar;
=====================================output=====================================
-({} satisfies {});
-({} satisfies X);
+({}) satisfies {};
+({}) satisfies X;
() => ({} satisfies X);
this.isTabActionBar((e.target || e.srcElement) satisfies HTMLElement);