View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 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       * A key is pointing to the warning message text in "messages.properties"
72       * file.
73       */
74      public static final String MSG_KEY_BLOCK_NO_STATEMENT = "block.noStatement";
75  
76      /**
77       * A key is pointing to the warning message text in "messages.properties"
78       * file.
79       */
80      public static final String MSG_KEY_BLOCK_EMPTY = "block.empty";
81  
82      /** The policy to enforce. */
83      private BlockOption option = BlockOption.STATEMENT;
84  
85      /**
86       * Set the option to enforce.
87       * @param optionStr string to decode option from
88       * @throws IllegalArgumentException if unable to decode
89       */
90      public void setOption(String optionStr) {
91          try {
92              option = BlockOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
93          }
94          catch (IllegalArgumentException iae) {
95              throw new IllegalArgumentException("unable to parse " + optionStr, iae);
96          }
97      }
98  
99      @Override
100     public int[] getDefaultTokens() {
101         return new int[] {
102             TokenTypes.LITERAL_WHILE,
103             TokenTypes.LITERAL_TRY,
104             TokenTypes.LITERAL_FINALLY,
105             TokenTypes.LITERAL_DO,
106             TokenTypes.LITERAL_IF,
107             TokenTypes.LITERAL_ELSE,
108             TokenTypes.LITERAL_FOR,
109             TokenTypes.INSTANCE_INIT,
110             TokenTypes.STATIC_INIT,
111             TokenTypes.LITERAL_SWITCH,
112             TokenTypes.LITERAL_SYNCHRONIZED,
113         };
114     }
115 
116     @Override
117     public int[] getAcceptableTokens() {
118         return new int[] {
119             TokenTypes.LITERAL_WHILE,
120             TokenTypes.LITERAL_TRY,
121             TokenTypes.LITERAL_CATCH,
122             TokenTypes.LITERAL_FINALLY,
123             TokenTypes.LITERAL_DO,
124             TokenTypes.LITERAL_IF,
125             TokenTypes.LITERAL_ELSE,
126             TokenTypes.LITERAL_FOR,
127             TokenTypes.INSTANCE_INIT,
128             TokenTypes.STATIC_INIT,
129             TokenTypes.LITERAL_SWITCH,
130             TokenTypes.LITERAL_SYNCHRONIZED,
131             TokenTypes.LITERAL_CASE,
132             TokenTypes.LITERAL_DEFAULT,
133             TokenTypes.ARRAY_INIT,
134         };
135     }
136 
137     @Override
138     public int[] getRequiredTokens() {
139         return CommonUtils.EMPTY_INT_ARRAY;
140     }
141 
142     @Override
143     public void visitToken(DetailAST ast) {
144         final DetailAST leftCurly = findLeftCurly(ast);
145         if (leftCurly != null) {
146             if (option == BlockOption.STATEMENT) {
147                 final boolean emptyBlock;
148                 if (leftCurly.getType() == TokenTypes.LCURLY) {
149                     emptyBlock = leftCurly.getNextSibling().getType() != TokenTypes.CASE_GROUP;
150                 }
151                 else {
152                     emptyBlock = leftCurly.getChildCount() <= 1;
153                 }
154                 if (emptyBlock) {
155                     log(leftCurly.getLineNo(),
156                         leftCurly.getColumnNo(),
157                         MSG_KEY_BLOCK_NO_STATEMENT,
158                         ast.getText());
159                 }
160             }
161             else if (!hasText(leftCurly)) {
162                 log(leftCurly.getLineNo(),
163                     leftCurly.getColumnNo(),
164                     MSG_KEY_BLOCK_EMPTY,
165                     ast.getText());
166             }
167         }
168     }
169 
170     /**
171      * Checks if SLIST token contains any text.
172      * @param slistAST a {@code DetailAST} value
173      * @return whether the SLIST token contains any text.
174      */
175     private boolean hasText(final DetailAST slistAST) {
176         final DetailAST rightCurly = slistAST.findFirstToken(TokenTypes.RCURLY);
177         final DetailAST rcurlyAST;
178 
179         if (rightCurly == null) {
180             rcurlyAST = slistAST.getParent().findFirstToken(TokenTypes.RCURLY);
181         }
182         else {
183             rcurlyAST = rightCurly;
184         }
185         final int slistLineNo = slistAST.getLineNo();
186         final int slistColNo = slistAST.getColumnNo();
187         final int rcurlyLineNo = rcurlyAST.getLineNo();
188         final int rcurlyColNo = rcurlyAST.getColumnNo();
189         final String[] lines = getLines();
190         boolean returnValue = false;
191         if (slistLineNo == rcurlyLineNo) {
192             // Handle braces on the same line
193             final String txt = lines[slistLineNo - 1]
194                     .substring(slistColNo + 1, rcurlyColNo);
195             if (!CommonUtils.isBlank(txt)) {
196                 returnValue = true;
197             }
198         }
199         else {
200             final String firstLine = lines[slistLineNo - 1].substring(slistColNo + 1);
201             final String lastLine = lines[rcurlyLineNo - 1].substring(0, rcurlyColNo);
202             // check if all lines are also only whitespace
203             returnValue = !(CommonUtils.isBlank(firstLine) && CommonUtils.isBlank(lastLine))
204                     || !checkIsAllLinesAreWhitespace(lines, slistLineNo, rcurlyLineNo);
205         }
206         return returnValue;
207     }
208 
209     /**
210      * Checks is all lines in array contain whitespaces only.
211      *
212      * @param lines
213      *            array of lines
214      * @param lineFrom
215      *            check from this line number
216      * @param lineTo
217      *            check to this line numbers
218      * @return true if lines contain only whitespaces
219      */
220     private static boolean checkIsAllLinesAreWhitespace(String[] lines, int lineFrom, int lineTo) {
221         boolean result = true;
222         for (int i = lineFrom; i < lineTo - 1; i++) {
223             if (!CommonUtils.isBlank(lines[i])) {
224                 result = false;
225                 break;
226             }
227         }
228         return result;
229     }
230 
231     /**
232      * Calculates the left curly corresponding to the block to be checked.
233      *
234      * @param ast a {@code DetailAST} value
235      * @return the left curly corresponding to the block to be checked
236      */
237     private static DetailAST findLeftCurly(DetailAST ast) {
238         final DetailAST leftCurly;
239         final DetailAST slistAST = ast.findFirstToken(TokenTypes.SLIST);
240         if ((ast.getType() == TokenTypes.LITERAL_CASE
241                 || ast.getType() == TokenTypes.LITERAL_DEFAULT)
242                 && ast.getNextSibling() != null
243                 && ast.getNextSibling().getFirstChild() != null
244                 && ast.getNextSibling().getFirstChild().getType() == TokenTypes.SLIST) {
245             leftCurly = ast.getNextSibling().getFirstChild();
246         }
247         else if (slistAST == null) {
248             leftCurly = ast.findFirstToken(TokenTypes.LCURLY);
249         }
250         else {
251             leftCurly = slistAST;
252         }
253         return leftCurly;
254     }
255 }