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: The check assumes that there is no unreachable code in the {@code case}.
053 * </p>
054 *
055 * @since 3.4
056 */
057@StatelessCheck
058public class FallThroughCheck extends AbstractCheck {
059
060    /**
061     * A key is pointing to the warning message text in "messages.properties"
062     * file.
063     */
064    public static final String MSG_FALL_THROUGH = "fall.through";
065
066    /**
067     * A key is pointing to the warning message text in "messages.properties"
068     * file.
069     */
070    public static final String MSG_FALL_THROUGH_LAST = "fall.through.last";
071
072    /** Control whether the last case group must be checked. */
073    private boolean checkLastCaseGroup;
074
075    /**
076     * Define the RegExp to match the relief comment that suppresses
077     * the warning about a fall through.
078     */
079    private Pattern reliefPattern = Pattern.compile("falls?[ -]?thr(u|ough)");
080
081    /**
082     * Creates a new {@code FallThroughCheck} instance.
083     */
084    public FallThroughCheck() {
085        // no code by default
086    }
087
088    @Override
089    public int[] getDefaultTokens() {
090        return getRequiredTokens();
091    }
092
093    @Override
094    public int[] getRequiredTokens() {
095        return new int[] {TokenTypes.CASE_GROUP};
096    }
097
098    @Override
099    public int[] getAcceptableTokens() {
100        return getRequiredTokens();
101    }
102
103    @Override
104    public boolean isCommentNodesRequired() {
105        return true;
106    }
107
108    /**
109     * Setter to define the RegExp to match the relief comment that suppresses
110     * the warning about a fall through.
111     *
112     * @param pattern
113     *            The regular expression pattern.
114     * @since 4.0
115     */
116    public void setReliefPattern(Pattern pattern) {
117        reliefPattern = pattern;
118    }
119
120    /**
121     * Setter to control whether the last case group must be checked.
122     *
123     * @param value new value of the property.
124     * @since 4.0
125     */
126    public void setCheckLastCaseGroup(boolean value) {
127        checkLastCaseGroup = value;
128    }
129
130    @Override
131    public void visitToken(DetailAST ast) {
132        final DetailAST nextGroup = ast.getNextSibling();
133        final boolean isLastGroup = nextGroup.getType() != TokenTypes.CASE_GROUP;
134        if (!isLastGroup || checkLastCaseGroup) {
135            final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
136
137            if (slist != null && !CheckUtil.isTerminated(slist) && !hasFallThroughComment(ast)) {
138                if (isLastGroup) {
139                    log(ast, MSG_FALL_THROUGH_LAST);
140                }
141                else {
142                    log(nextGroup, MSG_FALL_THROUGH);
143                }
144            }
145        }
146    }
147
148    /**
149     * Determines if the fall through case between {@code currentCase} and
150     * {@code nextCase} is relieved by an appropriate comment.
151     *
152     * <p>Handles</p>
153     * <pre>
154     * case 1:
155     * /&#42; FALLTHRU &#42;/ case 2:
156     *
157     * switch(i) {
158     * default:
159     * /&#42; FALLTHRU &#42;/}
160     *
161     * case 1:
162     * // FALLTHRU
163     * case 2:
164     *
165     * switch(i) {
166     * default:
167     * // FALLTHRU
168     * </pre>
169     *
170     * @param currentCase AST of the case that falls through to the next case.
171     * @return True if a relief comment was found
172     */
173    private boolean hasFallThroughComment(DetailAST currentCase) {
174        final DetailAST nextSibling = currentCase.getNextSibling();
175        final DetailAST ast;
176        if (nextSibling.getType() == TokenTypes.CASE_GROUP) {
177            ast = nextSibling.getFirstChild();
178        }
179        else {
180            ast = currentCase;
181        }
182        return hasReliefComment(ast);
183    }
184
185    /**
186     * Check if there is any fall through comment.
187     *
188     * @param ast ast to check
189     * @return true if relief comment found
190     */
191    private boolean hasReliefComment(DetailAST ast) {
192        final DetailAST nonCommentAst = CheckUtil.getNextNonCommentAst(ast);
193        boolean result = false;
194        if (nonCommentAst != null) {
195            final int prevLineNumber = nonCommentAst.getPreviousSibling().getLineNo();
196            result = Stream.iterate(nonCommentAst.getPreviousSibling(),
197                            Objects::nonNull,
198                            DetailAST::getPreviousSibling)
199                    .takeWhile(sibling -> sibling.getLineNo() == prevLineNumber)
200                    .map(DetailAST::getFirstChild)
201                    .filter(Objects::nonNull)
202                    .anyMatch(firstChild -> reliefPattern.matcher(firstChild.getText()).find());
203        }
204        return result;
205    }
206
207}