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 TokenTypes.SINGLE_LINE_COMMENT, 113 }; 114 } 115 116 @Override 117 public int[] getRequiredTokens() { 118 return CommonUtil.EMPTY_INT_ARRAY; 119 } 120 121 @Override 122 public boolean isCommentNodesRequired() { 123 return true; 124 } 125 126 @Override 127 public void visitToken(DetailAST ast) { 128 final int type = ast.getType(); 129 if (type == TokenTypes.TYPECAST) { 130 final DetailAST targetAST = ast.findFirstToken(TokenTypes.RPAREN); 131 final int[] line = getLineCodePoints(targetAST.getLineNo() - 1); 132 if (!isFollowedByWhitespace(targetAST, line)) { 133 log(targetAST, MSG_WS_TYPECAST); 134 } 135 } 136 else if (type == TokenTypes.ANNOTATIONS) { 137 if (ast.getFirstChild() != null) { 138 DetailAST targetAST = ast.getFirstChild().getLastChild(); 139 if (targetAST.getType() == TokenTypes.DOT) { 140 targetAST = targetAST.getLastChild(); 141 } 142 final int[] line = getLineCodePoints(targetAST.getLineNo() - 1); 143 if (!isFollowedByWhitespace(targetAST, line)) { 144 final Object[] message = {targetAST.getText()}; 145 log(targetAST, MSG_WS_NOT_FOLLOWED, message); 146 } 147 } 148 } 149 else if (type == TokenTypes.SINGLE_LINE_COMMENT) { 150 if (!isCommentFollowedByWhitespace(ast)) { 151 log(ast, MSG_WS_NOT_FOLLOWED, ast.getText()); 152 } 153 } 154 else { 155 final int[] line = getLineCodePoints(ast.getLineNo() - 1); 156 if (!isFollowedByWhitespace(ast, line)) { 157 final Object[] message = {ast.getText()}; 158 log(ast, MSG_WS_NOT_FOLLOWED, message); 159 } 160 } 161 } 162 163 /** 164 * Checks whether a single-line comment is followed by whitespace. 165 * 166 * @param ast AST token representing the single-line comment. 167 * @return {@code true} if the comment content is empty or is 168 * followed by whitespace after any leading slashes. 169 */ 170 private static boolean isCommentFollowedByWhitespace(DetailAST ast) { 171 final DetailAST commentContent = ast.findFirstToken(TokenTypes.COMMENT_CONTENT); 172 final String commentText = commentContent.getText(); 173 int index = 0; 174 while (commentText.charAt(index) == '/') { 175 index++; 176 } 177 final int codePoint = commentText.codePointAt(index); 178 return Character.isWhitespace(codePoint); 179 } 180 181 /** 182 * Checks whether token is followed by a whitespace. 183 * 184 * @param targetAST Ast token. 185 * @param line Unicode code points array of line associated with the ast token. 186 * @return true if ast token is followed by a whitespace. 187 */ 188 private static boolean isFollowedByWhitespace(DetailAST targetAST, int... line) { 189 final int after = 190 targetAST.getColumnNo() + targetAST.getText().length(); 191 boolean followedByWhitespace = true; 192 193 if (after < line.length) { 194 final int codePoint = line[after]; 195 196 followedByWhitespace = codePoint == ';' 197 || codePoint == ')' 198 || Character.isWhitespace(codePoint); 199 } 200 return followedByWhitespace; 201 } 202 203}