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 * <p>
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 * </p>
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 * </li>
081 * </ul>
082 * <p>
083 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
084 * </p>
085 * <p>
086 * Violation Message Keys:
087 * </p>
088 * <ul>
089 * <li>
090 * {@code ws.notFollowed}
091 * </li>
092 * <li>
093 * {@code ws.typeCast}
094 * </li>
095 * </ul>
096 *
097 * @since 3.0
098 */
099@StatelessCheck
100public class WhitespaceAfterCheck
101    extends AbstractCheck {
102
103    /**
104     * A key is pointing to the warning message text in "messages.properties"
105     * file.
106     */
107    public static final String MSG_WS_NOT_FOLLOWED = "ws.notFollowed";
108
109    /**
110     * A key is pointing to the warning message text in "messages.properties"
111     * file.
112     */
113    public static final String MSG_WS_TYPECAST = "ws.typeCast";
114
115    @Override
116    public int[] getDefaultTokens() {
117        return getAcceptableTokens();
118    }
119
120    @Override
121    public int[] getAcceptableTokens() {
122        return new int[] {
123            TokenTypes.COMMA,
124            TokenTypes.SEMI,
125            TokenTypes.TYPECAST,
126            TokenTypes.LITERAL_IF,
127            TokenTypes.LITERAL_ELSE,
128            TokenTypes.LITERAL_WHILE,
129            TokenTypes.LITERAL_DO,
130            TokenTypes.LITERAL_FOR,
131            TokenTypes.LITERAL_FINALLY,
132            TokenTypes.LITERAL_RETURN,
133            TokenTypes.LITERAL_YIELD,
134            TokenTypes.LITERAL_CATCH,
135            TokenTypes.DO_WHILE,
136            TokenTypes.ELLIPSIS,
137            TokenTypes.LITERAL_SWITCH,
138            TokenTypes.LITERAL_SYNCHRONIZED,
139            TokenTypes.LITERAL_TRY,
140            TokenTypes.LITERAL_CASE,
141            TokenTypes.LAMBDA,
142        };
143    }
144
145    @Override
146    public int[] getRequiredTokens() {
147        return CommonUtil.EMPTY_INT_ARRAY;
148    }
149
150    @Override
151    public void visitToken(DetailAST ast) {
152        if (ast.getType() == TokenTypes.TYPECAST) {
153            final DetailAST targetAST = ast.findFirstToken(TokenTypes.RPAREN);
154            final int[] line = getLineCodePoints(targetAST.getLineNo() - 1);
155            if (!isFollowedByWhitespace(targetAST, line)) {
156                log(targetAST, MSG_WS_TYPECAST);
157            }
158        }
159        else {
160            final int[] line = getLineCodePoints(ast.getLineNo() - 1);
161            if (!isFollowedByWhitespace(ast, line)) {
162                final Object[] message = {ast.getText()};
163                log(ast, MSG_WS_NOT_FOLLOWED, message);
164            }
165        }
166    }
167
168    /**
169     * Checks whether token is followed by a whitespace.
170     *
171     * @param targetAST Ast token.
172     * @param line Unicode code points array of line associated with the ast token.
173     * @return true if ast token is followed by a whitespace.
174     */
175    private static boolean isFollowedByWhitespace(DetailAST targetAST, int... line) {
176        final int after =
177            targetAST.getColumnNo() + targetAST.getText().length();
178        boolean followedByWhitespace = true;
179
180        if (after < line.length) {
181            final int codePoint = line[after];
182
183            followedByWhitespace = codePoint == ';'
184                || codePoint == ')'
185                || Character.isWhitespace(codePoint);
186        }
187        return followedByWhitespace;
188    }
189
190}