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