001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2022 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 there is no whitespace before a token. 031 * More specifically, it checks that it is not preceded with whitespace, 032 * or (if linebreaks are allowed) all characters on the line before are 033 * whitespace. To allow linebreaks before a token, set property 034 * {@code allowLineBreaks} to {@code true}. No check occurs before semi-colons in empty 035 * for loop initializers or conditions. 036 * </p> 037 * <ul> 038 * <li> 039 * Property {@code allowLineBreaks} - Control whether whitespace is allowed 040 * if the token is at a linebreak. 041 * Type is {@code boolean}. 042 * Default value is {@code false}. 043 * </li> 044 * <li> 045 * Property {@code tokens} - tokens to check 046 * Type is {@code java.lang.String[]}. 047 * Validation type is {@code tokenSet}. 048 * Default value is: 049 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMMA"> 050 * COMMA</a>, 051 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SEMI"> 052 * SEMI</a>, 053 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#POST_INC"> 054 * POST_INC</a>, 055 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#POST_DEC"> 056 * POST_DEC</a>, 057 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ELLIPSIS"> 058 * ELLIPSIS</a>, 059 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LABELED_STAT"> 060 * LABELED_STAT</a>. 061 * </li> 062 * </ul> 063 * <p> 064 * To configure the check: 065 * </p> 066 * <pre> 067 * <module name="NoWhitespaceBefore"/> 068 * </pre> 069 * <p>Example:</p> 070 * <pre> 071 * int foo; 072 * foo ++; // violation, whitespace before '++' is not allowed 073 * foo++; // OK 074 * for (int i = 0 ; i < 5; i++) {} // violation 075 * // ^ whitespace before ';' is not allowed 076 * for (int i = 0; i < 5; i++) {} // OK 077 * int[][] array = { { 1, 2 } 078 * , { 3, 4 } }; // violation, whitespace before ',' is not allowed 079 * int[][] array2 = { { 1, 2 }, 080 * { 3, 4 } }; // OK 081 * Lists.charactersOf("foo").listIterator() 082 * .forEachRemaining(System.out::print) 083 * ; // violation, whitespace before ';' is not allowed 084 * { 085 * label1 : // violation, whitespace before ':' is not allowed 086 * for (int i = 0; i < 10; i++) {} 087 * } 088 * 089 * { 090 * label2: // OK 091 * while (true) {} 092 * } 093 * </pre> 094 * <p>To configure the check to allow linebreaks before default tokens:</p> 095 * <pre> 096 * <module name="NoWhitespaceBefore"> 097 * <property name="allowLineBreaks" value="true"/> 098 * </module> 099 * </pre> 100 * <p>Example:</p> 101 * <pre> 102 * int[][] array = { { 1, 2 } 103 * , { 3, 4 } }; // OK, linebreak is allowed before ',' 104 * int[][] array2 = { { 1, 2 }, 105 * { 3, 4 } }; // OK, ideal code 106 * void ellipsisExample(String ...params) {}; // violation, whitespace before '...' is not allowed 107 * void ellipsisExample2(String 108 * ...params) {}; //OK, linebreak is allowed before '...' 109 * Lists.charactersOf("foo") 110 * .listIterator() 111 * .forEachRemaining(System.out::print); // OK 112 * </pre> 113 * <p> 114 * To Configure the check to restrict the use of whitespace before METHOD_REF and DOT tokens: 115 * </p> 116 * <pre> 117 * <module name="NoWhitespaceBefore"> 118 * <property name="tokens" value="METHOD_REF"/> 119 * <property name="tokens" value="DOT"/> 120 * </module> 121 * </pre> 122 * <p>Example:</p> 123 * <pre> 124 * Lists.charactersOf("foo").listIterator() 125 * .forEachRemaining(System.out::print); // violation, whitespace before '.' is not allowed 126 * Lists.charactersOf("foo").listIterator().forEachRemaining(System.out ::print); // violation, 127 * // whitespace before '::' is not allowed ^ 128 * Lists.charactersOf("foo").listIterator().forEachRemaining(System.out::print); // OK 129 * </pre> 130 * <p> 131 * To configure the check to allow linebreak before METHOD_REF and DOT tokens: 132 * </p> 133 * <pre> 134 * <module name="NoWhitespaceBefore"> 135 * <property name="tokens" value="METHOD_REF"/> 136 * <property name="tokens" value="DOT"/> 137 * <property name="allowLineBreaks" value="true"/> 138 * </module> 139 * </pre> 140 * <p>Example:</p> 141 * <pre> 142 * Lists .charactersOf("foo") //violation, whitespace before '.' is not allowed 143 * .listIterator() 144 * .forEachRemaining(System.out ::print); // violation, 145 * // ^ whitespace before '::' is not allowed 146 * Lists.charactersOf("foo") 147 * .listIterator() 148 * .forEachRemaining(System.out::print); // OK 149 * </pre> 150 * <p> 151 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 152 * </p> 153 * <p> 154 * Violation Message Keys: 155 * </p> 156 * <ul> 157 * <li> 158 * {@code ws.preceded} 159 * </li> 160 * </ul> 161 * 162 * @since 3.0 163 */ 164@StatelessCheck 165public class NoWhitespaceBeforeCheck 166 extends AbstractCheck { 167 168 /** 169 * A key is pointing to the warning message text in "messages.properties" 170 * file. 171 */ 172 public static final String MSG_KEY = "ws.preceded"; 173 174 /** Control whether whitespace is allowed if the token is at a linebreak. */ 175 private boolean allowLineBreaks; 176 177 @Override 178 public int[] getDefaultTokens() { 179 return new int[] { 180 TokenTypes.COMMA, 181 TokenTypes.SEMI, 182 TokenTypes.POST_INC, 183 TokenTypes.POST_DEC, 184 TokenTypes.ELLIPSIS, 185 TokenTypes.LABELED_STAT, 186 }; 187 } 188 189 @Override 190 public int[] getAcceptableTokens() { 191 return new int[] { 192 TokenTypes.COMMA, 193 TokenTypes.SEMI, 194 TokenTypes.POST_INC, 195 TokenTypes.POST_DEC, 196 TokenTypes.DOT, 197 TokenTypes.GENERIC_START, 198 TokenTypes.GENERIC_END, 199 TokenTypes.ELLIPSIS, 200 TokenTypes.LABELED_STAT, 201 TokenTypes.METHOD_REF, 202 }; 203 } 204 205 @Override 206 public int[] getRequiredTokens() { 207 return CommonUtil.EMPTY_INT_ARRAY; 208 } 209 210 @Override 211 public void visitToken(DetailAST ast) { 212 final String line = getLine(ast.getLineNo() - 1); 213 final int before = ast.getColumnNo() - 1; 214 final int[] codePoints = line.codePoints().toArray(); 215 216 if ((before == -1 || CommonUtil.isCodePointWhitespace(codePoints, before)) 217 && !isInEmptyForInitializerOrCondition(ast)) { 218 boolean flag = !allowLineBreaks; 219 // verify all characters before '.' are whitespace 220 for (int i = 0; i <= before - 1; i++) { 221 if (!CommonUtil.isCodePointWhitespace(codePoints, i)) { 222 flag = true; 223 break; 224 } 225 } 226 if (flag) { 227 log(ast, MSG_KEY, ast.getText()); 228 } 229 } 230 } 231 232 /** 233 * Checks that semicolon is in empty for initializer or condition. 234 * 235 * @param semicolonAst DetailAST of semicolon. 236 * @return true if semicolon is in empty for initializer or condition. 237 */ 238 private static boolean isInEmptyForInitializerOrCondition(DetailAST semicolonAst) { 239 boolean result = false; 240 final DetailAST sibling = semicolonAst.getPreviousSibling(); 241 if (sibling != null 242 && (sibling.getType() == TokenTypes.FOR_INIT 243 || sibling.getType() == TokenTypes.FOR_CONDITION) 244 && !sibling.hasChildren()) { 245 result = true; 246 } 247 return result; 248 } 249 250 /** 251 * Setter to control whether whitespace is allowed if the token is at a linebreak. 252 * 253 * @param allowLineBreaks whether whitespace should be 254 * flagged at line breaks. 255 */ 256 public void setAllowLineBreaks(boolean allowLineBreaks) { 257 this.allowLineBreaks = allowLineBreaks; 258 } 259 260}