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 cf9e1d58345c..0fd0207dbf47 100644
--- a/src/language-js/needs-parens.js
+++ b/src/language-js/needs-parens.js
@@ -127,6 +127,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;
@@ -200,21 +220,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 b771d6f39e28..1e59f82362c7 100644
--- a/tests/format/flow-repo/arith/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/flow-repo/arith/__snapshots__/jsfmt.spec.js.snap
@@ -156,7 +156,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
@@ -322,10 +322,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 b5e32ea11a1b..ac49857961df 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
@@ -143,7 +143,7 @@ function foo() { this.m(); }
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 cc7c660dd953..3f779a172790 100644
--- a/tests/format/js/classes/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/js/classes/__snapshots__/jsfmt.spec.js.snap
@@ -101,10 +101,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;
================================================================================
`;
@@ -118,7 +118,7 @@ printWidth: 80
(class {})(class {});
=====================================output=====================================
-(class {}(class {}));
+(class {})(class {});
================================================================================
`;
@@ -209,8 +209,8 @@ printWidth: 80
(class {}).a;
=====================================output=====================================
-(class {}[1]);
-(class {}.a);
+(class {})[1];
+(class {}).a;
================================================================================
`;
@@ -336,7 +336,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 ec9c9b8ededf..85dfa50ae28a 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
@@ -375,14 +375,14 @@ semi: false
(@deco class {}).name;
=====================================output=====================================
-;((
+;(
@deco
class Foo {}
-).name)
-;((
+).name
+;(
@deco
class {}
-).name)
+).name
================================================================================
`;
@@ -397,14 +397,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 bea0659aa7ef..7efa5a964522 100644
--- a/tests/format/js/do/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/js/do/__snapshots__/jsfmt.spec.js.snap
@@ -250,7 +250,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 6c3bbf8272f7..7eefc33f02cd 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 5bdccf844893..ffe2433746e9 100644
--- a/tests/format/js/method-chain/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/js/method-chain/__snapshots__/jsfmt.spec.js.snap
@@ -1455,8 +1455,8 @@ method().then(x => x)
=====================================output=====================================
method().then((x) => x)["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 7e0011674862..3d1345e20120 100644
--- a/tests/format/js/objects/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/js/objects/__snapshots__/jsfmt.spec.js.snap
@@ -125,12 +125,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 be2cc728c600..ff80058803d7 100644
--- a/tests/format/js/template/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/js/template/__snapshots__/jsfmt.spec.js.snap
@@ -388,7 +388,7 @@ async function f() { (await b)\`\`; }
b()\`\`;
// "ClassExpression"
-(class {}\`\`);
+(class {})\`\`;
// "ConditionalExpression"
(b ? c : d)\`\`;
@@ -406,7 +406,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 d7a336fdb2f0..cc8dd0508fe8 100644
--- a/tests/format/typescript/as/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/typescript/as/__snapshots__/jsfmt.spec.js.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`array-pattern.ts format 1`] = `
+exports["array-pattern.ts format 1"] = `
====================================options=====================================
parsers: ["typescript"]
printWidth: 80
@@ -14,7 +14,7 @@ printWidth: 80
================================================================================
`;
-exports[`as.ts format 1`] = `
+exports["as.ts format 1"] = `
====================================options=====================================
parsers: ["typescript"]
printWidth: 80
@@ -81,10 +81,10 @@ 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, nonce } as State);
@@ -121,7 +121,7 @@ const iter2 = createIterator(
================================================================================
`;
-exports[`assignment.ts format 1`] = `
+exports["assignment.ts format 1"] = `
====================================options=====================================
parsers: ["typescript"]
printWidth: 80
@@ -191,7 +191,7 @@ this.intervalID = setInterval(() => { self.step(); }, 30) as unknown as number;
================================================================================
`;
-exports[`assignment2.ts format 1`] = `
+exports["assignment2.ts format 1"] = `
====================================options=====================================
parsers: ["typescript"]
printWidth: 80
@@ -265,7 +265,7 @@ const originalPrototype = originalConstructor.prototype as TComponent &
================================================================================
`;
-exports[`export_default_as.ts format 1`] = `
+exports["export_default_as.ts format 1"] = `
====================================options=====================================
parsers: ["typescript"]
printWidth: 80
@@ -279,7 +279,7 @@ export default (function log() {} as typeof console.log);
================================================================================
`;
-exports[`long-identifiers.ts format 1`] = `
+exports["long-identifiers.ts format 1"] = `
====================================options=====================================
parsers: ["typescript"]
printWidth: 80
@@ -320,7 +320,7 @@ averredBathersBoxroomBuggyNurl(
================================================================================
`;
-exports[`nested-await-and-as.ts format 1`] = `
+exports["nested-await-and-as.ts format 1"] = `
====================================options=====================================
parsers: ["typescript"]
printWidth: 80
@@ -347,7 +347,7 @@ const getAccountCount = async () =>
================================================================================
`;
-exports[`return.ts format 1`] = `
+exports["return.ts format 1"] = `
====================================options=====================================
parsers: ["typescript"]
printWidth: 80
@@ -366,7 +366,7 @@ function foo() { return { foo: 1, bar: 2 } as Foo; }
================================================================================
`;
-exports[`ternary.ts format 1`] = `
+exports["ternary.ts format 1"] = `
====================================options=====================================
parsers: ["typescript"]
printWidth: 80
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 8d2be798074c..50537948ccd3 100644
--- a/tests/format/typescript/non-null/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/typescript/non-null/__snapshots__/jsfmt.spec.js.snap
@@ -29,9 +29,9 @@ const myFunction3 = (key) => ({}!.a);
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 021fa989ab89..a09e92e078f8 100644
--- a/tests/format/typescript/satisfies-operators/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/typescript/satisfies-operators/__snapshots__/jsfmt.spec.js.snap
@@ -272,7 +272,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() {},
@@ -344,7 +344,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() {},
@@ -724,8 +724,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)
@@ -778,8 +778,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);