Skip to content

[2.10.4] Parentheses dropped from a negated group containing a boundary block comment, silently inverting the condition #1069

Description

@jameshearttech

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:

return !(a || b);

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions