EmptyBlockCheck.java

1
///////////////////////////////////////////////////////////////////////////////////////////////
2
// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3
// Copyright (C) 2001-2022 the original author or authors.
4
//
5
// This library is free software; you can redistribute it and/or
6
// modify it under the terms of the GNU Lesser General Public
7
// License as published by the Free Software Foundation; either
8
// version 2.1 of the License, or (at your option) any later version.
9
//
10
// This library is distributed in the hope that it will be useful,
11
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
// Lesser General Public License for more details.
14
//
15
// You should have received a copy of the GNU Lesser General Public
16
// License along with this library; if not, write to the Free Software
17
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
///////////////////////////////////////////////////////////////////////////////////////////////
19
20
package com.puppycrawl.tools.checkstyle.checks.blocks;
21
22
import java.util.Arrays;
23
import java.util.Locale;
24
25
import com.puppycrawl.tools.checkstyle.StatelessCheck;
26
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
27
import com.puppycrawl.tools.checkstyle.api.DetailAST;
28
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
29
import com.puppycrawl.tools.checkstyle.utils.CodePointUtil;
30
import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
31
32
/**
33
 * <p>
34
 * Checks for empty blocks. This check does not validate sequential blocks.
35
 * </p>
36
 * <p>
37
 * Sequential blocks won't be checked. Also, no violations for fallthrough:
38
 * </p>
39
 * <pre>
40
 * switch (a) {
41
 *   case 1:                          // no violation
42
 *   case 2:                          // no violation
43
 *   case 3: someMethod(); { }        // no violation
44
 *   default: break;
45
 * }
46
 * </pre>
47
 * <p>
48
 * NOTE: This check processes LITERAL_CASE and LITERAL_DEFAULT separately.
49
 * Verification empty block is done for single nearest {@code case} or {@code default}.
50
 * </p>
51
 * <ul>
52
 * <li>
53
 * Property {@code option} - specify the policy on block contents.
54
 * Type is {@code com.puppycrawl.tools.checkstyle.checks.blocks.BlockOption}.
55
 * Default value is {@code statement}.
56
 * </li>
57
 * <li>
58
 * Property {@code tokens} - tokens to check
59
 * Type is {@code java.lang.String[]}.
60
 * Validation type is {@code tokenSet}.
61
 * Default value is:
62
 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHILE">
63
 * LITERAL_WHILE</a>,
64
 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_TRY">
65
 * LITERAL_TRY</a>,
66
 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FINALLY">
67
 * LITERAL_FINALLY</a>,
68
 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO">
69
 * LITERAL_DO</a>,
70
 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_IF">
71
 * LITERAL_IF</a>,
72
 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_ELSE">
73
 * LITERAL_ELSE</a>,
74
 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FOR">
75
 * LITERAL_FOR</a>,
76
 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INSTANCE_INIT">
77
 * INSTANCE_INIT</a>,
78
 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STATIC_INIT">
79
 * STATIC_INIT</a>,
80
 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SWITCH">
81
 * LITERAL_SWITCH</a>,
82
 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SYNCHRONIZED">
83
 * LITERAL_SYNCHRONIZED</a>.
84
 * </li>
85
 * </ul>
86
 * <p>
87
 * To configure the check:
88
 * </p>
89
 * <pre>
90
 * &lt;module name="EmptyBlock"/&gt;
91
 * </pre>
92
 * <p>
93
 * Example:
94
 * </p>
95
 * <pre>
96
 * public class Test {
97
 *   private void emptyLoop() {
98
 *     for (int i = 0; i &lt; 10; i++) { // violation
99
 *     }
100
 *
101
 *     try { // violation
102
 *
103
 *     } catch (Exception e) {
104
 *       // ignored
105
 *     }
106
 *   }
107
 * }
108
 * </pre>
109
 * <p>
110
 * To configure the check for the {@code text} policy and only {@code try} blocks:
111
 * </p>
112
 * <pre>
113
 * &lt;module name=&quot;EmptyBlock&quot;&gt;
114
 *   &lt;property name=&quot;option&quot; value=&quot;text&quot;/&gt;
115
 *   &lt;property name=&quot;tokens&quot; value=&quot;LITERAL_TRY&quot;/&gt;
116
 * &lt;/module&gt;
117
 * </pre>
118
 * <p> Example: </p>
119
 * <pre>
120
 * public class Test {
121
 *   private void emptyLoop() {
122
 *     for (int i = 0; i &lt; 10; i++) {
123
 *       // ignored
124
 *     }
125
 *
126
 *     // violation on next line
127
 *     try {
128
 *
129
 *     } catch (Exception e) {
130
 *       // ignored
131
 *     }
132
 *   }
133
 * }
134
 * </pre>
135
 * <p>
136
 * To configure the check for default in switch block:
137
 * </p>
138
 * <pre>
139
 * &lt;module name=&quot;EmptyBlock&quot;&gt;
140
 *   &lt;property name=&quot;tokens&quot; value=&quot;LITERAL_DEFAULT&quot;/&gt;
141
 * &lt;/module&gt;
142
 * </pre>
143
 * <p> Example: </p>
144
 * <pre>
145
 * public class Test {
146
 *   private void test(int a) {
147
 *     switch (a) {
148
 *       case 1: someMethod();
149
 *       default: // OK, as there is no block
150
 *     }
151
 *     switch (a) {
152
 *       case 1: someMethod();
153
 *       default: {} // violation
154
 *     }
155
 *   }
156
 * }
157
 * </pre>
158
 * <p>
159
 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
160
 * </p>
161
 * <p>
162
 * Violation Message Keys:
163
 * </p>
164
 * <ul>
165
 * <li>
166
 * {@code block.empty}
167
 * </li>
168
 * <li>
169
 * {@code block.noStatement}
170
 * </li>
171
 * </ul>
172
 *
173
 * @since 3.0
174
 */
