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.utils.CommonUtil; 026 027/** 028 * <p> 029 * Checks that non-whitespace characters are separated by no more than one 030 * whitespace. Separating characters by tabs or multiple spaces will be 031 * reported. Currently the check doesn't permit horizontal alignment. To inspect 032 * whitespaces before and after comments, set the property 033 * {@code validateComments} to true. 034 * </p> 035 * 036 * <p> 037 * Setting {@code validateComments} to false will ignore cases like: 038 * </p> 039 * 040 * <pre> 041 * int i; // Multiple whitespaces before comment tokens will be ignored. 042 * private void foo(int /* whitespaces before and after block-comments will be 043 * ignored */ i) { 044 * </pre> 045 * 046 * <p> 047 * Sometimes, users like to space similar items on different lines to the same 048 * column position for easier reading. This feature isn't supported by this 049 * check, so both braces in the following case will be reported as violations. 050 * </p> 051 * 052 * <pre> 053 * public long toNanos(long d) { return d; } // 2 violations 054 * public long toMicros(long d) { return d / (C1 / C0); } 055 * </pre> 056 * <ul> 057 * <li> 058 * Property {@code validateComments} - Control whether to validate whitespaces 059 * surrounding comments. 060 * Type is {@code boolean}. 061 * Default value is {@code false}. 062 * </li> 063 * </ul> 064 * <p> 065 * To configure the check: 066 * </p> 067 * 068 * <pre> 069 * <module name="SingleSpaceSeparator"/> 070 * </pre> 071 * <p>Example:</p> 072 * <pre> 073 * int foo() { // violation, 3 whitespaces 074 * return 1; // violation, 2 whitespaces 075 * } 076 * int fun1() { // OK, 1 whitespace 077 * return 3; // OK, 1 whitespace 078 * } 079 * void fun2() {} // violation, 2 whitespaces 080 * </pre> 081 * 082 * <p> 083 * To configure the check so that it validates comments: 084 * </p> 085 * 086 * <pre> 087 * <module name="SingleSpaceSeparator"> 088 * <property name="validateComments" value="true"/> 089 * </module> 090 * </pre> 091 * <p>Example:</p> 092 * <pre> 093 * void fun1() {} // violation, 2 whitespaces before the comment starts 094 * void fun2() { return; } /* violation here, 2 whitespaces before the comment starts */ 095 * 096 * /* violation, 2 whitespaces after the comment ends */ int a; 097 * 098 * String s; /* OK, 1 whitespace */ 099 * 100 * /** 101 * * This is a Javadoc comment 102 * */ int b; // violation, 2 whitespaces after the javadoc comment ends 103 * 104 * float f1; // OK, 1 whitespace 105 * 106 * /** 107 * * OK, 1 white space after the doc comment ends 108 * */ float f2; 109 * </pre> 110 * <p> 111 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 112 * </p> 113 * <p> 114 * Violation Message Keys: 115 * </p> 116 * <ul> 117 * <li> 118 * {@code single.space.separator} 119 * </li> 120 * </ul> 121 * 122 * @since 6.19 123 */ 124@StatelessCheck 125public class SingleSpaceSeparatorCheck extends AbstractCheck { 126 127 /** 128 * A key is pointing to the warning message text in "messages.properties" 129 * file. 130 */ 131 public static final String MSG_KEY = "single.space.separator"; 132 133 /** Control whether to validate whitespaces surrounding comments. */ 134 private boolean validateComments; 135 136 /** 137 * Setter to control whether to validate whitespaces surrounding comments. 138 * 139 * @param validateComments {@code true} to validate surrounding whitespaces at comments. 140 */ 141 public void setValidateComments(boolean validateComments) { 142 this.validateComments = validateComments; 143 } 144 145 @Override 146 public int[] getDefaultTokens() { 147 return getRequiredTokens(); 148 } 149 150 @Override 151 public int[] getAcceptableTokens() { 152 return getRequiredTokens(); 153 } 154 155 @Override 156 public int[] getRequiredTokens() { 157 return CommonUtil.EMPTY_INT_ARRAY; 158 } 159 160 @Override 161 public boolean isCommentNodesRequired() { 162 return validateComments; 163 } 164 165 @Override 166 public void beginTree(DetailAST rootAST) { 167 if (rootAST != null) { 168 visitEachToken(rootAST); 169 } 170 } 171 172 /** 173 * Examines every sibling and child of {@code node} for violations. 174 * 175 * @param node The node to start examining. 176 */ 177 private void visitEachToken(DetailAST node) { 178 DetailAST sibling = node; 179 180 do { 181 final int columnNo = sibling.getColumnNo() - 1; 182 183 // in such expression: "j =123", placed at the start of the string index of the second 184 // space character will be: 2 = 0(j) + 1(whitespace) + 1(whitespace). It is a minimal 185 // possible index for the second whitespace between non-whitespace characters. 186 final int minSecondWhitespaceColumnNo = 2; 187 188 if (columnNo >= minSecondWhitespaceColumnNo 189 && !isTextSeparatedCorrectlyFromPrevious(getLine(sibling.getLineNo() - 1), 190 columnNo)) { 191 log(sibling, MSG_KEY); 192 } 193 if (sibling.getChildCount() >= 1) { 194 visitEachToken(sibling.getFirstChild()); 195 } 196 197 sibling = sibling.getNextSibling(); 198 } while (sibling != null); 199 } 200 201 /** 202 * Checks if characters in {@code line} at and around {@code columnNo} has 203 * the correct number of spaces. to return {@code true} the following 204 * conditions must be met:<br /> 205 * - the character at {@code columnNo} is the first in the line.<br /> 206 * - the character at {@code columnNo} is not separated by whitespaces from 207 * the previous non-whitespace character. <br /> 208 * - the character at {@code columnNo} is separated by only one whitespace 209 * from the previous non-whitespace character.<br /> 210 * - {@link #validateComments} is disabled and the previous text is the 211 * end of a block comment. 212 * 213 * @param line The line in the file to examine. 214 * @param columnNo The column position in the {@code line} to examine. 215 * @return {@code true} if the text at {@code columnNo} is separated 216 * correctly from the previous token. 217 */ 218 private boolean isTextSeparatedCorrectlyFromPrevious(String line, int columnNo) { 219 return isSingleSpace(line, columnNo) 220 || !isWhitespace(line, columnNo) 221 || isFirstInLine(line, columnNo) 222 || !validateComments && isBlockCommentEnd(line, columnNo); 223 } 224 225 /** 226 * Checks if the {@code line} at {@code columnNo} is a single space, and not 227 * preceded by another space. 228 * 229 * @param line The line in the file to examine. 230 * @param columnNo The column position in the {@code line} to examine. 231 * @return {@code true} if the character at {@code columnNo} is a space, and 232 * not preceded by another space. 233 */ 234 private static boolean isSingleSpace(String line, int columnNo) { 235 return isSpace(line, columnNo) && !Character.isWhitespace(line.charAt(columnNo - 1)); 236 } 237 238 /** 239 * Checks if the {@code line} at {@code columnNo} is a space. 240 * 241 * @param line The line in the file to examine. 242 * @param columnNo The column position in the {@code line} to examine. 243 * @return {@code true} if the character at {@code columnNo} is a space. 244 */ 245 private static boolean isSpace(String line, int columnNo) { 246 return line.charAt(columnNo) == ' '; 247 } 248 249 /** 250 * Checks if the {@code line} at {@code columnNo} is a whitespace character. 251 * 252 * @param line The line in the file to examine. 253 * @param columnNo The column position in the {@code line} to examine. 254 * @return {@code true} if the character at {@code columnNo} is a 255 * whitespace. 256 */ 257 private static boolean isWhitespace(String line, int columnNo) { 258 return Character.isWhitespace(line.charAt(columnNo)); 259 } 260 261 /** 262 * Checks if the {@code line} up to and including {@code columnNo} is all 263 * non-whitespace text encountered. 264 * 265 * @param line The line in the file to examine. 266 * @param columnNo The column position in the {@code line} to examine. 267 * @return {@code true} if the column position is the first non-whitespace 268 * text on the {@code line}. 269 */ 270 private static boolean isFirstInLine(String line, int columnNo) { 271 return CommonUtil.isBlank(line.substring(0, columnNo)); 272 } 273 274 /** 275 * Checks if the {@code line} at {@code columnNo} is the end of a comment, 276 * '*/'. 277 * 278 * @param line The line in the file to examine. 279 * @param columnNo The column position in the {@code line} to examine. 280 * @return {@code true} if the previous text is a end comment block. 281 */ 282 private static boolean isBlockCommentEnd(String line, int columnNo) { 283 return line.substring(0, columnNo).trim().endsWith("*/"); 284 } 285 286}