001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2023 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 <a href="https://checkstyle.org/config_whitespace.html#EmptyForIteratorPad"> 033 * EmptyForIteratorPad</a> to validate empty for iterators. 034 * </p> 035 * <ul> 036 * <li> 037 * Property {@code tokens} - tokens to check 038 * Type is {@code java.lang.String[]}. 039 * Validation type is {@code tokenSet}. 040 * Default value is: 041 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMMA"> 042 * COMMA</a>, 043 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SEMI"> 044 * SEMI</a>, 045 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#TYPECAST"> 046 * TYPECAST</a>, 047 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_IF"> 048 * LITERAL_IF</a>, 049 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_ELSE"> 050 * LITERAL_ELSE</a>, 051 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHILE"> 052 * LITERAL_WHILE</a>, 053 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO"> 054 * LITERAL_DO</a>, 055 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FOR"> 056 * LITERAL_FOR</a>, 057 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FINALLY"> 058 * LITERAL_FINALLY</a>, 059 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_RETURN"> 060 * LITERAL_RETURN</a>, 061 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_YIELD"> 062 * LITERAL_YIELD</a>, 063 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CATCH"> 064 * LITERAL_CATCH</a>, 065 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO"> 066 * DO_WHILE</a>, 067 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ELLIPSIS"> 068 * ELLIPSIS</a>, 069 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SWITCH"> 070 * LITERAL_SWITCH</a>, 071 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SYNCHRONIZED"> 072 * LITERAL_SYNCHRONIZED</a>, 073 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_TRY"> 074 * LITERAL_TRY</a>, 075 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CASE"> 076 * LITERAL_CASE</a>, 077 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAMBDA"> 078 * LAMBDA</a>. 079 * </li> 080 * </ul> 081 * <p> 082 * To configure the check: 083 * </p> 084 * <pre> 085 * <module name="WhitespaceAfter"/> 086 * </pre> 087 * <p>Example:</p> 088 * <pre> 089 * public void myTest() { 090 * if (foo) { // OK 091 * //... 092 * } else if(bar) { // violation 093 * //... 094 * } 095 * 096 * testMethod(foo, bar); // OK 097 * testMethod(foo,bar); // violation 098 * 099 * for (;;){} // OK 100 * for(;;){} // violation, space after 'for' is required 101 * 102 * try (InputStream ignored = System.in) {} // OK 103 * try(InputStream ignored = System.in) {} // violation ''try' is not followed by whitespace' 104 * 105 * try {} catch (Exception e){} // OK 106 * try{} catch (Exception e){} // violation ''try' is not followed by whitespace' 107 * 108 * try {} finally {} // OK 109 * try {} finally{} // violation ''finally' is not followed by whitespace' 110 * 111 * try {} catch (Error e){} finally {} // OK 112 * try {} catch (Error e){} finally{} // violation ''finally' is not followed by whitespace' 113 * 114 * try {} catch (Exception e){} // OK 115 * try {} catch(Exception e){} // violation ''catch' is not followed by whitespace' 116 * 117 * synchronized (this) { } // OK 118 * synchronized(this) { } // violation ''synchronized' is not followed by whitespace' 119 * } 120 * public String testOne() { 121 * return ("a" + "b"); // OK 122 * } 123 * public String testTwo() { 124 * return("a" + "b"); // violation 'return' is not followed by whitespace' 125 * } 126 * public static void main(String[] args) { 127 * int a = switch (args[0]) { 128 * case "got": 129 * yield (1); // OK 130 * case "my": 131 * yield(3); // violation ''yield' is not followed by whitespace' 132 * default: 133 * yield 2; 134 * }; 135 * } 136 * </pre> 137 * <p> 138 * To configure the check for whitespace only after COMMA and SEMI tokens: 139 * </p> 140 * <pre> 141 * <module name="WhitespaceAfter"> 142 * <property name="tokens" value="COMMA, SEMI"/> 143 * </module> 144 * </pre> 145 * <p>Example:</p> 146 * <pre> 147 * public void myTest() { 148 * int a; int b; // OK 149 * int a;int b; // violation 150 * 151 * testMethod(foo, bar); // OK 152 * testMethod(foo,bar); // violation 153 * 154 * for(;;) {} // OK 155 * } 156 * </pre> 157 * <p> 158 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 159 * </p> 160 * <p> 161 * Violation Message Keys: 162 * </p> 163 * <ul> 164 * <li> 165 * {@code ws.notFollowed} 166 * </li> 167 * <li> 168 * {@code ws.typeCast} 169 * </li> 170 * </ul> 171 * 172 * @since 3.0 173 */ 174@StatelessCheck 175public class WhitespaceAfterCheck 176 extends AbstractCheck { 177 178 /** 179 * A key is pointing to the warning message text in "messages.properties" 180 * file. 181 */ 182 public static final String MSG_WS_NOT_FOLLOWED = "ws.notFollowed"; 183 184 /** 185 * A key is pointing to the warning message text in "messages.properties" 186 * file. 187 */ 188 public static final String MSG_WS_TYPECAST = "ws.typeCast"; 189 190 @Override 191 public int[] getDefaultTokens() { 192 return getAcceptableTokens(); 193 } 194 195 @Override 196 public int[] getAcceptableTokens() { 197 return new int[] { 198 TokenTypes.COMMA, 199 TokenTypes.SEMI, 200 TokenTypes.TYPECAST, 201 TokenTypes.LITERAL_IF, 202 TokenTypes.LITERAL_ELSE, 203 TokenTypes.LITERAL_WHILE, 204 TokenTypes.LITERAL_DO, 205 TokenTypes.LITERAL_FOR, 206 TokenTypes.LITERAL_FINALLY, 207 TokenTypes.LITERAL_RETURN, 208 TokenTypes.LITERAL_YIELD, 209 TokenTypes.LITERAL_CATCH, 210 TokenTypes.DO_WHILE, 211 TokenTypes.ELLIPSIS, 212 TokenTypes.LITERAL_SWITCH, 213 TokenTypes.LITERAL_SYNCHRONIZED, 214 TokenTypes.LITERAL_TRY, 215 TokenTypes.LITERAL_CASE, 216 TokenTypes.LAMBDA, 217 }; 218 } 219 220 @Override 221 public int[] getRequiredTokens() { 222 return CommonUtil.EMPTY_INT_ARRAY; 223 } 224 225 @Override 226 public void visitToken(DetailAST ast) { 227 if (ast.getType() == TokenTypes.TYPECAST) { 228 final DetailAST targetAST = ast.findFirstToken(TokenTypes.RPAREN); 229 final int[] line = getLineCodePoints(targetAST.getLineNo() - 1); 230 if (!isFollowedByWhitespace(targetAST, line)) { 231 log(targetAST, MSG_WS_TYPECAST); 232 } 233 } 234 else { 235 final int[] line = getLineCodePoints(ast.getLineNo() - 1); 236 if (!isFollowedByWhitespace(ast, line)) { 237 final Object[] message = {ast.getText()}; 238 log(ast, MSG_WS_NOT_FOLLOWED, message); 239 } 240 } 241 } 242 243 /** 244 * Checks whether token is followed by a whitespace. 245 * 246 * @param targetAST Ast token. 247 * @param line Unicode code points array of line associated with the ast token. 248 * @return true if ast token is followed by a whitespace. 249 */ 250 private static boolean isFollowedByWhitespace(DetailAST targetAST, int... line) { 251 final int after = 252 targetAST.getColumnNo() + targetAST.getText().length(); 253 boolean followedByWhitespace = true; 254 255 if (after < line.length) { 256 final int codePoint = line[after]; 257 258 followedByWhitespace = codePoint == ';' 259 || codePoint == ')' 260 || Character.isWhitespace(codePoint); 261 } 262 return followedByWhitespace; 263 } 264 265}