175
@StatelessCheck
176
public class EmptyBlockCheck
177
    extends AbstractCheck {
178
179
    /**
180
     * A key is pointing to the warning message text in "messages.properties"
181
     * file.
182
     */
183
    public static final String MSG_KEY_BLOCK_NO_STATEMENT = "block.noStatement";
184
185
    /**
186
     * A key is pointing to the warning message text in "messages.properties"
187
     * file.
188
     */
189
    public static final String MSG_KEY_BLOCK_EMPTY = "block.empty";
190
191
    /** Specify the policy on block contents. */
192
    private BlockOption option = BlockOption.STATEMENT;
193
194
    /**
195
     * Setter to specify the policy on block contents.
196
     *
197
     * @param optionStr string to decode option from
198
     * @throws IllegalArgumentException if unable to decode
199
     */
200
    public void setOption(String optionStr) {
201
        option = BlockOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
202
    }
203
204
    @Override
205
    public int[] getDefaultTokens() {
206
        return new int[] {
207
            TokenTypes.LITERAL_WHILE,
208
            TokenTypes.LITERAL_TRY,
209
            TokenTypes.LITERAL_FINALLY,
210
            TokenTypes.LITERAL_DO,
211
            TokenTypes.LITERAL_IF,
212
            TokenTypes.LITERAL_ELSE,
213
            TokenTypes.LITERAL_FOR,
214
            TokenTypes.INSTANCE_INIT,
215
            TokenTypes.STATIC_INIT,
216
            TokenTypes.LITERAL_SWITCH,
217
            TokenTypes.LITERAL_SYNCHRONIZED,
218
        };
219
    }
220
221
    @Override
222
    public int[] getAcceptableTokens() {
223
        return new int[] {
224
            TokenTypes.LITERAL_WHILE,
225
            TokenTypes.LITERAL_TRY,
226
            TokenTypes.LITERAL_CATCH,
227
            TokenTypes.LITERAL_FINALLY,
228
            TokenTypes.LITERAL_DO,
229
            TokenTypes.LITERAL_IF,
230
            TokenTypes.LITERAL_ELSE,
231
            TokenTypes.LITERAL_FOR,
232
            TokenTypes.INSTANCE_INIT,
233
            TokenTypes.STATIC_INIT,
234
            TokenTypes.LITERAL_SWITCH,
235
            TokenTypes.LITERAL_SYNCHRONIZED,
236
            TokenTypes.LITERAL_CASE,
237
            TokenTypes.LITERAL_DEFAULT,
238
            TokenTypes.ARRAY_INIT,
239
        };
240
    }
241
242
    @Override
243
    public int[] getRequiredTokens() {
244
        return CommonUtil.EMPTY_INT_ARRAY;
245
    }
246
247
    @Override
248
    public void visitToken(DetailAST ast) {
249
        final DetailAST leftCurly = findLeftCurly(ast);
250
        if (leftCurly != null) {
251
            if (option == BlockOption.STATEMENT) {
252
                final boolean emptyBlock;
253
                if (leftCurly.getType() == TokenTypes.LCURLY) {
254
                    final DetailAST nextSibling = leftCurly.getNextSibling();
255
                    emptyBlock = nextSibling.getType() != TokenTypes.CASE_GROUP
256
                            && nextSibling.getType() != TokenTypes.SWITCH_RULE;
257
                }
258
                else {
259
                    emptyBlock = leftCurly.getChildCount() <= 1;
260
                }
261
                if (emptyBlock) {
262
                    log(leftCurly,
263
                        MSG_KEY_BLOCK_NO_STATEMENT,
264
                        ast.getText());
265
                }
266
            }
267
            else if (!hasText(leftCurly)) {
268
                log(leftCurly,
269
                    MSG_KEY_BLOCK_EMPTY,
270
                    ast.getText());
271
            }
272
273
            if (leftCurly == null) {
274
                if (leftCurly.getType() == TokenTypes.ANNOTATION_DEF) {
275
                   int x = 12 + 123;
276
                   int y = 123 - 4;
277 1 1. visitToken : Replaced integer addition with subtraction → NO_COVERAGE
                   int z = y + x;
278
                }
279
            }
280
        }
281
    }
282
283
    /**
284
     * Checks if SLIST token contains any text.
285
     *
286
     * @param slistAST a {@code DetailAST} value
287
     * @return whether the SLIST token contains any text.
288
     */
289
    private boolean hasText(final DetailAST slistAST) {
290
        final DetailAST rightCurly = slistAST.findFirstToken(TokenTypes.RCURLY);
291
        final DetailAST rcurlyAST;
292
293
        if (rightCurly == null) {
294
            rcurlyAST = slistAST.getParent().findFirstToken(TokenTypes.RCURLY);
295
        }
296
        else {
297
            rcurlyAST = rightCurly;
298
        }
299
        final int slistLineNo = slistAST.getLineNo();
300
        final int slistColNo = slistAST.getColumnNo();
301
        final int rcurlyLineNo = rcurlyAST.getLineNo();
302
        final int rcurlyColNo = rcurlyAST.getColumnNo();
303
        boolean returnValue = false;
304
        if (slistLineNo == rcurlyLineNo) {
305
            // Handle braces on the same line
306 2 1. hasText : Replaced integer subtraction with addition → KILLED
2. hasText : Replaced integer addition with subtraction → KILLED
            final int[] txt = Arrays.copyOfRange(getLineCodePoints(slistLineNo - 1),
307
                    slistColNo + 1, rcurlyColNo);
308
309
            if (!CodePointUtil.isBlank(txt)) {
310
                returnValue = true;
311
            }
312
        }
313
        else {
314 1 1. hasText : Replaced integer subtraction with addition → KILLED
            final int[] codePointsFirstLine = getLineCodePoints(slistLineNo - 1);
315 1 1. hasText : Replaced integer addition with subtraction → KILLED
            final int[] firstLine = Arrays.copyOfRange(codePointsFirstLine,
316
                    slistColNo + 1, codePointsFirstLine.length);
317 1 1. hasText : Replaced integer subtraction with addition → KILLED
            final int[] codePointsLastLine = getLineCodePoints(rcurlyLineNo - 1);
318
            final int[] lastLine = Arrays.copyOfRange(codePointsLastLine, 0, rcurlyColNo);
319
            // check if all lines are also only whitespace
320
            returnValue = !(CodePointUtil.isBlank(firstLine) && CodePointUtil.isBlank(lastLine))
321
                    || !checkIsAllLinesAreWhitespace(slistLineNo, rcurlyLineNo);
322
        }
323
        return returnValue;
324
    }
325
326
    /**
327
     * Checks is all lines from 'lineFrom' to 'lineTo' (exclusive)
328
     * contain whitespaces only.
329
     *
330
     * @param lineFrom
331
     *            check from this line number
332
     * @param lineTo
333
     *            check to this line numbers
334
     * @return true if lines contain only whitespaces
335
     */
336
    private boolean checkIsAllLinesAreWhitespace(int lineFrom, int lineTo) {
337
        boolean result = true;
338 1 1. checkIsAllLinesAreWhitespace : Replaced integer subtraction with addition → KILLED
        for (int i = lineFrom; i < lineTo - 1; i++) {
339
            if (!CodePointUtil.isBlank(getLineCodePoints(i))) {
340
                result = false;
341
                break;
342
            }
343
        }
344
        return result;
345
    }
346
347
    /**
348
     * Calculates the left curly corresponding to the block to be checked.
349
     *
350
     * @param ast a {@code DetailAST} value
351
     * @return the left curly corresponding to the block to be checked
352
     */
353
    private static DetailAST findLeftCurly(DetailAST ast) {
354
        final DetailAST leftCurly;
355
        final DetailAST slistAST = ast.findFirstToken(TokenTypes.SLIST);
356
        if ((ast.getType() == TokenTypes.LITERAL_CASE
357
                || ast.getType() == TokenTypes.LITERAL_DEFAULT)
358
                && ast.getNextSibling() != null
359
                && ast.getNextSibling().getFirstChild() != null
360
                && ast.getNextSibling().getFirstChild().getType() == TokenTypes.SLIST) {
361
            leftCurly = ast.getNextSibling().getFirstChild();
362
        }
363
        else if (slistAST == null) {
364
            leftCurly = ast.findFirstToken(TokenTypes.LCURLY);
365
        }
366
        else {
367
            leftCurly = slistAST;
368
        }
369
        return leftCurly;
370
    }
371
372
}

