• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pmd / pmd / 197

19 Oct 2025 09:43PM UTC coverage: 78.676% (+0.03%) from 78.642%
197

push

github

oowekyala
Fix reviow comments on #6116

18231 of 24023 branches covered (75.89%)

Branch coverage included in aggregate %.

2 of 2 new or added lines in 1 file covered. (100.0%)

38 existing lines in 5 files now uncovered.

39748 of 49670 relevant lines covered (80.02%)

0.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.52
/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UselessParenthesesRule.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.lang.java.rule.codestyle;
6

7
import static net.sourceforge.pmd.lang.java.rule.codestyle.UselessParenthesesRule.Necessity.ALWAYS;
8
import static net.sourceforge.pmd.lang.java.rule.codestyle.UselessParenthesesRule.Necessity.BALANCING;
9
import static net.sourceforge.pmd.lang.java.rule.codestyle.UselessParenthesesRule.Necessity.CLARIFYING;
10
import static net.sourceforge.pmd.lang.java.rule.codestyle.UselessParenthesesRule.Necessity.NEVER;
11
import static net.sourceforge.pmd.lang.java.rule.codestyle.UselessParenthesesRule.Necessity.definitely;
12
import static net.sourceforge.pmd.lang.java.rule.codestyle.UselessParenthesesRule.Necessity.necessaryIf;
13

14
import net.sourceforge.pmd.lang.java.ast.ASTAssignmentExpression;
15
import net.sourceforge.pmd.lang.java.ast.ASTCastExpression;
16
import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression;
17
import net.sourceforge.pmd.lang.java.ast.ASTExpression;
18
import net.sourceforge.pmd.lang.java.ast.ASTInfixExpression;
19
import net.sourceforge.pmd.lang.java.ast.ASTLambdaExpression;
20
import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
21
import net.sourceforge.pmd.lang.java.ast.ASTSwitchExpression;
22
import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpression;
23
import net.sourceforge.pmd.lang.java.ast.BinaryOp;
24
import net.sourceforge.pmd.lang.java.ast.JavaNode;
25
import net.sourceforge.pmd.lang.java.ast.internal.PrettyPrintingUtil;
26
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
27
import net.sourceforge.pmd.properties.PropertyDescriptor;
28
import net.sourceforge.pmd.properties.PropertyFactory;
29
import net.sourceforge.pmd.util.AssertionUtil;
30

31

