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