001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2019 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 * {@code validateComments} to true. 034 * </p> 035 * 036 * <p> 037 * Setting {@code validateComments} 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 * <ul> 057 * <li> 058 * Property {@code validateComments} - Control whether to validate whitespaces 059 * surrounding comments. 060 * Default value is {@code false}. 061 * </li> 062 * </ul> 063 * <p> 064 * To configure the check: 065 * </p> 066 * 067 * <pre> 068 * <module name="SingleSpaceSeparator"/> 069 * </pre> 070 * 071 * <p> 072 * To configure the check so that it validates comments: 073 * </p> 074 * 075 * <pre> 076 * <module name="SingleSpaceSeparator"> 077 * <property name="validateComments" value="true"/> 078 * </module> 079 * </pre> 080 * 081 * @since 6.19 082 */ 083@StatelessCheck 084public class SingleSpaceSeparatorCheck extends AbstractCheck { 085 086 /** 087 * A key is pointing to the warning message text in "messages.properties" 088 * file. 089 */ 090 public static final String MSG_KEY = "single.space.separator"; 091 092 /** Control whether to validate whitespaces surrounding comments. */ 093 private boolean validateComments; 094 095 /** 096 * Setter to control whether to validate whitespaces surrounding comments. 097 * 098 * @param validateComments {@code true} to validate surrounding whitespaces at comments. 099 */ 100 public void setValidateComments(boolean validateComments) { 101 this.validateComments = validateComments; 102 } 103 104 @Override 105 public int[] getDefaultTokens() { 106 return getRequiredTokens(); 107 } 108 109 @Override 110 public int[] getAcceptableTokens() { 111 return getRequiredTokens(); 112 } 113 114 @Override 115 public int[] getRequiredTokens() { 116 return CommonUtil.EMPTY_INT_ARRAY; 117 } 118 119 // -@cs[SimpleAccessorNameNotation] Overrides method from base class. 120 // Issue: https://github.com/sevntu-checkstyle/sevntu.checkstyle/issues/166 121 @Override 122 public boolean isCommentNodesRequired() { 123 return validateComments; 124 } 125 126 @Override 127 public void beginTree(DetailAST rootAST) { 128 if (rootAST != null) { 129 visitEachToken(rootAST); 130 } 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 do { 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, MSG_KEY); 153 } 154 if (sibling.getChildCount() >= 1) { 155 visitEachToken(sibling.getFirstChild()); 156 } 157 158 sibling = sibling.getNextSibling(); 159 } while (sibling != null); 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 isSpace(line, columnNo) && !Character.isWhitespace(line.charAt(columnNo - 1)); 197 } 198 199 /** 200 * Checks if the {@code line} at {@code columnNo} is a space. 201 * 202 * @param line The line in the file to examine. 203 * @param columnNo The column position in the {@code line} to examine. 204 * @return {@code true} if the character at {@code columnNo} is a space. 205 */ 206 private static boolean isSpace(String line, int columnNo) { 207 return line.charAt(columnNo) == ' '; 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 CommonUtil.isBlank(line.substring(0, columnNo)); 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 247}