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