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