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