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