001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2018 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.CommonUtils; 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 * <b>validateComments</b> to true. 034 * </p> 035 * 036 * <p> 037 * Setting <b>validateComments</b> 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 * 057 * <p> 058 * Check have following options: 059 * </p> 060 * 061 * <ul> 062 * <li>validateComments - Boolean when set to {@code true}, whitespaces 063 * surrounding comments will be ignored. Default value is {@code false}.</li> 064 * </ul> 065 * 066 * <p> 067 * To configure the check: 068 * </p> 069 * 070 * <pre> 071 * <module name="SingleSpaceSeparator"/> 072 * </pre> 073 * 074 * <p> 075 * To configure the check so that it validates comments: 076 * </p> 077 * 078 * <pre> 079 * <module name="SingleSpaceSeparator"> 080 * <property name="validateComments" value="true"/> 081 * </module> 082 * </pre> 083 * 084 * @author Robert Whitebit 085 * @author Richard Veach 086 */ 087@StatelessCheck 088public class SingleSpaceSeparatorCheck extends AbstractCheck { 089 090 /** 091 * A key is pointing to the warning message text in "messages.properties" 092 * file. 093 */ 094 public static final String MSG_KEY = "single.space.separator"; 095 096 /** Indicates if whitespaces surrounding comments will be ignored. */ 097 private boolean validateComments; 098 099 /** 100 * Sets whether or not to validate surrounding whitespaces at comments. 101 * 102 * @param validateComments {@code true} to validate surrounding whitespaces at comments. 103 */ 104 public void setValidateComments(boolean validateComments) { 105 this.validateComments = validateComments; 106 } 107 108 @Override 109 public int[] getDefaultTokens() { 110 return getRequiredTokens(); 111 } 112 113 @Override 114 public int[] getAcceptableTokens() { 115 return getRequiredTokens(); 116 } 117 118 @Override 119 public int[] getRequiredTokens() { 120 return CommonUtils.EMPTY_INT_ARRAY; 121 } 122 123 // -@cs[SimpleAccessorNameNotation] Overrides method from base class. 124 // Issue: https://github.com/sevntu-checkstyle/sevntu.checkstyle/issues/166 125 @Override 126 public boolean isCommentNodesRequired() { 127 return validateComments; 128 } 129 130 @Override 131 public void beginTree(DetailAST rootAST) { 132 visitEachToken(rootAST); 133 } 134 135 /** 136 * Examines every sibling and child of {@code node} for violations. 137 * 138 * @param node The node to start examining. 139 */ 140 private void visitEachToken(DetailAST node) { 141 DetailAST sibling = node; 142 143 while (sibling != null) { 144 final int columnNo = sibling.getColumnNo() - 1; 145 146 // in such expression: "j =123", placed at the start of the string index of the second 147 // space character will be: 2 = 0(j) + 1(whitespace) + 1(whitespace). It is a minimal 148 // possible index for the second whitespace between non-whitespace characters. 149 final int minSecondWhitespaceColumnNo = 2; 150 151 if (columnNo >= minSecondWhitespaceColumnNo 152 && !isTextSeparatedCorrectlyFromPrevious(getLine(sibling.getLineNo() - 1), 153 columnNo)) { 154 log(sibling.getLineNo(), columnNo, MSG_KEY); 155 } 156 if (sibling.getChildCount() >= 1) { 157 visitEachToken(sibling.getFirstChild()); 158 } 159 160 sibling = sibling.getNextSibling(); 161 } 162 } 163 164 /** 165 * Checks if characters in {@code line} at and around {@code columnNo} has 166 * the correct number of spaces. to return {@code true} the following 167 * conditions must be met:<br /> 168 * - the character at {@code columnNo} is the first in the line.<br /> 169 * - the character at {@code columnNo} is not separated by whitespaces from 170 * the previous non-whitespace character. <br /> 171 * - the character at {@code columnNo} is separated by only one whitespace 172 * from the previous non-whitespace character.<br /> 173 * - {@link #validateComments} is disabled and the previous text is the 174 * end of a block comment. 175 * 176 * @param line The line in the file to examine. 177 * @param columnNo The column position in the {@code line} to examine. 178 * @return {@code true} if the text at {@code columnNo} is separated 179 * correctly from the previous token. 180 */ 181 private boolean isTextSeparatedCorrectlyFromPrevious(String line, int columnNo) { 182 return isSingleSpace(line, columnNo) 183 || !isWhitespace(line, columnNo) 184 || isFirstInLine(line, columnNo) 185 || !validateComments && isBlockCommentEnd(line, columnNo); 186 } 187 188 /** 189 * Checks if the {@code line} at {@code columnNo} is a single space, and not 190 * preceded by another space. 191 * 192 * @param line The line in the file to examine. 193 * @param columnNo The column position in the {@code line} to examine. 194 * @return {@code true} if the character at {@code columnNo} is a space, and 195 * not preceded by another space. 196 */ 197 private static boolean isSingleSpace(String line, int columnNo) { 198 return !isPrecededByMultipleWhitespaces(line, columnNo) 199 && isSpace(line, columnNo); 200 } 201 202 /** 203 * Checks if the {@code line} at {@code columnNo} is a space. 204 * 205 * @param line The line in the file to examine. 206 * @param columnNo The column position in the {@code line} to examine. 207 * @return {@code true} if the character at {@code columnNo} is a space. 208 */ 209 private static boolean isSpace(String line, int columnNo) { 210 return line.charAt(columnNo) == ' '; 211 } 212 213 /** 214 * Checks if the {@code line} at {@code columnNo} is preceded by at least 2 215 * whitespaces. 216 * 217 * @param line The line in the file to examine. 218 * @param columnNo The column position in the {@code line} to examine. 219 * @return {@code true} if there are at least 2 whitespace characters before 220 * {@code columnNo}. 221 */ 222 private static boolean isPrecededByMultipleWhitespaces(String line, int columnNo) { 223 return Character.isWhitespace(line.charAt(columnNo)) 224 && Character.isWhitespace(line.charAt(columnNo - 1)); 225 } 226 227 /** 228 * Checks if the {@code line} at {@code columnNo} is a whitespace character. 229 * 230 * @param line The line in the file to examine. 231 * @param columnNo The column position in the {@code line} to examine. 232 * @return {@code true} if the character at {@code columnNo} is a 233 * whitespace. 234 */ 235 private static boolean isWhitespace(String line, int columnNo) { 236 return Character.isWhitespace(line.charAt(columnNo)); 237 } 238 239 /** 240 * Checks if the {@code line} up to and including {@code columnNo} is all 241 * non-whitespace text encountered. 242 * 243 * @param line The line in the file to examine. 244 * @param columnNo The column position in the {@code line} to examine. 245 * @return {@code true} if the column position is the first non-whitespace 246 * text on the {@code line}. 247 */ 248 private static boolean isFirstInLine(String line, int columnNo) { 249 return CommonUtils.isBlank(line.substring(0, columnNo)); 250 } 251 252 /** 253 * Checks if the {@code line} at {@code columnNo} is the end of a comment, 254 * '*/'. 255 * 256 * @param line The line in the file to examine. 257 * @param columnNo The column position in the {@code line} to examine. 258 * @return {@code true} if the previous text is a end comment block. 259 */ 260 private static boolean isBlockCommentEnd(String line, int columnNo) { 261 return line.substring(0, columnNo).trim().endsWith("*/"); 262 } 263 264}