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  import java.util.Optional;
25  
26  import com.puppycrawl.tools.checkstyle.StatelessCheck;
27  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
28  import com.puppycrawl.tools.checkstyle.api.DetailAST;
29  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
30  import com.puppycrawl.tools.checkstyle.utils.CodePointUtil;
31  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
32  
33  /**
34   * <p>
35   * Checks for empty blocks. This check does not validate sequential blocks.
36   * </p>
37   * <p>
38   * Sequential blocks won't be checked. Also, no violations for fallthrough:
39   * </p>
40   * <pre>
41   * switch (a) {
42   *   case 1:                          // no violation
43   *   case 2:                          // no violation
44   *   case 3: someMethod(); { }        // no violation
45   *   default: break;
46   * }
47   * </pre>
48   * <p>
49   * NOTE: This check processes LITERAL_CASE and LITERAL_DEFAULT separately.
50   * Verification empty block is done for single nearest {@code case} or {@code default}.
51   * </p>
52   * <ul>
53   * <li>
54   * Property {@code option} - Specify the policy on block contents.
55   * Type is {@code com.puppycrawl.tools.checkstyle.checks.blocks.BlockOption}.
56   * Default value is {@code statement}.
57   * </li>
58   * <li>
59   * Property {@code tokens} - tokens to check
60   * Type is {@code java.lang.String[]}.
61   * Validation type is {@code tokenSet}.
62   * Default value is:
63   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHILE">
64   * LITERAL_WHILE</a>,
65   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_TRY">
66   * LITERAL_TRY</a>,
67   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FINALLY">
68   * LITERAL_FINALLY</a>,
69   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO">
70   * LITERAL_DO</a>,
71   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_IF">
72   * LITERAL_IF</a>,
73   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_ELSE">
74   * LITERAL_ELSE</a>,
75   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FOR">
76   * LITERAL_FOR</a>,
77   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INSTANCE_INIT">
78   * INSTANCE_INIT</a>,
79   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STATIC_INIT">
80   * STATIC_INIT</a>,
81   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SWITCH">
82   * LITERAL_SWITCH</a>,
83   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SYNCHRONIZED">
84   * LITERAL_SYNCHRONIZED</a>.
85   * </li>
86   * </ul>
87   * <p>
88   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
89   * </p>
90   * <p>
91   * Violation Message Keys:
92   * </p>
93   * <ul>
94   * <li>
95   * {@code block.empty}
96   * </li>
97   * <li>
98   * {@code block.noStatement}
99   * </li>
100  * </ul>
101  *
102  * @since 3.0
103  */
104 @StatelessCheck
105 public class EmptyBlockCheck
106     extends AbstractCheck {
107 
108     /**
109      * A key is pointing to the warning message text in "messages.properties"
110      * file.
111      */
112     public static final String MSG_KEY_BLOCK_NO_STATEMENT = "block.noStatement";
113 
114     /**
115      * A key is pointing to the warning message text in "messages.properties"
116      * file.
117      */
118     public static final String MSG_KEY_BLOCK_EMPTY = "block.empty";
119 
120     /** Specify the policy on block contents. */
121     private BlockOption option = BlockOption.STATEMENT;
122 
123     /**
124      * Setter to specify the policy on block contents.
125      *
126      * @param optionStr string to decode option from
127      * @throws IllegalArgumentException if unable to decode
128      * @since 3.0
129      */
130     public void setOption(String optionStr) {
131         option = BlockOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
132     }
133 
134     @Override
135     public int[] getDefaultTokens() {
136         return new int[] {
137             TokenTypes.LITERAL_WHILE,
138             TokenTypes.LITERAL_TRY,
139             TokenTypes.LITERAL_FINALLY,
140             TokenTypes.LITERAL_DO,
141             TokenTypes.LITERAL_IF,
142             TokenTypes.LITERAL_ELSE,
143             TokenTypes.LITERAL_FOR,
144             TokenTypes.INSTANCE_INIT,
145             TokenTypes.STATIC_INIT,
146             TokenTypes.LITERAL_SWITCH,
147             TokenTypes.LITERAL_SYNCHRONIZED,
148         };
149     }
150 
151     @Override
152     public int[] getAcceptableTokens() {
153         return new int[] {
154             TokenTypes.LITERAL_WHILE,
155             TokenTypes.LITERAL_TRY,
156             TokenTypes.LITERAL_CATCH,
157             TokenTypes.LITERAL_FINALLY,
158             TokenTypes.LITERAL_DO,
159             TokenTypes.LITERAL_IF,
160             TokenTypes.LITERAL_ELSE,
161             TokenTypes.LITERAL_FOR,
162             TokenTypes.INSTANCE_INIT,
163             TokenTypes.STATIC_INIT,
164             TokenTypes.LITERAL_SWITCH,
165             TokenTypes.LITERAL_SYNCHRONIZED,
166             TokenTypes.LITERAL_CASE,
167             TokenTypes.LITERAL_DEFAULT,
168             TokenTypes.ARRAY_INIT,
169         };
170     }
171 
172     @Override
173     public int[] getRequiredTokens() {
174         return CommonUtil.EMPTY_INT_ARRAY;
175     }
176 
177     @Override
178     public void visitToken(DetailAST ast) {
179         final Optional<DetailAST> leftCurly = getLeftCurly(ast);
180         if (leftCurly.isPresent()) {
181             final DetailAST leftCurlyAST = leftCurly.orElseThrow();
182             if (option == BlockOption.STATEMENT) {
183                 final boolean emptyBlock;
184                 if (leftCurlyAST.getType() == TokenTypes.LCURLY) {
185                     final DetailAST nextSibling = leftCurlyAST.getNextSibling();
186                     emptyBlock = nextSibling.getType() != TokenTypes.CASE_GROUP
187                             && nextSibling.getType() != TokenTypes.SWITCH_RULE;
188                 }
189                 else {
190                     emptyBlock = leftCurlyAST.getChildCount() <= 1;
191                 }
192                 if (emptyBlock) {
193                     log(leftCurlyAST,
194                         MSG_KEY_BLOCK_NO_STATEMENT);
195                 }
196             }
197             else if (!hasText(leftCurlyAST)) {
198                 log(leftCurlyAST,
199                     MSG_KEY_BLOCK_EMPTY,
200                     ast.getText());
201             }
202         }
203     }
204 
205     /**
206      * Checks if SLIST token contains any text.
207      *
208      * @param slistAST a {@code DetailAST} value
209      * @return whether the SLIST token contains any text.
210      */
211     private boolean hasText(final DetailAST slistAST) {
212         final DetailAST rightCurly = slistAST.findFirstToken(TokenTypes.RCURLY);
213         final DetailAST rcurlyAST;
214 
215         if (rightCurly == null) {
216             rcurlyAST = slistAST.getParent().findFirstToken(TokenTypes.RCURLY);
217         }
218         else {
219             rcurlyAST = rightCurly;
220         }
221         final int slistLineNo = slistAST.getLineNo();
222         final int slistColNo = slistAST.getColumnNo();
223         final int rcurlyLineNo = rcurlyAST.getLineNo();
224         final int rcurlyColNo = rcurlyAST.getColumnNo();
225         boolean returnValue = false;
226         if (slistLineNo == rcurlyLineNo) {
227             // Handle braces on the same line
228             final int[] txt = Arrays.copyOfRange(getLineCodePoints(slistLineNo - 1),
229                     slistColNo + 1, rcurlyColNo);
230 
231             if (!CodePointUtil.isBlank(txt)) {
232                 returnValue = true;
233             }
234         }
235         else {
236             final int[] codePointsFirstLine = getLineCodePoints(slistLineNo - 1);
237             final int[] firstLine = Arrays.copyOfRange(codePointsFirstLine,
238                     slistColNo + 1, codePointsFirstLine.length);
239             final int[] codePointsLastLine = getLineCodePoints(rcurlyLineNo - 1);
240             final int[] lastLine = Arrays.copyOfRange(codePointsLastLine, 0, rcurlyColNo);
241             // check if all lines are also only whitespace
242             returnValue = !(CodePointUtil.isBlank(firstLine) && CodePointUtil.isBlank(lastLine))
243                     || !checkIsAllLinesAreWhitespace(slistLineNo, rcurlyLineNo);
244         }
245         return returnValue;
246     }
247 
248     /**
249      * Checks is all lines from 'lineFrom' to 'lineTo' (exclusive)
250      * contain whitespaces only.
251      *
252      * @param lineFrom
253      *            check from this line number
254      * @param lineTo
255      *            check to this line numbers
256      * @return true if lines contain only whitespaces
257      */
258     private boolean checkIsAllLinesAreWhitespace(int lineFrom, int lineTo) {
259         boolean result = true;
260         for (int i = lineFrom; i < lineTo - 1; i++) {
261             if (!CodePointUtil.isBlank(getLineCodePoints(i))) {
262                 result = false;
263                 break;
264             }
265         }
266         return result;
267     }
268 
269     /**
270      * Calculates the left curly corresponding to the block to be checked.
271      *
272      * @param ast a {@code DetailAST} value
273      * @return the left curly corresponding to the block to be checked
274      */
275     private static Optional<DetailAST> getLeftCurly(DetailAST ast) {
276         final DetailAST parent = ast.getParent();
277         final int parentType = parent.getType();
278         final Optional<DetailAST> leftCurly;
279 
280         if (parentType == TokenTypes.SWITCH_RULE) {
281             // get left curly of a case or default that is in switch rule
282             leftCurly = Optional.ofNullable(parent.findFirstToken(TokenTypes.SLIST));
283         }
284         else if (parentType == TokenTypes.CASE_GROUP) {
285             // get left curly of a case or default that is in switch statement
286             leftCurly = Optional.ofNullable(ast.getNextSibling())
287                          .map(DetailAST::getFirstChild)
288                          .filter(node -> node.getType() == TokenTypes.SLIST);
289         }
290         else if (ast.findFirstToken(TokenTypes.SLIST) != null) {
291             // we have a left curly that is part of a statement list, but not in a case or default
292             leftCurly = Optional.of(ast.findFirstToken(TokenTypes.SLIST));
293         }
294         else {
295             // get the first left curly that we can find, if it is present
296             leftCurly = Optional.ofNullable(ast.findFirstToken(TokenTypes.LCURLY));
297         }
298         return leftCurly;
299     }
300 
301 }