001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 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.coding;
021
022import java.util.Objects;
023import java.util.regex.Pattern;
024import java.util.stream.Stream;
025
026import com.puppycrawl.tools.checkstyle.StatelessCheck;
027import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
028import com.puppycrawl.tools.checkstyle.api.DetailAST;
029import com.puppycrawl.tools.checkstyle.api.TokenTypes;
030import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
031
032/**
033 * <div>
034 * Checks for fall-through in {@code switch} statements.
035 * Finds locations where a {@code case} <b>contains</b> Java code but lacks a
036 * {@code break}, {@code return}, {@code yield}, {@code throw} or {@code continue} statement.
037 * </div>
038 *
039 * <p>
040 * The check honors special comments to suppress the warning.
041 * By default, the texts
042 * "fallthru", "fall thru", "fall-thru",
043 * "fallthrough", "fall through", "fall-through"
044 * "fallsthrough", "falls through", "falls-through" (case-sensitive).
045 * The comment containing these words must be all on one line,
046 * and must be on the last non-empty line before the {@code case} triggering
047 * the warning or on the same line before the {@code case}(ugly, but possible).
048 * Any other comment may follow on the same line.
049 * </p>
050 *
051 * <p>
052 * Note:
053 * The check assumes that there is no unreachable code in the {@code case}.
054 * </p>
055 *
056 * <p>
057 *  A {@code case} whose code ends in an infinite loop is not flagged, e.g.
058 * {@code while (true) {}}, {@code for (;;) {}}, {@code for (;true;) {}}
059 * or {@code do {} while (true);}.
060 * </p>
061 *
062 * @since 3.4
063 */
064@StatelessCheck
065public class FallThroughCheck extends AbstractCheck {
066
067    /**
068     * A key is pointing to the warning message text in "messages.properties"
069     * file.
070     */
071    public static final String MSG_FALL_THROUGH = "fall.through";
072
073    /**
074     * A key is pointing to the warning message text in "messages.properties"
075     * file.
076     */
077    public static final String MSG_FALL_THROUGH_LAST = "fall.through.last";
078
079    /** Control whether the last case group must be checked. */
080    private boolean checkLastCaseGroup;
081
082    /**
083     * Define the RegExp to match the relief comment that suppresses
084     * the warning about a fall through.
085     */
086    private Pattern reliefPattern = Pattern.compile("falls?[ -]?thr(u|ough)");
087
088    /**
089     * Creates a new {@code FallThroughCheck} instance.
090     */
091    public FallThroughCheck() {
092        // no code by default
093    }
094
095    @Override
096    public int[] getDefaultTokens() {
097        return getRequiredTokens();
098    }
099
100    @Override
101    public int[] getRequiredTokens() {
102        return new int[] {TokenTypes.CASE_GROUP};
103    }
104
105    @Override
106    public int[] getAcceptableTokens() {
107        return getRequiredTokens();
108    }
109
110    @Override
111    public boolean isCommentNodesRequired() {
112        return true;
113    }
114
115    /**
116     * Setter to define the RegExp to match the relief comment that suppresses
117     * the warning about a fall through.
118     *
119     * @param pattern
120     *            The regular expression pattern.
121     * @since 4.0
122     */
123    public void setReliefPattern(Pattern pattern) {
124        reliefPattern = pattern;
125    }
126
127    /**
128     * Setter to control whether the last case group must be checked.
129     *
130     * @param value new value of the property.
131     * @since 4.0
132     */
133    public void setCheckLastCaseGroup(boolean value) {
134        checkLastCaseGroup = value;
135    }
136
137    @Override
138    public void visitToken(DetailAST ast) {
139        final DetailAST nextGroup = ast.getNextSibling();
140        final boolean isLastGroup = nextGroup.getType() != TokenTypes.CASE_GROUP;
141        if (!isLastGroup || checkLastCaseGroup) {
142            final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
143
144            if (slist != null && !CheckUtil.isTerminated(slist) && !hasFallThroughComment(ast)) {
145                if (isLastGroup) {
146                    log(ast, MSG_FALL_THROUGH_LAST);
147                }
148                else {
149                    log(nextGroup, MSG_FALL_THROUGH);
150                }
151            }
152        }
153    }
154
155    /**
156     * Determines if the fall through case between {@code currentCase} and
157     * {@code nextCase} is relieved by an appropriate comment.
158     *
159     * <p>Handles</p>
160     * <pre>
161     * case 1:
162     * /&#42; FALLTHRU &#42;/ case 2:
163     *
164     * switch(i) {
165     * default:
166     * /&#42; FALLTHRU &#42;/}
167     *
168     * case 1:
169     * // FALLTHRU
170     * case 2:
171     *
172     * switch(i) {
173     * default:
174     * // FALLTHRU
175     * </pre>
176     *
177     * @param currentCase AST of the case that falls through to the next case.
178     * @return True if a relief comment was found
179     */
180    private boolean hasFallThroughComment(DetailAST currentCase) {
181        final DetailAST nextSibling = currentCase.getNextSibling();
182        final DetailAST ast;
183        if (nextSibling.getType() == TokenTypes.CASE_GROUP) {
184            ast = nextSibling.getFirstChild();
185        }
186        else {
187            ast = currentCase;
188        }
189        return hasReliefComment(ast);
190    }
191
192    /**
193     * Check if there is any fall through comment.
194     *
195     * @param ast ast to check
196     * @return true if relief comment found
197     */
198    private boolean hasReliefComment(DetailAST ast) {
199        final DetailAST nonCommentAst = CheckUtil.getNextNonCommentAst(ast);
200        boolean result = false;
201        if (nonCommentAst != null) {
202            final int prevLineNumber = nonCommentAst.getPreviousSibling().getLineNo();
203            result = Stream.iterate(nonCommentAst.getPreviousSibling(),
204                            Objects::nonNull,
205                            DetailAST::getPreviousSibling)
206                    .takeWhile(sibling -> sibling.getLineNo() == prevLineNumber)
207                    .map(DetailAST::getFirstChild)
208                    .filter(Objects::nonNull)
209                    .anyMatch(firstChild -> reliefPattern.matcher(firstChild.getText()).find());
210        }
211        return result;
212    }
213
214}