32
public final class UselessParenthesesRule extends AbstractJavaRulechainRule {
33
    // todo rename to UnnecessaryParentheses
34

35
    private static final PropertyDescriptor<Boolean> IGNORE_CLARIFYING =
1✔
36
        PropertyFactory.booleanProperty("ignoreClarifying")
1✔
37
                       .defaultValue(true)
1✔
38
                       .desc("Ignore parentheses that separate expressions of difference precedence,"
1✔
39
                                 + " like in `(a % 2 == 0) ? x : -x`")
40
                       .build();
1✔
41

42
    private static final PropertyDescriptor<Boolean> IGNORE_BALANCING =
1✔
43
        PropertyFactory.booleanProperty("ignoreBalancing")
1✔
44
                       .defaultValue(true)
1✔
45
                       .desc("Ignore unnecessary parentheses that appear balanced around an equality "
1✔
46
                                 + "operator, because the other operand requires parentheses."
47
                                 + "For example, in `(a == null) == (b == null)`, only the second pair "
48
                                 + "of parentheses is necessary, but the expression is clearer that way.")
49
                       .build();
1✔
50
    private static final int MAX_SNIPPET_LENGTH = 50;
51

52
    public UselessParenthesesRule() {
53
        super(ASTExpression.class);
1✔
54
        definePropertyDescriptor(IGNORE_CLARIFYING);
1✔
55
        definePropertyDescriptor(IGNORE_BALANCING);
1✔
56
    }
1✔
57

58
    private boolean reportClarifying() {
59
        return !getProperty(IGNORE_CLARIFYING);
1✔
60
    }
61

62
    private boolean reportBalancing() {
63
        return !getProperty(IGNORE_BALANCING);
1!
64
    }
65

66

67
    @Override
68
    public Object visitJavaNode(JavaNode node, Object data) {
69
        if (node instanceof ASTExpression) {
1!
70
            checkExpr((ASTExpression) node, data);
1✔
71
        } else {
72
            throw new IllegalArgumentException("Expected an expression, got " + node);
×
73
        }
74
        return null;
1✔
75
    }
76

77
    private void checkExpr(ASTExpression e, Object data) {
78
        if (!e.isParenthesized()) {
1✔
79
            return;
1✔
80
        }
81

82
        Necessity necessity = needsParentheses(e, e.getParent());
1✔
83

84
        if (necessity == NEVER
1✔
85
            || reportClarifying() && necessity == CLARIFYING
1✔
86
            || reportBalancing() && necessity == BALANCING) {
1!
87
            String template = e.getParenthesisDepth() > 1 ? "Duplicate parentheses around `{0}`."
1✔
88
                : "Useless parentheses around `{0}`.";
1✔
89
            CharSequence snippet = PrettyPrintingUtil.prettyPrint(e);
1✔
90
            String dots = "...";
1✔
91
            if (snippet.length() > MAX_SNIPPET_LENGTH) {
1✔
92
                snippet = snippet.subSequence(0, MAX_SNIPPET_LENGTH - dots.length()) + dots;
1✔
93
            }
94
            asCtx(data).addViolationWithMessage(e, template,
1✔
95
                snippet);
96

97
        }
98
    }
1✔
99

100
    public static Necessity needsParentheses(ASTExpression inner, JavaNode outer) {
101
        // Note: as of jdk 15, PatternExpression cannot be parenthesized
102
        // TypeExpression may never be parenthesized either
103
        assert inner.isParenthesized() : inner + " is not parenthesized";
1!
104

105

106
        if (inner.getParenthesisDepth() > 1
1✔
107
            || !(outer instanceof ASTExpression)) {
108
            // ((a + b))        unnecessary
109
            // new int[(2)]     unnecessary
110
            // return (1 + 2);
111
            return NEVER;
1✔
112
        }
113

114
        if (inner instanceof ASTPrimaryExpression) {
1✔
115
            return NEVER;
1✔
116
        }
117
        if (inner instanceof ASTSwitchExpression) {
1✔
118
            return outer instanceof ASTPrimaryExpression && inner.getIndexInParent() == 0
1✔
119
                    ? ALWAYS : NEVER;
1✔
120
        }
121

122
        if (outer instanceof ASTLambdaExpression) {
1✔
123
            // () -> (a + b)        unnecessary
124
            // () -> (() -> b)      unnecessary
125
            // () -> (a ? b : c);   unnecessary unless the parent of the lambda is itself a ternary
126
            if (inner instanceof ASTLambdaExpression) {
1✔
127
                return NEVER;
1✔
128
            }
129
            return definitely(inner instanceof ASTConditionalExpression && outer.getParent() instanceof ASTConditionalExpression);
1✔
130
        }
131

132
        if (inner instanceof ASTAssignmentExpression) {
1✔
133
            //  a * (b = c)          necessary
134
            //  a ? (b = c) : d      necessary
135
            //  a = (b = c)          associative
136
            //  (a = b) = c          (impossible)
137
            return outer instanceof ASTAssignmentExpression ? NEVER : ALWAYS;
1✔
138
        }
139

140
        if (inner instanceof ASTConditionalExpression) {
1✔
141

142
            // a ? (b ? c : d) : e    necessary
143
            // a ? b : (c ? d : e)    associative
144
            // (a ? b : c) ? d : e    necessary
145
            if (outer instanceof ASTConditionalExpression) {
1✔
146
                return inner.getIndexInParent() == 2 ? NEVER  // last child
1✔
147
                                                     : ALWAYS;
1✔
148
            } else {
149
                return necessaryIf(!(outer instanceof ASTAssignmentExpression));
1!
150
            }
151
        }
152

153
        if (inner instanceof ASTLambdaExpression) {
1✔
154
            // a ? (() -> b) + c : d     invalid, but necessary
155
            // a ? (() -> b) : d         clarifying
156
            return outer instanceof ASTConditionalExpression ? CLARIFYING
1✔
157
                                                             : definitely(!(outer instanceof ASTAssignmentExpression));
1✔
158
        }
159

160
        if (inner instanceof ASTInfixExpression) {
1✔
161
            if (outer instanceof ASTInfixExpression) {
1✔
162
                BinaryOp inop = ((ASTInfixExpression) inner).getOperator();
1✔
163
                BinaryOp outop = ((ASTInfixExpression) outer).getOperator();
1✔
164
                int comp = outop.comparePrecedence(inop);
1✔
165

166
                // (a * b) + c       unnecessary
167
                // a * (b + c)       necessary
168
                // a + (b + c)       unnecessary
169
                // (a + b) + c       unnecessary
170
                // (a - b) + c       clarifying
171

172
                if (comp > 0) {
1✔
173
                    return ALWAYS; // outer has greater precedence
1✔
174
                } else if (comp < 0) {
1✔
175
                    return CLARIFYING; // outer has lower precedence, but the operators are different
1✔
176
                }
177

178
                // the rest deals with ties in precedence
179

180
                if (inner.getIndexInParent() == 1) {
1✔
181
                    // parentheses are on the right
182
                    // eg a - (b + c)
183

184
                    if (associatesRightWith(outop, inop, (ASTInfixExpression) inner, (ASTInfixExpression) outer)) {
1✔
185
                        // a & (b & c)
186
                        // a | (b | c)
187
                        // a ^ (b ^ c)
188
                        // a && (b && c)
189
                        // a || (b || c)
190
                        return NEVER;
1✔
191
                    } else {
192
                        return ALWAYS;
1✔
193
                    }
194
                } else {
195
                    // parentheses are on the left
196
                    // eg (a + b) + c
197
                    if (outop.hasSamePrecedenceAs(BinaryOp.EQ) // EQ or NE
1✔
198
                        && ((ASTInfixExpression) outer).getRightOperand().isParenthesized()) {
1✔
199
                        // (a == null) == (b == null)
200
                        return BALANCING;
1✔
201
                    } else if (outop == BinaryOp.ADD
1✔
202
                        && inop.hasSamePrecedenceAs(BinaryOp.ADD) && inner.getTypeMirror().isNumeric()
1!
203
                        && !((ASTInfixExpression) outer).getTypeMirror().isNumeric()) {
1✔
204
                        return CLARIFYING;
1✔
205
                    }
206

207
                    return NEVER;
1✔
208
                }
209
            } else { // outer !is ASTInfixExpression
210
                if (outer instanceof ASTConditionalExpression && inner.getIndexInParent() == 0) {
1!
211
                    // (a == b) ? .. : ..
212
                    return CLARIFYING;
1✔
213
                }
214

215
                return necessaryIf(isUnary(outer) || outer instanceof ASTPrimaryExpression);
1!
216
            }
217
        }
218

219
        if (isUnary(inner)) {
1!
220
            return isUnary(outer) ? NEVER : necessaryIf(outer instanceof ASTPrimaryExpression);
1✔
221
        }
222

223

UNCOV
224
        throw AssertionUtil.shouldNotReachHere("Unhandled case inside " + outer);
×
225
    }
226

227
    private static boolean isUnary(JavaNode expr) {
228
        return expr instanceof ASTUnaryExpression || expr instanceof ASTCastExpression;
1✔
229
    }
230

231
    // Returns true if it is safe to remove parentheses in the expression `A outop (B inop C)`
232
    // outop and inop have the same precedence class
233
    private static boolean associatesRightWith(BinaryOp outop, BinaryOp inop, ASTInfixExpression inner, ASTInfixExpression outer) {
234

235
        /*
236
         Notes about associativity:
237
         - Integer multiplication/addition is associative when the operands are all of the same type.
238
         - Floating-point multiplication/addition is not associative.
239
         - The boolean equality operators are associative, but input type != output type in general
240
         - Bitwise and logical operators are associative
241
         - The conditional-OR/AND operator is fully associative with respect to both side effects and result value.
242
         */
243

244
        switch (inop) {
1✔
245
        case AND:
246
        case OR:
247
        case XOR:
248
        case CONDITIONAL_AND:
249
        case CONDITIONAL_OR:
250
            return true;
1✔
251
        case MUL:
252
            // a * (b * c)      -- yes
253
            // a * (b / c)      -- no, could change semantics
254
            // a * (b % c)      -- no
255

256
            // a / (b * c)      -- no
257
            // a / (b / c)      -- no
258
            // a / (b % c)      -- no
259

260
            // a % (b * c)      -- no
261
            // a % (b / c)      -- no
262
            // a % (b % c)      -- no
263

264
            return inop == outop; // == MUL
1✔
265
        case SUB:
266
        case ADD:
267
            // a + (b + c)      -- yes, unless outop is concatenation and inop is addition, or operands are floats
268
            // a + (b - c)      -- yes
269

270
            // a - (b + c)      -- no
271
            // a - (b - c)      -- no
272
            return outop == BinaryOp.ADD
1✔
273
                && outer.getTypeMirror().isPrimitive() == inner.getTypeMirror().isPrimitive()
1✔
274
                && !inner.getTypeMirror().isFloatingPoint();
1✔
275
        default:
276
            return false;
1✔
277
        }
278
    }
279

280
    public enum Necessity {
1✔
281
        ALWAYS,
1✔
282
        NEVER,
1✔
283
        CLARIFYING,
1✔
284
        BALANCING;
1✔
285

286
        static Necessity definitely(boolean b) {
287
            return b ? ALWAYS : NEVER;
1✔
288
        }
289

290
        static Necessity necessaryIf(boolean b) {
291
            return b ? ALWAYS : CLARIFYING;
1✔
292
        }
293
    }
294
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc