View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2018 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.Locale;
23  
24  import com.puppycrawl.tools.checkstyle.StatelessCheck;
25  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
26  import com.puppycrawl.tools.checkstyle.api.DetailAST;
27  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
28  import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
29  
30  /**
31   * Checks for empty blocks. This check does not validate sequential blocks.
32   * The policy to verify is specified using the {@link
33   * BlockOption} class and defaults to {@link BlockOption#STATEMENT}.
34   *
35   * <p> By default the check will check the following blocks:
36   *  {@link TokenTypes#LITERAL_WHILE LITERAL_WHILE},
37   *  {@link TokenTypes#LITERAL_TRY LITERAL_TRY},
38   *  {@link TokenTypes#LITERAL_FINALLY LITERAL_FINALLY},
39   *  {@link TokenTypes#LITERAL_DO LITERAL_DO},
40   *  {@link TokenTypes#LITERAL_IF LITERAL_IF},
41   *  {@link TokenTypes#LITERAL_ELSE LITERAL_ELSE},
42   *  {@link TokenTypes#LITERAL_FOR LITERAL_FOR},
43   *  {@link TokenTypes#STATIC_INIT STATIC_INIT},
44   *  {@link TokenTypes#LITERAL_SWITCH LITERAL_SWITCH}.
45   *  {@link TokenTypes#LITERAL_SYNCHRONIZED LITERAL_SYNCHRONIZED}.
46   * </p>
47   *
48   * <p> An example of how to configure the check is:
49   * </p>
50   * <pre>
51   * &lt;module name="EmptyBlock"/&gt;
52   * </pre>
53   *
54   * <p> An example of how to configure the check for the {@link
55   * BlockOption#TEXT} policy and only try blocks is:
56   * </p>
57   *
58   * <pre>
59   * &lt;module name="EmptyBlock"&gt;
60   *    &lt;property name="tokens" value="LITERAL_TRY"/&gt;
61   *    &lt;property name="option" value="text"/&gt;
62   * &lt;/module&gt;
63   * </pre>
64   *
65   * @author Lars K├╝hne
66   */
67  @StatelessCheck
68  public class EmptyBlockCheck
69      extends AbstractCheck {
70  
71      /**
72       * A key is pointing to the warning message text in "messages.properties"
73       * file.
74       */
75      public static final String MSG_KEY_BLOCK_NO_STATEMENT = "block.noStatement";
76  
77      /**
78       * A key is pointing to the warning message text in "messages.properties"
79       * file.
80       */
81      public static final String MSG_KEY_BLOCK_EMPTY = "block.empty";
82  
83      /** The policy to enforce. */
84      private BlockOption option = BlockOption.STATEMENT;
85  
86      /**
87       * Set the option to enforce.
88       * @param optionStr string to decode option from
89       * @throws IllegalArgumentException if unable to decode
90       */
91      public void setOption(String optionStr) {
92          try {
93              option = BlockOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
94          }
95          catch (IllegalArgumentException iae) {
96              throw new IllegalArgumentException("unable to parse " + optionStr, iae);
97          }
98      }
99  
100     @Override
101     public int[] getDefaultTokens() {
102         return new int[] {
103             TokenTypes.LITERAL_WHILE,
104             TokenTypes.LITERAL_TRY,
105             TokenTypes.LITERAL_FINALLY,
106             TokenTypes.LITERAL_DO,
107             TokenTypes.LITERAL_IF,
108             TokenTypes.LITERAL_ELSE,
109             TokenTypes.LITERAL_FOR,
110             TokenTypes.INSTANCE_INIT,
111             TokenTypes.STATIC_INIT,
112             TokenTypes.LITERAL_SWITCH,
113             TokenTypes.LITERAL_SYNCHRONIZED,
114         };
115     }
116 
117     @Override
118     public int[] getAcceptableTokens() {
119         return new int[] {
120             TokenTypes.LITERAL_WHILE,
121             TokenTypes.LITERAL_TRY,
122             TokenTypes.LITERAL_CATCH,
123             TokenTypes.LITERAL_FINALLY,
124             TokenTypes.LITERAL_DO,
125             TokenTypes.LITERAL_IF,
126             TokenTypes.LITERAL_ELSE,
127             TokenTypes.LITERAL_FOR,
128             TokenTypes.INSTANCE_INIT,
129             TokenTypes.STATIC_INIT,
130             TokenTypes.LITERAL_SWITCH,
131             TokenTypes.LITERAL_SYNCHRONIZED,
132             TokenTypes.LITERAL_CASE,
133             TokenTypes.LITERAL_DEFAULT,
134             TokenTypes.ARRAY_INIT,
135         };
136     }
137 
138     @Override
139     public int[] getRequiredTokens() {
140         return CommonUtils.EMPTY_INT_ARRAY;
141     }
142 
143     @Override
144     public void visitToken(DetailAST ast) {
145         final DetailAST leftCurly = findLeftCurly(ast);
146         if (leftCurly != null) {
147             if (option == BlockOption.STATEMENT) {
148                 final boolean emptyBlock;
149                 if (leftCurly.getType() == TokenTypes.LCURLY) {
150                     emptyBlock = leftCurly.getNextSibling().getType() != TokenTypes.CASE_GROUP;
151                 }
152                 else {
153                     emptyBlock = leftCurly.getChildCount() <= 1;
154                 }
155                 if (emptyBlock) {
156                     log(leftCurly.getLineNo(),
157                         leftCurly.getColumnNo(),
158                         MSG_KEY_BLOCK_NO_STATEMENT,
159                         ast.getText());
160                 }
161             }
162             else if (!hasText(leftCurly)) {
163                 log(leftCurly.getLineNo(),
164                     leftCurly.getColumnNo(),
165                     MSG_KEY_BLOCK_EMPTY,
166                     ast.getText());
167             }
168         }
169     }
170 
171     /**
172      * Checks if SLIST token contains any text.
173      * @param slistAST a {@code DetailAST} value
174      * @return whether the SLIST token contains any text.
175      */
176     private boolean hasText(final DetailAST slistAST) {
177         final DetailAST rightCurly = slistAST.findFirstToken(TokenTypes.RCURLY);
178         final DetailAST rcurlyAST;
179 
180         if (rightCurly == null) {
181             rcurlyAST = slistAST.getParent().findFirstToken(TokenTypes.RCURLY);
182         }
183         else {
184             rcurlyAST = rightCurly;
185         }
186         final int slistLineNo = slistAST.getLineNo();
187         final int slistColNo = slistAST.getColumnNo();
188         final int rcurlyLineNo = rcurlyAST.getLineNo();
189         final int rcurlyColNo = rcurlyAST.getColumnNo();
190         final String[] lines = getLines();
191         boolean returnValue = false;
192         if (slistLineNo == rcurlyLineNo) {
193             // Handle braces on the same line
194             final String txt = lines[slistLineNo - 1]
195                     .substring(slistColNo + 1, rcurlyColNo);
196             if (!CommonUtils.isBlank(txt)) {
197                 returnValue = true;
198             }
199         }
200         else {
201             final String firstLine = lines[slistLineNo - 1].substring(slistColNo + 1);
202             final String lastLine = lines[rcurlyLineNo - 1].substring(0, rcurlyColNo);
203             // check if all lines are also only whitespace
204             returnValue = !(CommonUtils.isBlank(firstLine) && CommonUtils.isBlank(lastLine))
205                     || !checkIsAllLinesAreWhitespace(lines, slistLineNo, rcurlyLineNo);
206         }
207         return returnValue;
208     }
209 
210     /**
211      * Checks is all lines in array contain whitespaces only.
212      *
213      * @param lines
214      *            array of lines
215      * @param lineFrom
216      *            check from this line number
217      * @param lineTo
218      *            check to this line numbers
219      * @return true if lines contain only whitespaces
220      */
221     private static boolean checkIsAllLinesAreWhitespace(String[] lines, int lineFrom, int lineTo) {
222         boolean result = true;
223         for (int i = lineFrom; i < lineTo - 1; i++) {
224             if (!CommonUtils.isBlank(lines[i])) {
225                 result = false;
226                 break;
227             }
228         }
229         return result;
230     }
231 
232     /**
233      * Calculates the left curly corresponding to the block to be checked.
234      *
235      * @param ast a {@code DetailAST} value
236      * @return the left curly corresponding to the block to be checked
237      */
238     private static DetailAST findLeftCurly(DetailAST ast) {
239         final DetailAST leftCurly;
240         final DetailAST slistAST = ast.findFirstToken(TokenTypes.SLIST);
241         if ((ast.getType() == TokenTypes.LITERAL_CASE
242                 || ast.getType() == TokenTypes.LITERAL_DEFAULT)
243                 && ast.getNextSibling() != null
244                 && ast.getNextSibling().getFirstChild() != null
245                 && ast.getNextSibling().getFirstChild().getType() == TokenTypes.SLIST) {
246             leftCurly = ast.getNextSibling().getFirstChild();
247         }
248         else if (slistAST == null) {
249             leftCurly = ast.findFirstToken(TokenTypes.LCURLY);
250         }
251         else {
252             leftCurly = slistAST;
253         }
254         return leftCurly;
255     }
256 
257 }