View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
88   * </p>
89   * <p>
90   * Violation Message Keys:
91   * </p>
92   * <ul>
93   * <li>
94   * {@code block.empty}
95   * </li>
96   * <li>
97   * {@code block.noStatement}
98   * </li>
99   * </ul>
100  *
101  * @since 3.0
102  */
103 @StatelessCheck
104 public class EmptyBlockCheck
105     extends AbstractCheck {
106 
107     /**
108      * A key is pointing to the warning message text in "messages.properties"
109      * file.
110      */
111     public static final String MSG_KEY_BLOCK_NO_STATEMENT = "block.noStatement";
112 
113     /**
114      * A key is pointing to the warning message text in "messages.properties"
115      * file.
116      */
117     public static final String MSG_KEY_BLOCK_EMPTY = "block.empty";
118 
119     /** Specify the policy on block contents. */
120     private BlockOption option = BlockOption.STATEMENT;
121 
122     /**
123      * Setter to specify the policy on block contents.
124      *
125      * @param optionStr string to decode option from
126      * @throws IllegalArgumentException if unable to decode
127      * @since 3.0
128      */
129     public void setOption(String optionStr) {
130         option = BlockOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
131     }
132 
133     @Override
134     public int[] getDefaultTokens() {
135         return new int[] {
136             TokenTypes.LITERAL_WHILE,
137             TokenTypes.LITERAL_TRY,
138             TokenTypes.LITERAL_FINALLY,
139             TokenTypes.LITERAL_DO,
140             TokenTypes.LITERAL_IF,
141             TokenTypes.LITERAL_ELSE,
142             TokenTypes.LITERAL_FOR,
143             TokenTypes.INSTANCE_INIT,
144             TokenTypes.STATIC_INIT,
145             TokenTypes.LITERAL_SWITCH,
146             TokenTypes.LITERAL_SYNCHRONIZED,
147         };
148     }
149 
150     @Override
151     public int[] getAcceptableTokens() {
152         return new int[] {
153             TokenTypes.LITERAL_WHILE,
154             TokenTypes.LITERAL_TRY,
155             TokenTypes.LITERAL_CATCH,
156             TokenTypes.LITERAL_FINALLY,
157             TokenTypes.LITERAL_DO,
158             TokenTypes.LITERAL_IF,
159             TokenTypes.LITERAL_ELSE,
160             TokenTypes.LITERAL_FOR,
161             TokenTypes.INSTANCE_INIT,
162             TokenTypes.STATIC_INIT,
163             TokenTypes.LITERAL_SWITCH,
164             TokenTypes.LITERAL_SYNCHRONIZED,
165             TokenTypes.LITERAL_CASE,
166             TokenTypes.LITERAL_DEFAULT,
167             TokenTypes.ARRAY_INIT,
168         };
169     }
170 
171     @Override
172     public int[] getRequiredTokens() {
173         return CommonUtil.EMPTY_INT_ARRAY;
174     }
175 
176     @Override
177     public void visitToken(DetailAST ast) {
178         final DetailAST leftCurly = findLeftCurly(ast);
179         if (leftCurly != null) {
180             if (option == BlockOption.STATEMENT) {
181                 final boolean emptyBlock;
182                 if (leftCurly.getType() == TokenTypes.LCURLY) {
183                     final DetailAST nextSibling = leftCurly.getNextSibling();
184                     emptyBlock = nextSibling.getType() != TokenTypes.CASE_GROUP
185                             && nextSibling.getType() != TokenTypes.SWITCH_RULE;
186                 }
187                 else {
188                     emptyBlock = leftCurly.getChildCount() <= 1;
189                 }
190                 if (emptyBlock) {
191                     log(leftCurly,
192                         MSG_KEY_BLOCK_NO_STATEMENT);
193                 }
194             }
195             else if (!hasText(leftCurly)) {
196                 log(leftCurly,
197                     MSG_KEY_BLOCK_EMPTY,
198                     ast.getText());
199             }
200         }
201     }
202 
203     /**
204      * Checks if SLIST token contains any text.
205      *
206      * @param slistAST a {@code DetailAST} value
207      * @return whether the SLIST token contains any text.
208      */
209     private boolean hasText(final DetailAST slistAST) {
210         final DetailAST rightCurly = slistAST.findFirstToken(TokenTypes.RCURLY);
211         final DetailAST rcurlyAST;
212 
213         if (rightCurly == null) {
214             rcurlyAST = slistAST.getParent().findFirstToken(TokenTypes.RCURLY);
215         }
216         else {
217             rcurlyAST = rightCurly;
218         }
219         final int slistLineNo = slistAST.getLineNo();
220         final int slistColNo = slistAST.getColumnNo();
221         final int rcurlyLineNo = rcurlyAST.getLineNo();
222         final int rcurlyColNo = rcurlyAST.getColumnNo();
223         boolean returnValue = false;
224         if (slistLineNo == rcurlyLineNo) {
225             // Handle braces on the same line
226             final int[] txt = Arrays.copyOfRange(getLineCodePoints(slistLineNo - 1),
227                     slistColNo + 1, rcurlyColNo);
228 
229             if (!CodePointUtil.isBlank(txt)) {
230                 returnValue = true;
231             }
232         }
233         else {
234             final int[] codePointsFirstLine = getLineCodePoints(slistLineNo - 1);
235             final int[] firstLine = Arrays.copyOfRange(codePointsFirstLine,
236                     slistColNo + 1, codePointsFirstLine.length);
237             final int[] codePointsLastLine = getLineCodePoints(rcurlyLineNo - 1);
238             final int[] lastLine = Arrays.copyOfRange(codePointsLastLine, 0, rcurlyColNo);
239             // check if all lines are also only whitespace
240             returnValue = !(CodePointUtil.isBlank(firstLine) && CodePointUtil.isBlank(lastLine))
241                     || !checkIsAllLinesAreWhitespace(slistLineNo, rcurlyLineNo);
242         }
243         return returnValue;
244     }
245 
246     /**
247      * Checks is all lines from 'lineFrom' to 'lineTo' (exclusive)
248      * contain whitespaces only.
249      *
250      * @param lineFrom
251      *            check from this line number
252      * @param lineTo
253      *            check to this line numbers
254      * @return true if lines contain only whitespaces
255      */
256     private boolean checkIsAllLinesAreWhitespace(int lineFrom, int lineTo) {
257         boolean result = true;
258         for (int i = lineFrom; i < lineTo - 1; i++) {
259             if (!CodePointUtil.isBlank(getLineCodePoints(i))) {
260                 result = false;
261                 break;
262             }
263         }
264         return result;
265     }
266 
267     /**
268      * Calculates the left curly corresponding to the block to be checked.
269      *
270      * @param ast a {@code DetailAST} value
271      * @return the left curly corresponding to the block to be checked
272      */
273     private static DetailAST findLeftCurly(DetailAST ast) {
274         final DetailAST leftCurly;
275         final DetailAST slistAST = ast.findFirstToken(TokenTypes.SLIST);
276         if ((ast.getType() == TokenTypes.LITERAL_CASE
277                 || ast.getType() == TokenTypes.LITERAL_DEFAULT)
278                 && ast.getNextSibling() != null
279                 && ast.getNextSibling().getFirstChild() != null
280                 && ast.getNextSibling().getFirstChild().getType() == TokenTypes.SLIST) {
281             leftCurly = ast.getNextSibling().getFirstChild();
282         }
283         else if (slistAST == null) {
284             leftCurly = ast.findFirstToken(TokenTypes.LCURLY);
285         }
286         else {
287             leftCurly = slistAST;
288         }
289         return leftCurly;
290     }
291 
292 }