001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2022 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.blocks;
021
022import java.util.Arrays;
023import java.util.Locale;
024
025import com.puppycrawl.tools.checkstyle.StatelessCheck;
026import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
027import com.puppycrawl.tools.checkstyle.api.DetailAST;
028import com.puppycrawl.tools.checkstyle.api.TokenTypes;
029import com.puppycrawl.tools.checkstyle.utils.CodePointUtil;
030import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
031
032/**
033 * <p>
034 * Checks for empty blocks. This check does not validate sequential blocks.
035 * </p>
036 * <p>
037 * Sequential blocks won't be checked. Also, no violations for fallthrough:
038 * </p>
039 * <pre>
040 * switch (a) {
041 *   case 1:                          // no violation
042 *   case 2:                          // no violation
043 *   case 3: someMethod(); { }        // no violation
044 *   default: break;
045 * }
046 * </pre>
047 * <p>
048 * NOTE: This check processes LITERAL_CASE and LITERAL_DEFAULT separately.
049 * Verification empty block is done for single most nearest {@code case} or {@code default}.
050 * </p>
051 * <ul>
052 * <li>
053 * Property {@code option} - specify the policy on block contents.
054 * Type is {@code com.puppycrawl.tools.checkstyle.checks.blocks.BlockOption}.
055 * Default value is {@code statement}.
056 * </li>
057 * <li>
058 * Property {@code tokens} - tokens to check
059 * Type is {@code java.lang.String[]}.
060 * Validation type is {@code tokenSet}.
061 * Default value is:
062 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHILE">
063 * LITERAL_WHILE</a>,
064 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_TRY">
065 * LITERAL_TRY</a>,
066 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FINALLY">
067 * LITERAL_FINALLY</a>,
068 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO">
069 * LITERAL_DO</a>,
070 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_IF">
071 * LITERAL_IF</a>,
072 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_ELSE">
073 * LITERAL_ELSE</a>,
074 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FOR">
075 * LITERAL_FOR</a>,
076 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INSTANCE_INIT">
077 * INSTANCE_INIT</a>,
078 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STATIC_INIT">
079 * STATIC_INIT</a>,
080 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SWITCH">
081 * LITERAL_SWITCH</a>,
082 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SYNCHRONIZED">
083 * LITERAL_SYNCHRONIZED</a>.
084 * </li>
085 * </ul>
086 * <p>
087 * To configure the check:
088 * </p>
089 * <pre>
090 * &lt;module name="EmptyBlock"/&gt;
091 * </pre>
092 * <p>
093 * Example:
094 * </p>
095 * <pre>
096 * public class Test {
097 *   private void emptyLoop() {
098 *     for (int i = 0; i &lt; 10; i++) { // violation
099 *     }
100 *
101 *     try { // violation
102 *
103 *     } catch (Exception e) {
104 *       // ignored
105 *     }
106 *   }
107 * }
108 * </pre>
109 * <p>
110 * To configure the check for the {@code text} policy and only {@code try} blocks:
111 * </p>
112 * <pre>
113 * &lt;module name=&quot;EmptyBlock&quot;&gt;
114 *   &lt;property name=&quot;option&quot; value=&quot;text&quot;/&gt;
115 *   &lt;property name=&quot;tokens&quot; value=&quot;LITERAL_TRY&quot;/&gt;
116 * &lt;/module&gt;
117 * </pre>
118 * <p> Example: </p>
119 * <pre>
120 * public class Test {
121 *   private void emptyLoop() {
122 *     for (int i = 0; i &lt; 10; i++) {
123 *       // ignored
124 *     }
125 *
126 *     // violation on next line
127 *     try {
128 *
129 *     } catch (Exception e) {
130 *       // ignored
131 *     }
132 *   }
133 * }
134 * </pre>
135 * <p>
136 * To configure the check for default in switch block:
137 * </p>
138 * <pre>
139 * &lt;module name=&quot;EmptyBlock&quot;&gt;
140 *   &lt;property name=&quot;tokens&quot; value=&quot;LITERAL_DEFAULT&quot;/&gt;
141 * &lt;/module&gt;
142 * </pre>
143 * <p> Example: </p>
144 * <pre>
145 * public class Test {
146 *   private void test(int a) {
147 *     switch (a) {
148 *       case 1: someMethod();
149 *       default: // OK, as there is no block
150 *     }
151 *     switch (a) {
152 *       case 1: someMethod();
153 *       default: {} // violation
154 *     }
155 *   }
156 * }
157 * </pre>
158 * <p>
159 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
160 * </p>
161 * <p>
162 * Violation Message Keys:
163 * </p>
164 * <ul>
165 * <li>
166 * {@code block.empty}
167 * </li>
168 * <li>
169 * {@code block.noStatement}
170 * </li>
171 * </ul>
172 *
173 * @since 3.0
174 */
175@StatelessCheck
176public class EmptyBlockCheck
177    extends AbstractCheck {
178
179    /**
180     * A key is pointing to the warning message text in "messages.properties"
181     * file.
182     */
183    public static final String MSG_KEY_BLOCK_NO_STATEMENT = "block.noStatement";
184
185    /**
186     * A key is pointing to the warning message text in "messages.properties"
187     * file.
188     */
189    public static final String MSG_KEY_BLOCK_EMPTY = "block.empty";
190
191    /** Specify the policy on block contents. */
192    private BlockOption option = BlockOption.STATEMENT;
193
194    /**
195     * Setter to specify the policy on block contents.
196     *
197     * @param optionStr string to decode option from
198     * @throws IllegalArgumentException if unable to decode
199     */
200    public void setOption(String optionStr) {
201        option = BlockOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
202    }
203
204    @Override
205    public int[] getDefaultTokens() {
206        return new int[] {
207            TokenTypes.LITERAL_WHILE,
208            TokenTypes.LITERAL_TRY,
209            TokenTypes.LITERAL_FINALLY,
210            TokenTypes.LITERAL_DO,
211            TokenTypes.LITERAL_IF,
212            TokenTypes.LITERAL_ELSE,
213            TokenTypes.LITERAL_FOR,
214            TokenTypes.INSTANCE_INIT,
215            TokenTypes.STATIC_INIT,
216            TokenTypes.LITERAL_SWITCH,
217            TokenTypes.LITERAL_SYNCHRONIZED,
218        };
219    }
220
221    @Override
222    public int[] getAcceptableTokens() {
223        return new int[] {
224            TokenTypes.LITERAL_WHILE,
225            TokenTypes.LITERAL_TRY,
226            TokenTypes.LITERAL_CATCH,
227            TokenTypes.LITERAL_FINALLY,
228            TokenTypes.LITERAL_DO,
229            TokenTypes.LITERAL_IF,
230            TokenTypes.LITERAL_ELSE,
231            TokenTypes.LITERAL_FOR,
232            TokenTypes.INSTANCE_INIT,
233            TokenTypes.STATIC_INIT,
234            TokenTypes.LITERAL_SWITCH,
235            TokenTypes.LITERAL_SYNCHRONIZED,
236            TokenTypes.LITERAL_CASE,
237            TokenTypes.LITERAL_DEFAULT,
238            TokenTypes.ARRAY_INIT,
239        };
240    }
241
242    @Override
243    public int[] getRequiredTokens() {
244        return CommonUtil.EMPTY_INT_ARRAY;
245    }
246
247    @Override
248    public void visitToken(DetailAST ast) {
249        final DetailAST leftCurly = findLeftCurly(ast);
250        if (leftCurly != null) {
251            if (option == BlockOption.STATEMENT) {
252                final boolean emptyBlock;
253                if (leftCurly.getType() == TokenTypes.LCURLY) {
254                    final DetailAST nextSibling = leftCurly.getNextSibling();
255                    emptyBlock = nextSibling.getType() != TokenTypes.CASE_GROUP
256                            && nextSibling.getType() != TokenTypes.SWITCH_RULE;
257                }
258                else {
259                    emptyBlock = leftCurly.getChildCount() <= 1;
260                }
261                if (emptyBlock) {
262                    log(leftCurly,
263                        MSG_KEY_BLOCK_NO_STATEMENT,
264                        ast.getText());
265                }
266            }
267            else if (!hasText(leftCurly)) {
268                log(leftCurly,
269                    MSG_KEY_BLOCK_EMPTY,
270                    ast.getText());
271            }
272        }
273    }
274
275    /**
276     * Checks if SLIST token contains any text.
277     *
278     * @param slistAST a {@code DetailAST} value
279     * @return whether the SLIST token contains any text.
280     */
281    private boolean hasText(final DetailAST slistAST) {
282        final DetailAST rightCurly = slistAST.findFirstToken(TokenTypes.RCURLY);
283        final DetailAST rcurlyAST;
284
285        if (rightCurly == null) {
286            rcurlyAST = slistAST.getParent().findFirstToken(TokenTypes.RCURLY);
287        }
288        else {
289            rcurlyAST = rightCurly;
290        }
291        final int slistLineNo = slistAST.getLineNo();
292        final int slistColNo = slistAST.getColumnNo();
293        final int rcurlyLineNo = rcurlyAST.getLineNo();
294        final int rcurlyColNo = rcurlyAST.getColumnNo();
295        boolean returnValue = false;
296        if (slistLineNo == rcurlyLineNo) {
297            // Handle braces on the same line
298            final int[] txt = Arrays.copyOfRange(getLineCodePoints(slistLineNo - 1),
299                    slistColNo + 1, rcurlyColNo);
300
301            if (!CodePointUtil.isBlank(txt)) {
302                returnValue = true;
303            }
304        }
305        else {
306            final int[] codePointsFirstLine = getLineCodePoints(slistLineNo - 1);
307            final int[] firstLine = Arrays.copyOfRange(codePointsFirstLine,
308                    slistColNo + 1, codePointsFirstLine.length);
309            final int[] codePointsLastLine = getLineCodePoints(rcurlyLineNo - 1);
310            final int[] lastLine = Arrays.copyOfRange(codePointsLastLine, 0, rcurlyColNo);
311            // check if all lines are also only whitespace
312            returnValue = !(CodePointUtil.isBlank(firstLine) && CodePointUtil.isBlank(lastLine))
313                    || !checkIsAllLinesAreWhitespace(slistLineNo, rcurlyLineNo);
314        }
315        return returnValue;
316    }
317
318    /**
319     * Checks is all lines from 'lineFrom' to 'lineTo' (exclusive)
320     * contain whitespaces only.
321     *
322     * @param lineFrom
323     *            check from this line number
324     * @param lineTo
325     *            check to this line numbers
326     * @return true if lines contain only whitespaces
327     */
328    private boolean checkIsAllLinesAreWhitespace(int lineFrom, int lineTo) {
329        boolean result = true;
330        for (int i = lineFrom; i < lineTo - 1; i++) {
331            if (!CodePointUtil.isBlank(getLineCodePoints(i))) {
332                result = false;
333                break;
334            }
335        }
336        return result;
337    }
338
339    /**
340     * Calculates the left curly corresponding to the block to be checked.
341     *
342     * @param ast a {@code DetailAST} value
343     * @return the left curly corresponding to the block to be checked
344     */
345    private static DetailAST findLeftCurly(DetailAST ast) {
346        final DetailAST leftCurly;
347        final DetailAST slistAST = ast.findFirstToken(TokenTypes.SLIST);
348        if ((ast.getType() == TokenTypes.LITERAL_CASE
349                || ast.getType() == TokenTypes.LITERAL_DEFAULT)
350                && ast.getNextSibling() != null
351                && ast.getNextSibling().getFirstChild() != null
352                && ast.getNextSibling().getFirstChild().getType() == TokenTypes.SLIST) {
353            leftCurly = ast.getNextSibling().getFirstChild();
354        }
355        else if (slistAST == null) {
356            leftCurly = ast.findFirstToken(TokenTypes.LCURLY);
357        }
358        else {
359            leftCurly = slistAST;
360        }
361        return leftCurly;
362    }
363
364}