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