Summary
When a parenthesised expression is the operand of !, and that expression contains a block comment in the leading or trailing position, the parentheses are dropped. The result parses and compiles, but means something different — !(a || b) becomes !a || b.
Nothing fails. prettier --write succeeds, a subsequent --check passes, and the code now has a different truth table.
Input
public class Repro {
boolean f(boolean a, boolean b) {
return !(a || b /* note */);
}
}
Actual output
public class Repro {
boolean f(boolean a, boolean b) {
return !a || b /* note */;
}
}
Expected output
The parentheses preserved. Without the comment they are:
Why this is worse than a formatting nit
Both forms compile, so the change is silent:
a |
b |
!(a || b) |
!a || b |
| false |
false |
true |
true |
| false |
true |
false |
true |
| true |
false |
false |
false |
| true |
true |
false |
true |
Wrong on half the inputs, with a green build.
Scope
A block comment must sit at the leading or trailing edge inside the negated group. Interior comments are fine.
| Input |
Parens kept? |
!(a || b /* n */) |
no |
!(/* n */ a || b) |
no |
!((a || b) && c /* n */) |
no |
!(a /* n */ || b) |
yes |
!(a || b) /* n */ |
yes |
!(a || b) |
yes |
(a || b /* n */) && c |
yes |
Two things this is not sensitive to:
- Line length. The one-liner above is well under the print width and still loses its parentheses. It does not need to wrap.
- Comment style. Only block comments trigger it;
// comments in the same positions are safe.
The last row is suggestive of the mechanism: there the comment is also hoisted out past the closing paren, but the parentheses survive because nothing depends on them. It looks like the boundary comment is being attached above the parenthesised group, and in the ! case the group's parentheses are lost along with it.
Regression range
Introduced in 2.10.0 and present in every release since, including the current 2.10.4:
| Version |
Result |
| 2.9.7 |
return !(a || b /* n */); — correct |
| 2.10.0 |
return !a || b /* n */; — inverted |
| 2.10.1 |
inverted |
| 2.10.2 |
inverted |
| 2.10.3 |
inverted |
| 2.10.4 |
inverted |
2.10.0 is where #944 introduced parenthesis removal, so this looks like a defect in that feature's interaction with comment attachment rather than a separate printer path.
Related, but not duplicates
Versions
prettier-plugin-java 2.10.4 (bisected above)
prettier 3.6.2
- Node 22.13.1, Linux
Reproduction
npm install prettier@3.6.2 prettier-plugin-java@2.10.4
cat > Repro.java <<'EOF'
public class Repro {
boolean f(boolean a, boolean b) {
return !(a || b /* note */);
}
}
EOF
npx prettier --plugin=prettier-plugin-java --write Repro.java
cat Repro.java
How we hit it
A routine reformat of an existing file silently inverted a guard clause in a private codebase. It was caught only by luck — the operands there were boxed types, so the mangled expression happened to be a type error. With boolean operands, as above, it compiles and ships.
Summary
When a parenthesised expression is the operand of
!, and that expression contains a block comment in the leading or trailing position, the parentheses are dropped. The result parses and compiles, but means something different —!(a || b)becomes!a || b.Nothing fails.
prettier --writesucceeds, a subsequent--checkpasses, and the code now has a different truth table.Input
Actual output
Expected output
The parentheses preserved. Without the comment they are:
Why this is worse than a formatting nit
Both forms compile, so the change is silent:
ab!(a || b)!a || bWrong on half the inputs, with a green build.
Scope
A block comment must sit at the leading or trailing edge inside the negated group. Interior comments are fine.
!(a || b /* n */)!(/* n */ a || b)!((a || b) && c /* n */)!(a /* n */ || b)!(a || b) /* n */!(a || b)(a || b /* n */) && cTwo things this is not sensitive to:
//comments in the same positions are safe.The last row is suggestive of the mechanism: there the comment is also hoisted out past the closing paren, but the parentheses survive because nothing depends on them. It looks like the boundary comment is being attached above the parenthesised group, and in the
!case the group's parentheses are lost along with it.Regression range
Introduced in 2.10.0 and present in every release since, including the current 2.10.4:
return !(a || b /* n */);— correctreturn !a || b /* n */;— inverted2.10.0 is where #944 introduced parenthesis removal, so this looks like a defect in that feature's interaction with comment attachment rather than a separate printer path.
Related, but not duplicates
Versions
prettier-plugin-java2.10.4 (bisected above)prettier3.6.2Reproduction
How we hit it
A routine reformat of an existing file silently inverted a guard clause in a private codebase. It was caught only by luck — the operands there were boxed types, so the mangled expression happened to be a type error. With
booleanoperands, as above, it compiles and ships.