001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2016 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 boolean isCommentNodesRequired() { 112 return validateComments; 113 } 114 115 @Override 116 public void beginTree(DetailAST rootAST) { 117 visitEachToken(rootAST); 118 } 119 120 /** 121 * Examines every sibling and child of {@code node} for violations. 122 * 123 * @param node The node to start examining. 124 */ 125 private void visitEachToken(DetailAST node) { 126 DetailAST sibling = node; 127 128 while (sibling != null) { 129 final int columnNo = sibling.getColumnNo() - 1; 130 131 if (columnNo >= 0 132 && !isTextSeparatedCorrectlyFromPrevious(getLine(sibling.getLineNo() - 1), 133 columnNo)) { 134 log(sibling.getLineNo(), columnNo, MSG_KEY); 135 } 136 if (sibling.getChildCount() > 0) { 137 visitEachToken(sibling.getFirstChild()); 138 } 139 140 sibling = sibling.getNextSibling(); 141 } 142 } 143 144 /** 145 * Checks if characters in {@code line} at and around {@code columnNo} has 146 * the correct number of spaces. to return {@code true} the following 147 * conditions must be met:<br /> 148 * - the character at {@code columnNo} is the first in the line.<br /> 149 * - the character at {@code columnNo} is not separated by whitespaces from 150 * the previous non-whitespace character. <br /> 151 * - the character at {@code columnNo} is separated by only one whitespace 152 * from the previous non-whitespace character.<br /> 153 * - {@link #validateComments} is disabled and the previous text is the 154 * end of a block comment. 155 * 156 * @param line The line in the file to examine. 157 * @param columnNo The column position in the {@code line} to examine. 158 * @return {@code true} if the text at {@code columnNo} is separated 159 * correctly from the previous token. 160 */ 161 private boolean isTextSeparatedCorrectlyFromPrevious(String line, int columnNo) { 162 return isSingleSpace(line, columnNo) 163 || !isWhitespace(line, columnNo) 164 || isFirstInLine(line, columnNo) 165 || !validateComments && isBlockCommentEnd(line, columnNo); 166 } 167 168 /** 169 * Checks if the {@code line} at {@code columnNo} is a single space, and not 170 * preceded by another space. 171 * 172 * @param line The line in the file to examine. 173 * @param columnNo The column position in the {@code line} to examine. 174 * @return {@code true} if the character at {@code columnNo} is a space, and 175 * not preceded by another space. 176 */ 177 private static boolean isSingleSpace(String line, int columnNo) { 178 return !isPrecededByMultipleWhitespaces(line, columnNo) 179 && isSpace(line, columnNo); 180 } 181 182 /** 183 * Checks if the {@code line} at {@code columnNo} is a space. 184 * 185 * @param line The line in the file to examine. 186 * @param columnNo The column position in the {@code line} to examine. 187 * @return {@code true} if the character at {@code columnNo} is a space. 188 */ 189 private static boolean isSpace(String line, int columnNo) { 190 return line.charAt(columnNo) == ' '; 191 } 192 193 /** 194 * Checks if the {@code line} at {@code columnNo} is preceded by at least 2 195 * whitespaces. 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 there are at least 2 whitespace characters before 200 * {@code columnNo}. 201 */ 202 private static boolean isPrecededByMultipleWhitespaces(String line, int columnNo) { 203 return columnNo >= 1 204 && Character.isWhitespace(line.charAt(columnNo)) 205 && Character.isWhitespace(line.charAt(columnNo - 1)); 206 } 207 208 /** 209 * Checks if the {@code line} at {@code columnNo} is a whitespace character. 210 * 211 * @param line The line in the file to examine. 212 * @param columnNo The column position in the {@code line} to examine. 213 * @return {@code true} if the character at {@code columnNo} is a 214 * whitespace. 215 */ 216 private static boolean isWhitespace(String line, int columnNo) { 217 return Character.isWhitespace(line.charAt(columnNo)); 218 } 219 220 /** 221 * Checks if the {@code line} up to and including {@code columnNo} is all 222 * non-whitespace text encountered. 223 * 224 * @param line The line in the file to examine. 225 * @param columnNo The column position in the {@code line} to examine. 226 * @return {@code true} if the column position is the first non-whitespace 227 * text on the {@code line}. 228 */ 229 private static boolean isFirstInLine(String line, int columnNo) { 230 return line.substring(0, columnNo + 1).trim().isEmpty(); 231 } 232 233 /** 234 * Checks if the {@code line} at {@code columnNo} is the end of a comment, 235 * '*/'. 236 * 237 * @param line The line in the file to examine. 238 * @param columnNo The column position in the {@code line} to examine. 239 * @return {@code true} if the previous text is a end comment block. 240 */ 241 private static boolean isBlockCommentEnd(String line, int columnNo) { 242 return line.substring(0, columnNo).trim().endsWith("*/"); 243 } 244}