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

pmd / pmd / 130

06 Sep 2025 12:33PM UTC coverage: 78.533% (+0.04%) from 78.498%
130

push

github

oowekyala
Fix #4770: [java] UnusedFormalParameter should ignore public constructor as same as method (#5994)

Merge branch 'issue-4770-UnusedFormalParameter-should-ignore-public-constructor-as-same-as-method'

17953 of 23697 branches covered (75.76%)

Branch coverage included in aggregate %.

3 of 3 new or added lines in 2 files covered. (100.0%)

27 existing lines in 6 files now uncovered.

39280 of 49181 relevant lines covered (79.87%)

0.81 hits per line

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

93.3
/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 {
UNCOV
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
            || inner instanceof ASTSwitchExpression) {
116
            return NEVER;
1✔
117
        }
118

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

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

137
        if (inner instanceof ASTConditionalExpression) {
1✔
138

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

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

157
        if (inner instanceof ASTInfixExpression) {
1✔
158
            if (outer instanceof ASTInfixExpression) {
1✔
159
                BinaryOp inop = ((ASTInfixExpression) inner).getOperator();
1✔
160
                BinaryOp outop = ((ASTInfixExpression) outer).getOperator();
1✔
161
                int comp = outop.comparePrecedence(inop);
1✔
162

163
                // (a * b) + c       unnecessary
164
                // a * (b + c)       necessary
165
                // a + (b + c)       unnecessary
166
                // (a + b) + c       unnecessary
167
                // (a - b) + c       clarifying
168

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

175
                // the rest deals with ties in precedence
176

177
                if (inner.getIndexInParent() == 1) {
1✔
178
                    // parentheses are on the right
179
                    // eg a - (b + c)
180

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

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

212
                return necessaryIf(isUnary(outer) || outer instanceof ASTPrimaryExpression);
1!
213
            }
214
        }
215

216
        if (isUnary(inner)) {
1!
217
            return isUnary(outer) ? NEVER : necessaryIf(outer instanceof ASTPrimaryExpression);
1✔
218
        }
219

220

UNCOV
221
        throw AssertionUtil.shouldNotReachHere("Unhandled case inside " + outer);
×
222
    }
223

224
    private static boolean isUnary(JavaNode expr) {
225
        return expr instanceof ASTUnaryExpression || expr instanceof ASTCastExpression;
1✔
226
    }
227

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

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

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

253
            // a / (b * c)      -- no
254
            // a / (b / c)      -- no
255
            // a / (b % c)      -- no
256

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

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

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

277
    public enum Necessity {
1✔
278
        ALWAYS,
1✔
279
        NEVER,
1✔
280
        CLARIFYING,
1✔
281
        BALANCING;
1✔
282

283
        static Necessity definitely(boolean b) {
284
            return b ? ALWAYS : NEVER;
1✔
285
        }
286

287
        static Necessity necessaryIf(boolean b) {
288
            return b ? ALWAYS : CLARIFYING;
1✔
289
        }
290
    }
291
}
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