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.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"> 034 * EmptyForIteratorPad</a> to validate empty for iterators. 035 * </div> 036 * 037 * @since 3.0 038 */ 039@StatelessCheck 040public class WhitespaceAfterCheck 041 extends AbstractCheck { 042 043 /** 044 * A key is pointing to the warning message text in "messages.properties" 045 * file. 046 */ 047 public static final String MSG_WS_NOT_FOLLOWED = "ws.notFollowed"; 048 049 /** 050 * A key is pointing to the warning message text in "messages.properties" 051 * file. 052 */ 053 public static final String MSG_WS_TYPECAST = "ws.typeCast"; 054 055 /** 056 * Creates a new {@code WhitespaceAfterCheck} instance. 057 */ 058 public WhitespaceAfterCheck() { 059 // no code by default 060 } 061 062 @Override 063 public int[] getDefaultTokens() { 064 return new int[] { 065 TokenTypes.COMMA, 066 TokenTypes.SEMI, 067 TokenTypes.TYPECAST, 068 TokenTypes.LITERAL_IF, 069 TokenTypes.LITERAL_ELSE, 070 TokenTypes.LITERAL_WHILE, 071 TokenTypes.LITERAL_DO, 072 TokenTypes.LITERAL_FOR, 073 TokenTypes.LITERAL_FINALLY, 074 TokenTypes.LITERAL_RETURN, 075 TokenTypes.LITERAL_YIELD, 076 TokenTypes.LITERAL_CATCH, 077 TokenTypes.DO_WHILE, 078 TokenTypes.ELLIPSIS, 079 TokenTypes.LITERAL_SWITCH, 080 TokenTypes.LITERAL_SYNCHRONIZED, 081 TokenTypes.LITERAL_TRY, 082 TokenTypes.LITERAL_CASE, 083 TokenTypes.LAMBDA, 084 TokenTypes.LITERAL_WHEN, 085 }; 086 } 087 088 @Override 089 public int[] getAcceptableTokens() { 090 return new int[] { 091 TokenTypes.COMMA, 092 TokenTypes.SEMI, 093 TokenTypes.TYPECAST, 094 TokenTypes.LITERAL_IF, 095 TokenTypes.LITERAL_ELSE, 096 TokenTypes.LITERAL_WHILE, 097 TokenTypes.LITERAL_DO, 098 TokenTypes.LITERAL_FOR, 099 TokenTypes.LITERAL_FINALLY, 100 TokenTypes.LITERAL_RETURN, 101 TokenTypes.LITERAL_YIELD, 102 TokenTypes.LITERAL_CATCH, 103 TokenTypes.DO_WHILE, 104 TokenTypes.ELLIPSIS, 105 TokenTypes.LITERAL_SWITCH, 106 TokenTypes.LITERAL_SYNCHRONIZED, 107 TokenTypes.LITERAL_TRY, 108 TokenTypes.LITERAL_CASE, 109 TokenTypes.LAMBDA, 110 TokenTypes.LITERAL_WHEN, 111 TokenTypes.ANNOTATIONS, 112 }; 113 } 114 115 @Override 116 public int[] getRequiredTokens() { 117 return CommonUtil.EMPTY_INT_ARRAY; 118 } 119 120 @Override 121 public void visitToken(DetailAST ast) { 122 if (ast.getType() == TokenTypes.TYPECAST) { 123 final DetailAST targetAST = ast.findFirstToken(TokenTypes.RPAREN); 124 final int[] line = getLineCodePoints(targetAST.getLineNo() - 1); 125 if (!isFollowedByWhitespace(targetAST, line)) { 126 log(targetAST, MSG_WS_TYPECAST); 127 } 128 } 129 else if (ast.getType() == TokenTypes.ANNOTATIONS) { 130 if (ast.getFirstChild() != null) { 131 DetailAST targetAST = ast.getFirstChild().getLastChild(); 132 if (targetAST.getType() == TokenTypes.DOT) { 133 targetAST = targetAST.getLastChild(); 134 } 135 final int[] line = getLineCodePoints(targetAST.getLineNo() - 1); 136 if (!isFollowedByWhitespace(targetAST, line)) { 137 final Object[] message = {targetAST.getText()}; 138 log(targetAST, MSG_WS_NOT_FOLLOWED, message); 139 } 140 } 141 } 142 else { 143 final int[] line = getLineCodePoints(ast.getLineNo() - 1); 144 if (!isFollowedByWhitespace(ast, line)) { 145 final Object[] message = {ast.getText()}; 146 log(ast, MSG_WS_NOT_FOLLOWED, message); 147 } 148 } 149 } 150 151 /** 152 * Checks whether token is followed by a whitespace. 153 * 154 * @param targetAST Ast token. 155 * @param line Unicode code points array of line associated with the ast token. 156 * @return true if ast token is followed by a whitespace. 157 */ 158 private static boolean isFollowedByWhitespace(DetailAST targetAST, int... line) { 159 final int after = 160 targetAST.getColumnNo() + targetAST.getText().length(); 161 boolean followedByWhitespace = true; 162 163 if (after < line.length) { 164 final int codePoint = line[after]; 165 166 followedByWhitespace = codePoint == ';' 167 || codePoint == ')' 168 || Character.isWhitespace(codePoint); 169 } 170 return followedByWhitespace; 171 } 172 173}