001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2024 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.api.TokenTypes; 026import com.puppycrawl.tools.checkstyle.utils.CodePointUtil; 027import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 028 029/** 030 * <div> 031 * Checks that there is no whitespace before a token. 032 * More specifically, it checks that it is not preceded with whitespace, 033 * or (if linebreaks are allowed) all characters on the line before are 034 * whitespace. To allow linebreaks before a token, set property 035 * {@code allowLineBreaks} to {@code true}. No check occurs before semicolons in empty 036 * for loop initializers or conditions. 037 * </div> 038 * <ul> 039 * <li> 040 * Property {@code allowLineBreaks} - Control whether whitespace is allowed 041 * if the token is at a linebreak. 042 * Type is {@code boolean}. 043 * Default value is {@code false}. 044 * </li> 045 * <li> 046 * Property {@code tokens} - tokens to check 047 * Type is {@code java.lang.String[]}. 048 * Validation type is {@code tokenSet}. 049 * Default value is: 050 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMMA"> 051 * COMMA</a>, 052 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SEMI"> 053 * SEMI</a>, 054 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#POST_INC"> 055 * POST_INC</a>, 056 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#POST_DEC"> 057 * POST_DEC</a>, 058 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ELLIPSIS"> 059 * ELLIPSIS</a>, 060 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LABELED_STAT"> 061 * LABELED_STAT</a>. 062 * </li> 063 * </ul> 064 * 065 * <p> 066 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 067 * </p> 068 * 069 * <p> 070 * Violation Message Keys: 071 * </p> 072 * <ul> 073 * <li> 074 * {@code ws.preceded} 075 * </li> 076 * </ul> 077 * 078 * @since 3.0 079 */ 080@StatelessCheck 081public class NoWhitespaceBeforeCheck 082 extends AbstractCheck { 083 084 /** 085 * A key is pointing to the warning message text in "messages.properties" 086 * file. 087 */ 088 public static final String MSG_KEY = "ws.preceded"; 089 090 /** Control whether whitespace is allowed if the token is at a linebreak. */ 091 private boolean allowLineBreaks; 092 093 @Override 094 public int[] getDefaultTokens() { 095 return new int[] { 096 TokenTypes.COMMA, 097 TokenTypes.SEMI, 098 TokenTypes.POST_INC, 099 TokenTypes.POST_DEC, 100 TokenTypes.ELLIPSIS, 101 TokenTypes.LABELED_STAT, 102 }; 103 } 104 105 @Override 106 public int[] getAcceptableTokens() { 107 return new int[] { 108 TokenTypes.COMMA, 109 TokenTypes.SEMI, 110 TokenTypes.POST_INC, 111 TokenTypes.POST_DEC, 112 TokenTypes.DOT, 113 TokenTypes.GENERIC_START, 114 TokenTypes.GENERIC_END, 115 TokenTypes.ELLIPSIS, 116 TokenTypes.LABELED_STAT, 117 TokenTypes.METHOD_REF, 118 }; 119 } 120 121 @Override 122 public int[] getRequiredTokens() { 123 return CommonUtil.EMPTY_INT_ARRAY; 124 } 125 126 @Override 127 public void visitToken(DetailAST ast) { 128 final int[] line = getLineCodePoints(ast.getLineNo() - 1); 129 final int columnNoBeforeToken = ast.getColumnNo() - 1; 130 final boolean isFirstToken = columnNoBeforeToken == -1; 131 132 if ((isFirstToken || CommonUtil.isCodePointWhitespace(line, columnNoBeforeToken)) 133 && !isInEmptyForInitializerOrCondition(ast)) { 134 final boolean isViolation = !allowLineBreaks 135 || !isFirstToken 136 && !CodePointUtil.hasWhitespaceBefore(columnNoBeforeToken, line); 137 138 if (isViolation) { 139 log(ast, MSG_KEY, ast.getText()); 140 } 141 } 142 } 143 144 /** 145 * Checks that semicolon is in empty for initializer or condition. 146 * 147 * @param semicolonAst DetailAST of semicolon. 148 * @return true if semicolon is in empty for initializer or condition. 149 */ 150 private static boolean isInEmptyForInitializerOrCondition(DetailAST semicolonAst) { 151 boolean result = false; 152 final DetailAST sibling = semicolonAst.getPreviousSibling(); 153 if (sibling != null 154 && (sibling.getType() == TokenTypes.FOR_INIT 155 || sibling.getType() == TokenTypes.FOR_CONDITION) 156 && !sibling.hasChildren()) { 157 result = true; 158 } 159 return result; 160 } 161 162 /** 163 * Setter to control whether whitespace is allowed if the token is at a linebreak. 164 * 165 * @param allowLineBreaks whether whitespace should be 166 * flagged at line breaks. 167 * @since 3.0 168 */ 169 public void setAllowLineBreaks(boolean allowLineBreaks) { 170 this.allowLineBreaks = allowLineBreaks; 171 } 172 173}