001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2019 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 com.puppycrawl.tools.checkstyle.StatelessCheck;
023import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
024import com.puppycrawl.tools.checkstyle.api.DetailAST;
025import com.puppycrawl.tools.checkstyle.api.TokenTypes;
026import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
027
028/**
029 * <p>
030 * Checks for braces around code blocks.
031 * </p>
032 * <p> By default the check will check the following blocks:
033 *  {@link TokenTypes#LITERAL_DO LITERAL_DO},
034 *  {@link TokenTypes#LITERAL_ELSE LITERAL_ELSE},
035 *  {@link TokenTypes#LITERAL_FOR LITERAL_FOR},
036 *  {@link TokenTypes#LITERAL_IF LITERAL_IF},
037 *  {@link TokenTypes#LITERAL_WHILE LITERAL_WHILE}.
038 * </p>
039 * <p>
040 * An example of how to configure the check is:
041 * </p>
042 * <pre>
043 * &lt;module name="NeedBraces"/&gt;
044 * </pre>
045 * <p> An example of how to configure the check for {@code if} and
046 * {@code else} blocks is:
047 * </p>
048 * <pre>
049 * &lt;module name="NeedBraces"&gt;
050 *     &lt;property name="tokens" value="LITERAL_IF, LITERAL_ELSE"/&gt;
051 * &lt;/module&gt;
052 * </pre>
053 * Check has the following options:
054 * <p><b>allowSingleLineStatement</b> which allows single-line statements without braces, e.g.:</p>
055 * <p>
056 * {@code
057 * if (obj.isValid()) return true;
058 * }
059 * </p>
060 * <p>
061 * {@code
062 * while (obj.isValid()) return true;
063 * }
064 * </p>
065 * <p>
066 * {@code
067 * do this.notify(); while (o != null);
068 * }
069 * </p>
070 * <p>
071 * {@code
072 * for (int i = 0; ; ) this.notify();
073 * }
074 * </p>
075 * <p><b>allowEmptyLoopBody</b> which allows loops with empty bodies, e.g.:</p>
076 * <p>
077 * {@code
078 * while (value.incrementValue() < 5);
079 * }
080 * </p>
081 * <p>
082 * {@code
083 * for(int i = 0; i < 10; value.incrementValue());
084 * }
085 * </p>
086 * <p>Default value for allowEmptyLoopBody option is <b>false</b>.</p>
087 * <p>
088 * To configure the Check to allow {@code case, default} single-line statements
089 * without braces:
090 * </p>
091 *
092 * <pre>
093 * &lt;module name=&quot;NeedBraces&quot;&gt;
094 *     &lt;property name=&quot;tokens&quot; value=&quot;LITERAL_CASE, LITERAL_DEFAULT&quot;/&gt;
095 *     &lt;property name=&quot;allowSingleLineStatement&quot; value=&quot;true&quot;/&gt;
096 * &lt;/module&gt;
097 * </pre>
098 *
099 * <p>
100 * Such statements would be allowed:
101 * </p>
102 *
103 * <pre>
104 * {@code
105 * switch (num) {
106 *     case 1: counter++; break; // OK
107 *     case 6: counter += 10; break; // OK
108 *     default: counter = 100; break; // OK
109 * }
110 * }
111 * </pre>
112 * <p>
113 * To configure the Check to allow {@code while, for} loops with empty bodies:
114 * </p>
115 *
116 * <pre>
117 * &lt;module name=&quot;NeedBraces&quot;&gt;
118 *     &lt;property name=&quot;allowEmptyLoopBody&quot; value=&quot;true&quot;/&gt;
119 * &lt;/module&gt;
120 * </pre>
121 *
122 * <p>
123 * Such statements would be allowed:
124 * </p>
125 *
126 * <pre>
127 * {@code
128 * while (value.incrementValue() &lt; 5); // OK
129 * for(int i = 0; i &lt; 10; value.incrementValue()); // OK
130 * }
131 * </pre>
132 *
133 */
134@StatelessCheck
135public class NeedBracesCheck extends AbstractCheck {
136
137    /**
138     * A key is pointing to the warning message text in "messages.properties"
139     * file.
140     */
141    public static final String MSG_KEY_NEED_BRACES = "needBraces";
142
143    /**
144     * Check's option for skipping single-line statements.
145     */
146    private boolean allowSingleLineStatement;
147
148    /**
149     * Check's option for allowing loops with empty body.
150     */
151    private boolean allowEmptyLoopBody;
152
153    /**
154     * Setter.
155     * @param allowSingleLineStatement Check's option for skipping single-line statements
156     */
157    public void setAllowSingleLineStatement(boolean allowSingleLineStatement) {
158        this.allowSingleLineStatement = allowSingleLineStatement;
159    }
160
161    /**
162     * Sets whether to allow empty loop body.
163     * @param allowEmptyLoopBody Check's option for allowing loops with empty body.
164     */
165    public void setAllowEmptyLoopBody(boolean allowEmptyLoopBody) {
166        this.allowEmptyLoopBody = allowEmptyLoopBody;
167    }
168
169    @Override
170    public int[] getDefaultTokens() {
171        return new int[] {
172            TokenTypes.LITERAL_DO,
173            TokenTypes.LITERAL_ELSE,
174            TokenTypes.LITERAL_FOR,
175            TokenTypes.LITERAL_IF,
176            TokenTypes.LITERAL_WHILE,
177        };
178    }
179
180    @Override
181    public int[] getAcceptableTokens() {
182        return new int[] {
183            TokenTypes.LITERAL_DO,
184            TokenTypes.LITERAL_ELSE,
185            TokenTypes.LITERAL_FOR,
186            TokenTypes.LITERAL_IF,
187            TokenTypes.LITERAL_WHILE,
188            TokenTypes.LITERAL_CASE,
189            TokenTypes.LITERAL_DEFAULT,
190            TokenTypes.LAMBDA,
191        };
192    }
193
194    @Override
195    public int[] getRequiredTokens() {
196        return CommonUtil.EMPTY_INT_ARRAY;
197    }
198
199    @Override
200    public void visitToken(DetailAST ast) {
201        final DetailAST slistAST = ast.findFirstToken(TokenTypes.SLIST);
202        boolean isElseIf = false;
203        if (ast.getType() == TokenTypes.LITERAL_ELSE
204            && ast.findFirstToken(TokenTypes.LITERAL_IF) != null) {
205            isElseIf = true;
206        }
207        final boolean isInAnnotationField = isInAnnotationField(ast);
208        final boolean skipStatement = isSkipStatement(ast);
209        final boolean skipEmptyLoopBody = allowEmptyLoopBody && isEmptyLoopBody(ast);
210
211        if (slistAST == null && !isElseIf && !isInAnnotationField
212                && !skipStatement && !skipEmptyLoopBody) {
213            log(ast.getLineNo(), MSG_KEY_NEED_BRACES, ast.getText());
214        }
215    }
216
217    /**
218     * Checks if ast is in an annotation field.
219     * @param ast ast to test.
220     * @return true if current ast is part of an annotation field.
221     */
222    private static boolean isInAnnotationField(DetailAST ast) {
223        boolean isDefaultInAnnotation = false;
224        if (ast.getParent().getType() == TokenTypes.ANNOTATION_FIELD_DEF) {
225            isDefaultInAnnotation = true;
226        }
227        return isDefaultInAnnotation;
228    }
229
230    /**
231     * Checks if current statement can be skipped by "need braces" warning.
232     * @param statement if, for, while, do-while, lambda, else, case, default statements.
233     * @return true if current statement can be skipped by Check.
234     */
235    private boolean isSkipStatement(DetailAST statement) {
236        return allowSingleLineStatement && isSingleLineStatement(statement);
237    }
238
239    /**
240     * Checks if current loop statement does not have body, e.g.:
241     * <p>
242     * {@code
243     *   while (value.incrementValue() < 5);
244     *   ...
245     *   for(int i = 0; i < 10; value.incrementValue());
246     * }
247     * </p>
248     * @param ast ast token.
249     * @return true if current loop statement does not have body.
250     */
251    private static boolean isEmptyLoopBody(DetailAST ast) {
252        boolean noBodyLoop = false;
253
254        if (ast.getType() == TokenTypes.LITERAL_FOR
255                || ast.getType() == TokenTypes.LITERAL_WHILE) {
256            DetailAST currentToken = ast.getFirstChild();
257            while (currentToken.getNextSibling() != null) {
258                currentToken = currentToken.getNextSibling();
259            }
260            noBodyLoop = currentToken.getType() == TokenTypes.EMPTY_STAT;
261        }
262        return noBodyLoop;
263    }
264
265    /**
266     * Checks if current statement is single-line statement, e.g.:
267     * <p>
268     * {@code
269     * if (obj.isValid()) return true;
270     * }
271     * </p>
272     * <p>
273     * {@code
274     * while (obj.isValid()) return true;
275     * }
276     * </p>
277     * @param statement if, for, while, do-while, lambda, else, case, default statements.
278     * @return true if current statement is single-line statement.
279     */
280    private static boolean isSingleLineStatement(DetailAST statement) {
281        final boolean result;
282
283        switch (statement.getType()) {
284            case TokenTypes.LITERAL_IF:
285                result = isSingleLineIf(statement);
286                break;
287            case TokenTypes.LITERAL_FOR:
288                result = isSingleLineFor(statement);
289                break;
290            case TokenTypes.LITERAL_DO:
291                result = isSingleLineDoWhile(statement);
292                break;
293            case TokenTypes.LITERAL_WHILE:
294                result = isSingleLineWhile(statement);
295                break;
296            case TokenTypes.LAMBDA:
297                result = isSingleLineLambda(statement);
298                break;
299            case TokenTypes.LITERAL_CASE:
300                result = isSingleLineCase(statement);
301                break;
302            case TokenTypes.LITERAL_DEFAULT:
303                result = isSingleLineDefault(statement);
304                break;
305            default:
306                result = isSingleLineElse(statement);
307                break;
308        }
309
310        return result;
311    }
312
313    /**
314     * Checks if current while statement is single-line statement, e.g.:
315     * <p>
316     * {@code
317     * while (obj.isValid()) return true;
318     * }
319     * </p>
320     * @param literalWhile {@link TokenTypes#LITERAL_WHILE while statement}.
321     * @return true if current while statement is single-line statement.
322     */
323    private static boolean isSingleLineWhile(DetailAST literalWhile) {
324        boolean result = false;
325        if (literalWhile.getParent().getType() == TokenTypes.SLIST) {
326            final DetailAST block = literalWhile.getLastChild().getPreviousSibling();
327            result = literalWhile.getLineNo() == block.getLineNo();
328        }
329        return result;
330    }
331
332    /**
333     * Checks if current do-while statement is single-line statement, e.g.:
334     * <p>
335     * {@code
336     * do this.notify(); while (o != null);
337     * }
338     * </p>
339     * @param literalDo {@link TokenTypes#LITERAL_DO do-while statement}.
340     * @return true if current do-while statement is single-line statement.
341     */
342    private static boolean isSingleLineDoWhile(DetailAST literalDo) {
343        boolean result = false;
344        if (literalDo.getParent().getType() == TokenTypes.SLIST) {
345            final DetailAST block = literalDo.getFirstChild();
346            result = block.getLineNo() == literalDo.getLineNo();
347        }
348        return result;
349    }
350
351    /**
352     * Checks if current for statement is single-line statement, e.g.:
353     * <p>
354     * {@code
355     * for (int i = 0; ; ) this.notify();
356     * }
357     * </p>
358     * @param literalFor {@link TokenTypes#LITERAL_FOR for statement}.
359     * @return true if current for statement is single-line statement.
360     */
361    private static boolean isSingleLineFor(DetailAST literalFor) {
362        boolean result = false;
363        if (literalFor.getLastChild().getType() == TokenTypes.EMPTY_STAT) {
364            result = true;
365        }
366        else if (literalFor.getParent().getType() == TokenTypes.SLIST) {
367            result = literalFor.getLineNo() == literalFor.getLastChild().getLineNo();
368        }
369        return result;
370    }
371
372    /**
373     * Checks if current if statement is single-line statement, e.g.:
374     * <p>
375     * {@code
376     * if (obj.isValid()) return true;
377     * }
378     * </p>
379     * @param literalIf {@link TokenTypes#LITERAL_IF if statement}.
380     * @return true if current if statement is single-line statement.
381     */
382    private static boolean isSingleLineIf(DetailAST literalIf) {
383        boolean result = false;
384        if (literalIf.getParent().getType() == TokenTypes.SLIST) {
385            final DetailAST literalIfLastChild = literalIf.getLastChild();
386            final DetailAST block;
387            if (literalIfLastChild.getType() == TokenTypes.LITERAL_ELSE) {
388                block = literalIfLastChild.getPreviousSibling();
389            }
390            else {
391                block = literalIfLastChild;
392            }
393            final DetailAST ifCondition = literalIf.findFirstToken(TokenTypes.EXPR);
394            result = ifCondition.getLineNo() == block.getLineNo();
395        }
396        return result;
397    }
398
399    /**
400     * Checks if current lambda statement is single-line statement, e.g.:
401     * <p>
402     * {@code
403     * Runnable r = () -> System.out.println("Hello, world!");
404     * }
405     * </p>
406     * @param lambda {@link TokenTypes#LAMBDA lambda statement}.
407     * @return true if current lambda statement is single-line statement.
408     */
409    private static boolean isSingleLineLambda(DetailAST lambda) {
410        final DetailAST block = lambda.getLastChild();
411        return lambda.getLineNo() == block.getLineNo();
412    }
413
414    /**
415     * Checks if current case statement is single-line statement, e.g.:
416     * <p>
417     * {@code
418     * case 1: doSomeStuff(); break;
419     * case 2: doSomeStuff(); break;
420     * case 3: ;
421     * }
422     * </p>
423     * @param literalCase {@link TokenTypes#LITERAL_CASE case statement}.
424     * @return true if current case statement is single-line statement.
425     */
426    private static boolean isSingleLineCase(DetailAST literalCase) {
427        boolean result = false;
428        final DetailAST slist = literalCase.getNextSibling();
429        if (slist == null) {
430            result = true;
431        }
432        else {
433            final DetailAST caseBreak = slist.findFirstToken(TokenTypes.LITERAL_BREAK);
434            if (caseBreak != null) {
435                final DetailAST block = slist.getFirstChild();
436                final boolean atOneLine = literalCase.getLineNo() == block.getLineNo();
437                result = atOneLine && block.getLineNo() == caseBreak.getLineNo();
438            }
439        }
440        return result;
441    }
442
443    /**
444     * Checks if current default statement is single-line statement, e.g.:
445     * <p>
446     * {@code
447     * default: doSomeStuff();
448     * }
449     * </p>
450     * @param literalDefault {@link TokenTypes#LITERAL_DEFAULT default statement}.
451     * @return true if current default statement is single-line statement.
452     */
453    private static boolean isSingleLineDefault(DetailAST literalDefault) {
454        boolean result = false;
455        final DetailAST slist = literalDefault.getNextSibling();
456        if (slist == null) {
457            result = true;
458        }
459        else {
460            final DetailAST block = slist.getFirstChild();
461            if (block != null && block.getType() != TokenTypes.SLIST) {
462                result = literalDefault.getLineNo() == block.getLineNo();
463            }
464        }
465        return result;
466    }
467
468    /**
469     * Checks if current else statement is single-line statement, e.g.:
470     * <p>
471     * {@code
472     * else doSomeStuff();
473     * }
474     * </p>
475     * @param literalElse {@link TokenTypes#LITERAL_ELSE else statement}.
476     * @return true if current else statement is single-line statement.
477     */
478    private static boolean isSingleLineElse(DetailAST literalElse) {
479        final DetailAST block = literalElse.getFirstChild();
480        return literalElse.getLineNo() == block.getLineNo();
481    }
482
483}