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 * <p> 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 * </p> 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 * <p> 065 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 066 * </p> 067 * <p> 068 * Violation Message Keys: 069 * </p> 070 * <ul> 071 * <li> 072 * {@code ws.preceded} 073 * </li> 074 * </ul> 075 * 076 * @since 3.0 077 */ 078@StatelessCheck 079public class NoWhitespaceBeforeCheck 080 extends AbstractCheck { 081 082 /** 083 * A key is pointing to the warning message text in "messages.properties" 084 * file. 085 */ 086 public static final String MSG_KEY = "ws.preceded"; 087 088 /** Control whether whitespace is allowed if the token is at a linebreak. */ 089 private boolean allowLineBreaks; 090 091 @Override 092 public int[] getDefaultTokens() { 093 return new int[] { 094 TokenTypes.COMMA, 095 TokenTypes.SEMI, 096 TokenTypes.POST_INC, 097 TokenTypes.POST_DEC, 098 TokenTypes.ELLIPSIS, 099 TokenTypes.LABELED_STAT, 100 }; 101 } 102 103 @Override 104 public int[] getAcceptableTokens() { 105 return new int[] { 106 TokenTypes.COMMA, 107 TokenTypes.SEMI, 108 TokenTypes.POST_INC, 109 TokenTypes.POST_DEC, 110 TokenTypes.DOT, 111 TokenTypes.GENERIC_START, 112 TokenTypes.GENERIC_END, 113 TokenTypes.ELLIPSIS, 114 TokenTypes.LABELED_STAT, 115 TokenTypes.METHOD_REF, 116 }; 117 } 118 119 @Override 120 public int[] getRequiredTokens() { 121 return CommonUtil.EMPTY_INT_ARRAY; 122 } 123 124 @Override 125 public void visitToken(DetailAST ast) { 126 final int[] line = getLineCodePoints(ast.getLineNo() - 1); 127 final int columnNoBeforeToken = ast.getColumnNo() - 1; 128 final boolean isFirstToken = columnNoBeforeToken == -1; 129 130 if ((isFirstToken || CommonUtil.isCodePointWhitespace(line, columnNoBeforeToken)) 131 && !isInEmptyForInitializerOrCondition(ast)) { 132 final boolean isViolation = !allowLineBreaks 133 || !isFirstToken 134 && !CodePointUtil.hasWhitespaceBefore(columnNoBeforeToken, line); 135 136 if (isViolation) { 137 log(ast, MSG_KEY, ast.getText()); 138 } 139 } 140 } 141 142 /** 143 * Checks that semicolon is in empty for initializer or condition. 144 * 145 * @param semicolonAst DetailAST of semicolon. 146 * @return true if semicolon is in empty for initializer or condition. 147 */ 148 private static boolean isInEmptyForInitializerOrCondition(DetailAST semicolonAst) { 149 boolean result = false; 150 final DetailAST sibling = semicolonAst.getPreviousSibling(); 151 if (sibling != null 152 && (sibling.getType() == TokenTypes.FOR_INIT 153 || sibling.getType() == TokenTypes.FOR_CONDITION) 154 && !sibling.hasChildren()) { 155 result = true; 156 } 157 return result; 158 } 159 160 /** 161 * Setter to control whether whitespace is allowed if the token is at a linebreak. 162 * 163 * @param allowLineBreaks whether whitespace should be 164 * flagged at line breaks. 165 * @since 3.0 166 */ 167 public void setAllowLineBreaks(boolean allowLineBreaks) { 168 this.allowLineBreaks = allowLineBreaks; 169 } 170 171}