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