001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 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.whitespace;
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 * <div>
030 * Checks that a token is followed by whitespace, with the exception that it
031 * does not check for whitespace after the semicolon of an empty for iterator.
032 * Use Check
033 * <a href="https://checkstyle.org/checks/whitespace/emptyforiteratorpad.html#EmptyForIteratorPad">
034 * EmptyForIteratorPad</a> to validate empty for iterators.
035 * </div>
036 * <ul>
037 * <li>
038 * Property {@code tokens} - tokens to check
039 * Type is {@code java.lang.String[]}.
040 * Validation type is {@code tokenSet}.
041 * Default value is:
042 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMMA">
043 * COMMA</a>,
044 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SEMI">
045 * SEMI</a>,
046 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#TYPECAST">
047 * TYPECAST</a>,
048 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_IF">
049 * LITERAL_IF</a>,
050 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_ELSE">
051 * LITERAL_ELSE</a>,
052 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHILE">
053 * LITERAL_WHILE</a>,
054 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO">
055 * LITERAL_DO</a>,
056 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FOR">
057 * LITERAL_FOR</a>,
058 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FINALLY">
059 * LITERAL_FINALLY</a>,
060 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_RETURN">
061 * LITERAL_RETURN</a>,
062 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_YIELD">
063 * LITERAL_YIELD</a>,
064 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CATCH">
065 * LITERAL_CATCH</a>,
066 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DO_WHILE">
067 * DO_WHILE</a>,
068 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ELLIPSIS">
069 * ELLIPSIS</a>,
070 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SWITCH">
071 * LITERAL_SWITCH</a>,
072 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SYNCHRONIZED">
073 * LITERAL_SYNCHRONIZED</a>,
074 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_TRY">
075 * LITERAL_TRY</a>,
076 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CASE">
077 * LITERAL_CASE</a>,
078 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAMBDA">
079 * LAMBDA</a>,
080 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHEN">
081 * LITERAL_WHEN</a>.
082 * </li>
083 * </ul>
084 *
085 * <p>
086 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
087 * </p>
088 *
089 * <p>
090 * Violation Message Keys:
091 * </p>
092 * <ul>
093 * <li>
094 * {@code ws.notFollowed}
095 * </li>
096 * <li>
097 * {@code ws.typeCast}
098 * </li>
099 * </ul>
100 *
101 * @since 3.0
102 */
103@StatelessCheck
104public class WhitespaceAfterCheck
105    extends AbstractCheck {
106
107    /**
108     * A key is pointing to the warning message text in "messages.properties"
109     * file.
110     */
111    public static final String MSG_WS_NOT_FOLLOWED = "ws.notFollowed";
112
113    /**
114     * A key is pointing to the warning message text in "messages.properties"
115     * file.
116     */
117    public static final String MSG_WS_TYPECAST = "ws.typeCast";
118
119    @Override
120    public int[] getDefaultTokens() {
121        return getAcceptableTokens();
122    }
123
124    @Override
125    public int[] getAcceptableTokens() {
126        return new int[] {
127            TokenTypes.COMMA,
128            TokenTypes.SEMI,
129            TokenTypes.TYPECAST,
130            TokenTypes.LITERAL_IF,
131            TokenTypes.LITERAL_ELSE,
132            TokenTypes.LITERAL_WHILE,
133            TokenTypes.LITERAL_DO,
134            TokenTypes.LITERAL_FOR,
135            TokenTypes.LITERAL_FINALLY,
136            TokenTypes.LITERAL_RETURN,
137            TokenTypes.LITERAL_YIELD,
138            TokenTypes.LITERAL_CATCH,
139            TokenTypes.DO_WHILE,
140            TokenTypes.ELLIPSIS,
141            TokenTypes.LITERAL_SWITCH,
142            TokenTypes.LITERAL_SYNCHRONIZED,
143            TokenTypes.LITERAL_TRY,
144            TokenTypes.LITERAL_CASE,
145            TokenTypes.LAMBDA,
146            TokenTypes.LITERAL_WHEN,
147        };
148    }
149
150    @Override
151    public int[] getRequiredTokens() {
152        return CommonUtil.EMPTY_INT_ARRAY;
153    }
154
155    @Override
156    public void visitToken(DetailAST ast) {
157        if (ast.getType() == TokenTypes.TYPECAST) {
158            final DetailAST targetAST = ast.findFirstToken(TokenTypes.RPAREN);
159            final int[] line = getLineCodePoints(targetAST.getLineNo() - 1);
160            if (!isFollowedByWhitespace(targetAST, line)) {
161                log(targetAST, MSG_WS_TYPECAST);
162            }
163        }
164        else {
165            final int[] line = getLineCodePoints(ast.getLineNo() - 1);
166            if (!isFollowedByWhitespace(ast, line)) {
167                final Object[] message = {ast.getText()};
168                log(ast, MSG_WS_NOT_FOLLOWED, message);
169            }
170        }
171    }
172
173    /**
174     * Checks whether token is followed by a whitespace.
175     *
176     * @param targetAST Ast token.
177     * @param line Unicode code points array of line associated with the ast token.
178     * @return true if ast token is followed by a whitespace.
179     */
180    private static boolean isFollowedByWhitespace(DetailAST targetAST, int... line) {
181        final int after =
182            targetAST.getColumnNo() + targetAST.getText().length();
183        boolean followedByWhitespace = true;
184
185        if (after < line.length) {
186            final int codePoint = line[after];
187
188            followedByWhitespace = codePoint == ';'
189                || codePoint == ')'
190                || Character.isWhitespace(codePoint);
191        }
192        return followedByWhitespace;
193    }
194
195}