Mutations

277

1.1
Location : visitToken
Killed by : none
Replaced integer addition with subtraction → NO_COVERAGE

306

1.1
Location : hasText
Killed by : com.puppycrawl.tools.checkstyle.checks.blocks.EmptyBlockCheckTest.[engine:junit-jupiter]/[class:com.puppycrawl.tools.checkstyle.checks.blocks.EmptyBlockCheckTest]/[method:allowEmptyLoopsText()]
Replaced integer subtraction with addition → KILLED

2.2
Location : hasText
Killed by : com.puppycrawl.tools.checkstyle.checks.blocks.EmptyBlockCheckTest.[engine:junit-jupiter]/[class:com.puppycrawl.tools.checkstyle.checks.blocks.EmptyBlockCheckTest]/[method:testText()]
Replaced integer addition with subtraction → KILLED

314

1.1
Location : hasText
Killed by : com.puppycrawl.tools.checkstyle.checks.blocks.EmptyBlockCheckTest.[engine:junit-jupiter]/[class:com.puppycrawl.tools.checkstyle.checks.blocks.EmptyBlockCheckTest]/[method:testText()]
Replaced integer subtraction with addition → KILLED

315

1.1
Location : hasText
Killed by : com.puppycrawl.tools.checkstyle.checks.blocks.EmptyBlockCheckTest.[engine:junit-jupiter]/[class:com.puppycrawl.tools.checkstyle.checks.blocks.EmptyBlockCheckTest]/[method:testText()]
Replaced integer addition with subtraction → KILLED

317

1.1
Location : hasText
Killed by : com.puppycrawl.tools.checkstyle.checks.blocks.EmptyBlockCheckTest.[engine:junit-jupiter]/[class:com.puppycrawl.tools.checkstyle.checks.blocks.EmptyBlockCheckTest]/[method:testText()]
Replaced integer subtraction with addition → KILLED

338

1.1
Location : checkIsAllLinesAreWhitespace
Killed by : com.puppycrawl.tools.checkstyle.checks.blocks.EmptyBlockCheckTest.[engine:junit-jupiter]/[class:com.puppycrawl.tools.checkstyle.checks.blocks.EmptyBlockCheckTest]/[method:testText()]
Replaced integer subtraction with addition → KILLED

Active mutators

Tests examined


Report generated by PIT 1.8.0