1 //////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code for adherence to a set of rules. 3 // Copyright (C) 2001-2019 the original author or authors. 4 // 5 // This library is free software; you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 2.1 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library; if not, write to the Free Software 17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 //////////////////////////////////////////////////////////////////////////////// 19 20 package com.puppycrawl.tools.checkstyle.checks.whitespace; 21 22 import com.puppycrawl.tools.checkstyle.StatelessCheck; 23 import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 24 import com.puppycrawl.tools.checkstyle.api.DetailAST; 25 import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 26 27 /** 28 * <p> 29 * Checks that non-whitespace characters are separated by no more than one 30 * whitespace. Separating characters by tabs or multiple spaces will be 31 * reported. Currently the check doesn't permit horizontal alignment. To inspect 32 * whitespaces before and after comments, set the property 33 * <b>validateComments</b> to true. 34 * </p> 35 * 36 * <p> 37 * Setting <b>validateComments</b> to false will ignore cases like: 38 * </p> 39 * 40 * <pre> 41 * int i; // Multiple whitespaces before comment tokens will be ignored. 42 * private void foo(int /* whitespaces before and after block-comments will be 43 * ignored */ i) { 44 * </pre> 45 * 46 * <p> 47 * Sometimes, users like to space similar items on different lines to the same 48 * column position for easier reading. This feature isn't supported by this 49 * check, so both braces in the following case will be reported as violations. 50 * </p> 51 * 52 * <pre> 53 * public long toNanos(long d) { return d; } // 2 violations 54 * public long toMicros(long d) { return d / (C1 / C0); } 55 * </pre> 56 * 57 * <p> 58 * Check have following options: 59 * </p> 60 * 61 * <ul> 62 * <li>validateComments - Boolean when set to {@code true}, whitespaces 63 * surrounding comments will be ignored. Default value is {@code false}.</li> 64 * </ul> 65 * 66 * <p> 67 * To configure the check: 68 * </p> 69 * 70 * <pre> 71 * <module name="SingleSpaceSeparator"/> 72 * </pre> 73 * 74 * <p> 75 * To configure the check so that it validates comments: 76 * </p> 77 * 78 * <pre> 79 * <module name="SingleSpaceSeparator"> 80 * <property name="validateComments" value="true"/> 81 * </module> 82 * </pre> 83 * 84 */ 85 @StatelessCheck 86 public class SingleSpaceSeparatorCheck extends AbstractCheck { 87 88 /** 89 * A key is pointing to the warning message text in "messages.properties" 90 * file. 91 */ 92 public static final String MSG_KEY = "single.space.separator"; 93 94 /** Indicates if whitespaces surrounding comments will be ignored. */ 95 private boolean validateComments; 96 97 /** 98 * Sets whether or not to validate surrounding whitespaces at comments. 99 * 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 if (rootAST != null) { 131 visitEachToken(rootAST); 132 } 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 do { 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, MSG_KEY); 155 } 156 if (sibling.getChildCount() >= 1) { 157 visitEachToken(sibling.getFirstChild()); 158 } 159 160 sibling = sibling.getNextSibling(); 161 } while (sibling != null); 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 isSpace(line, columnNo) && !Character.isWhitespace(line.charAt(columnNo - 1)); 199 } 200 201 /** 202 * Checks if the {@code line} at {@code columnNo} is a space. 203 * 204 * @param line The line in the file to examine. 205 * @param columnNo The column position in the {@code line} to examine. 206 * @return {@code true} if the character at {@code columnNo} is a space. 207 */ 208 private static boolean isSpace(String line, int columnNo) { 209 return line.charAt(columnNo) == ' '; 210 } 211 212 /** 213 * Checks if the {@code line} at {@code columnNo} is a whitespace character. 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 the character at {@code columnNo} is a 218 * whitespace. 219 */ 220 private static boolean isWhitespace(String line, int columnNo) { 221 return Character.isWhitespace(line.charAt(columnNo)); 222 } 223 224 /** 225 * Checks if the {@code line} up to and including {@code columnNo} is all 226 * non-whitespace text encountered. 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 column position is the first non-whitespace 231 * text on the {@code line}. 232 */ 233 private static boolean isFirstInLine(String line, int columnNo) { 234 return CommonUtil.isBlank(line.substring(0, columnNo)); 235 } 236 237 /** 238 * Checks if the {@code line} at {@code columnNo} is the end of a comment, 239 * '*/'. 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 previous text is a end comment block. 244 */ 245 private static boolean isBlockCommentEnd(String line, int columnNo) { 246 return line.substring(0, columnNo).trim().endsWith("*/"); 247 } 248 249 }