diff --git a/changelog_unreleased/javascript/15468.md b/changelog_unreleased/javascript/15468.md new file mode 100644 index 000000000..690335f5f --- /dev/null +++ b/changelog_unreleased/javascript/15468.md @@ -0,0 +1,24 @@ +#### Disambiguate unary expressions on left hand side of instanceof and in (#15468 by @lucacasonato) + +Parentheses are now added around unary expression on the left hand side of +`instanceof` and `in` expressions, to disambiguate the unary on the left hand +side with a unary applying to the entire binary expression. + +This helps catch a common mistake where a user intends to write `!("x" in y)` +but instead writes `!"x" in y`, which is really parsed as the nonsensical +`(!"x") in y`. + + +```js +// Input +!"x" in y; +!("x" in y); + +// Prettier stable +!"x" in y; +!("x" in y); + +// Prettier main +(!"x") in y; +!("x" in y); +``` diff --git a/src/language-js/needs-parens.js b/src/language-js/needs-parens.js index 987362be8..f51b8dbda 100644 --- a/src/language-js/needs-parens.js +++ b/src/language-js/needs-parens.js @@ -241,6 +241,19 @@ function needsParens(path, options) { return true; } break; + + // A user typing `!foo instanceof Bar` probably intended + // `!(foo instanceof Bar)`, so format to `(!foo) instance Bar` to what is + // really happening + case "BinaryExpression": + if ( + key === "left" && + (parent.operator === "in" || parent.operator === "instanceof") && + node.type === "UnaryExpression" + ) { + return true; + } + break; } switch (node.type) { 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 13ec952db..d06376d8e 100644 --- a/tests/format/flow-repo/binary/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/flow-repo/binary/__snapshots__/jsfmt.spec.js.snap @@ -97,7 +97,7 @@ let tests = [ // bogus stuff on LHS function () { null in {}; // error - void 0 in {}; // error + (void 0) in {}; // error ({}) in {}; // error [] in {}; // error false in []; // error @@ -107,7 +107,7 @@ let tests = [ function () { if ("foo" in 123) { } // error - if (!"foo" in {}) { + if ((!"foo") in {}) { } // error, !'foo' is a boolean if (!("foo" in {})) { } diff --git a/tests/format/js/binary-expressions/__snapshots__/jsfmt.spec.js.snap b/tests/format/js/binary-expressions/__snapshots__/jsfmt.spec.js.snap index a74499621..04e294c24 100644 --- a/tests/format/js/binary-expressions/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/js/binary-expressions/__snapshots__/jsfmt.spec.js.snap @@ -424,6 +424,164 @@ if ( ================================================================================ `; +exports[`in_instanceof.js format 1`] = ` +====================================options===================================== +parsers: ["babel", "flow", "typescript"] +printWidth: 80 + | printWidth +=====================================input====================================== +!foo in bar; +(!foo in bar); +!(foo in bar); +(!foo) in bar; + +!foo instanceof Bar; +(!foo instanceof Bar); +!(foo instanceof Bar); +(!foo) instanceof Bar; + +~foo in bar; +(~foo in bar); +~(foo in bar); +(~foo) in bar; + +~foo instanceof Bar; +(~foo instanceof Bar); +~(foo instanceof Bar); +(~foo) instanceof Bar; + ++foo in bar; +(+foo in bar); ++(foo in bar); +(+foo) in bar; + ++foo instanceof Bar; +(+foo instanceof Bar); ++(foo instanceof Bar); +(+foo) instanceof Bar; + +-foo in bar; +(-foo in bar); +-(foo in bar); +(-foo) in bar; + +-foo instanceof Bar; +(-foo instanceof Bar); +-(foo instanceof Bar); +(-foo) instanceof Bar; + +void 0 in bar; +(void 0 in bar); +void (0 in bar); +(void 0) in bar; + +void 0 instanceof bar; +(void 0 instanceof bar); +void (0 instanceof bar); +(void 0) instanceof bar; + +delete 0 in bar; +(delete 0 in bar); +delete (0 in bar); +(delete 0) in bar; + +delete 0 instanceof bar; +(delete 0 instanceof bar); +delete (0 instanceof bar); +(delete 0) instanceof bar; + +typeof 0 in bar; +(typeof 0 in bar); +typeof (0 in bar); +(typeof 0) in bar; + +typeof 0 instanceof bar; +(typeof 0 instanceof bar); +typeof (0 instanceof bar); +(typeof 0) instanceof bar; + +++x instanceof bar; // not ambiguous, because ++(x instanceof bar) is obviously invalid + +!!foo instanceof Bar; + +=====================================output===================================== +(!foo) in bar; +(!foo) in bar; +!(foo in bar); +(!foo) in bar; + +(!foo) instanceof Bar; +(!foo) instanceof Bar; +!(foo instanceof Bar); +(!foo) instanceof Bar; + +(~foo) in bar; +(~foo) in bar; +~(foo in bar); +(~foo) in bar; + +(~foo) instanceof Bar; +(~foo) instanceof Bar; +~(foo instanceof Bar); +(~foo) instanceof Bar; + +(+foo) in bar; +(+foo) in bar; ++(foo in bar); +(+foo) in bar; + +(+foo) instanceof Bar; +(+foo) instanceof Bar; ++(foo instanceof Bar); +(+foo) instanceof Bar; + +(-foo) in bar; +(-foo) in bar; +-(foo in bar); +(-foo) in bar; + +(-foo) instanceof Bar; +(-foo) instanceof Bar; +-(foo instanceof Bar); +(-foo) instanceof Bar; + +(void 0) in bar; +(void 0) in bar; +void (0 in bar); +(void 0) in bar; + +(void 0) instanceof bar; +(void 0) instanceof bar; +void (0 instanceof bar); +(void 0) instanceof bar; + +(delete 0) in bar; +(delete 0) in bar; +delete (0 in bar); +(delete 0) in bar; + +(delete 0) instanceof bar; +(delete 0) instanceof bar; +delete (0 instanceof bar); +(delete 0) instanceof bar; + +(typeof 0) in bar; +(typeof 0) in bar; +typeof (0 in bar); +(typeof 0) in bar; + +(typeof 0) instanceof bar; +(typeof 0) instanceof bar; +typeof (0 instanceof bar); +(typeof 0) instanceof bar; + +++x instanceof bar; // not ambiguous, because ++(x instanceof bar) is obviously invalid + +(!!foo) instanceof Bar; + +================================================================================ +`; + exports[`inline-jsx.js format 1`] = ` ====================================options===================================== parsers: ["babel", "flow", "typescript"] diff --git a/tests/format/js/binary-expressions/in_instanceof.js b/tests/format/js/binary-expressions/in_instanceof.js new file mode 100644 index 000000000..35e89db9f --- /dev/null +++ b/tests/format/js/binary-expressions/in_instanceof.js @@ -0,0 +1,73 @@ +!foo in bar; +(!foo in bar); +!(foo in bar); +(!foo) in bar; + +!foo instanceof Bar; +(!foo instanceof Bar); +!(foo instanceof Bar); +(!foo) instanceof Bar; + +~foo in bar; +(~foo in bar); +~(foo in bar); +(~foo) in bar; + +~foo instanceof Bar; +(~foo instanceof Bar); +~(foo instanceof Bar); +(~foo) instanceof Bar; + ++foo in bar; +(+foo in bar); ++(foo in bar); +(+foo) in bar; + ++foo instanceof Bar; +(+foo instanceof Bar); ++(foo instanceof Bar); +(+foo) instanceof Bar; + +-foo in bar; +(-foo in bar); +-(foo in bar); +(-foo) in bar; + +-foo instanceof Bar; +(-foo instanceof Bar); +-(foo instanceof Bar); +(-foo) instanceof Bar; + +void 0 in bar; +(void 0 in bar); +void (0 in bar); +(void 0) in bar; + +void 0 instanceof bar; +(void 0 instanceof bar); +void (0 instanceof bar); +(void 0) instanceof bar; + +delete 0 in bar; +(delete 0 in bar); +delete (0 in bar); +(delete 0) in bar; + +delete 0 instanceof bar; +(delete 0 instanceof bar); +delete (0 instanceof bar); +(delete 0) instanceof bar; + +typeof 0 in bar; +(typeof 0 in bar); +typeof (0 in bar); +(typeof 0) in bar; + +typeof 0 instanceof bar; +(typeof 0 instanceof bar); +typeof (0 instanceof bar); +(typeof 0) instanceof bar; + +++x instanceof bar; // not ambiguous, because ++(x instanceof bar) is obviously invalid + +!!foo instanceof Bar;