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 java.util.Locale; 023 024import com.puppycrawl.tools.checkstyle.StatelessCheck; 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.TokenTypes; 028import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 029 030/** 031 * <p> 032 * Checks the policy on how to wrap lines on operators. 033 * </p> 034 * <ul> 035 * <li> 036 * Property {@code option} - Specify policy on how to wrap lines. 037 * Default value is {@code nl}. 038 * </li> 039 * <li> 040 * Property {@code tokens} - tokens to check 041 * Default value is: 042 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#QUESTION"> 043 * QUESTION</a>, 044 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COLON"> 045 * COLON</a>, 046 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#EQUAL"> 047 * EQUAL</a>, 048 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NOT_EQUAL"> 049 * NOT_EQUAL</a>, 050 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DIV"> 051 * DIV</a>, 052 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PLUS"> 053 * PLUS</a>, 054 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MINUS"> 055 * MINUS</a>, 056 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STAR"> 057 * STAR</a>, 058 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MOD"> 059 * MOD</a>, 060 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SR"> 061 * SR</a>, 062 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BSR"> 063 * BSR</a>, 064 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#GE"> 065 * GE</a>, 066 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#GT"> 067 * GT</a>, 068 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SL"> 069 * SL</a>, 070 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LE"> 071 * LE</a>, 072 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LT"> 073 * LT</a>, 074 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BXOR"> 075 * BXOR</a>, 076 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BOR"> 077 * BOR</a>, 078 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LOR"> 079 * LOR</a>, 080 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BAND"> 081 * BAND</a>, 082 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAND"> 083 * LAND</a>, 084 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#TYPE_EXTENSION_AND"> 085 * TYPE_EXTENSION_AND</a>, 086 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_INSTANCEOF"> 087 * LITERAL_INSTANCEOF</a>. 088 * </li> 089 * </ul> 090 * <p> 091 * To configure the check: 092 * </p> 093 * <pre> 094 * <module name="OperatorWrap"/> 095 * </pre> 096 * <p> 097 * Example: 098 * </p> 099 * <pre> 100 * class Test { 101 * public static void main(String[] args) { 102 * String s = "Hello" + 103 * "World"; // violation, '+' should be on new line 104 * 105 * if (10 == 106 * 20) { // violation, '==' should be on new line. 107 * // body 108 * } 109 * if (10 110 * == 111 * 20) { // ok 112 * // body 113 * } 114 * 115 * int c = 10 / 116 * 5; // violation, '/' should be on new line. 117 * 118 * int d = c 119 * + 10; // ok 120 * } 121 * 122 * } 123 * </pre> 124 * <p> 125 * To configure the check for assignment operators at the end of a line: 126 * </p> 127 * <pre> 128 * <module name="OperatorWrap"> 129 * <property name="tokens" 130 * value="ASSIGN,DIV_ASSIGN,PLUS_ASSIGN,MINUS_ASSIGN,STAR_ASSIGN,MOD_ASSIGN, 131 * SR_ASSIGN,BSR_ASSIGN,SL_ASSIGN,BXOR_ASSIGN,BOR_ASSIGN,BAND_ASSIGN"/> 132 * <property name="option" value="eol"/> 133 * </module> 134 * </pre> 135 * <p> 136 * Example: 137 * </p> 138 * <pre> 139 * class Test { 140 * public static void main(String[] args) { 141 * int b 142 * = 10; // violation, '=' should be on previous line 143 * int c = 144 * 10; // ok 145 * b 146 * += 10; // violation, '+=' should be on previous line 147 * b += 148 * 10; // ok 149 * c 150 * *= 10; // violation, '*=' should be on previous line 151 * c *= 152 * 10; // ok 153 * c 154 * -= 5; // violation, '-=' should be on previous line 155 * c -= 156 * 5; // ok 157 * c 158 * /= 2; // violation, '/=' should be on previous line 159 * c 160 * %= 1; // violation, '%=' should be on previous line 161 * c 162 * >>= 1; // violation, '>>=' should be on previous line 163 * c 164 * >>>= 1; // violation, '>>>=' should be on previous line 165 * } 166 * public void myFunction() { 167 * c 168 * ^= 1; // violation, '^=' should be on previous line 169 * c 170 * |= 1; // violation, '|=' should be on previous line 171 * c 172 * &=1 ; // violation, '&=' should be on previous line 173 * c 174 * <<= 1; // violation, '<<=' should be on previous line 175 * } 176 * } 177 * </pre> 178 * 179 * @since 3.0 180 */ 181@StatelessCheck 182public class OperatorWrapCheck 183 extends AbstractCheck { 184 185 /** 186 * A key is pointing to the warning message text in "messages.properties" 187 * file. 188 */ 189 public static final String MSG_LINE_NEW = "line.new"; 190 191 /** 192 * A key is pointing to the warning message text in "messages.properties" 193 * file. 194 */ 195 public static final String MSG_LINE_PREVIOUS = "line.previous"; 196 197 /** Specify policy on how to wrap lines. */ 198 private WrapOption option = WrapOption.NL; 199 200 /** 201 * Setter to specify policy on how to wrap lines. 202 * 203 * @param optionStr string to decode option from 204 * @throws IllegalArgumentException if unable to decode 205 */ 206 public void setOption(String optionStr) { 207 option = WrapOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH)); 208 } 209 210 @Override 211 public int[] getDefaultTokens() { 212 return new int[] { 213 TokenTypes.QUESTION, // '?' 214 TokenTypes.COLON, // ':' (not reported for a case) 215 TokenTypes.EQUAL, // "==" 216 TokenTypes.NOT_EQUAL, // "!=" 217 TokenTypes.DIV, // '/' 218 TokenTypes.PLUS, // '+' (unary plus is UNARY_PLUS) 219 TokenTypes.MINUS, // '-' (unary minus is UNARY_MINUS) 220 TokenTypes.STAR, // '*' 221 TokenTypes.MOD, // '%' 222 TokenTypes.SR, // ">>" 223 TokenTypes.BSR, // ">>>" 224 TokenTypes.GE, // ">=" 225 TokenTypes.GT, // ">" 226 TokenTypes.SL, // "<<" 227 TokenTypes.LE, // "<=" 228 TokenTypes.LT, // '<' 229 TokenTypes.BXOR, // '^' 230 TokenTypes.BOR, // '|' 231 TokenTypes.LOR, // "||" 232 TokenTypes.BAND, // '&' 233 TokenTypes.LAND, // "&&" 234 TokenTypes.TYPE_EXTENSION_AND, 235 TokenTypes.LITERAL_INSTANCEOF, 236 }; 237 } 238 239 @Override 240 public int[] getAcceptableTokens() { 241 return new int[] { 242 TokenTypes.QUESTION, // '?' 243 TokenTypes.COLON, // ':' (not reported for a case) 244 TokenTypes.EQUAL, // "==" 245 TokenTypes.NOT_EQUAL, // "!=" 246 TokenTypes.DIV, // '/' 247 TokenTypes.PLUS, // '+' (unary plus is UNARY_PLUS) 248 TokenTypes.MINUS, // '-' (unary minus is UNARY_MINUS) 249 TokenTypes.STAR, // '*' 250 TokenTypes.MOD, // '%' 251 TokenTypes.SR, // ">>" 252 TokenTypes.BSR, // ">>>" 253 TokenTypes.GE, // ">=" 254 TokenTypes.GT, // ">" 255 TokenTypes.SL, // "<<" 256 TokenTypes.LE, // "<=" 257 TokenTypes.LT, // '<' 258 TokenTypes.BXOR, // '^' 259 TokenTypes.BOR, // '|' 260 TokenTypes.LOR, // "||" 261 TokenTypes.BAND, // '&' 262 TokenTypes.LAND, // "&&" 263 TokenTypes.LITERAL_INSTANCEOF, 264 TokenTypes.TYPE_EXTENSION_AND, 265 TokenTypes.ASSIGN, // '=' 266 TokenTypes.DIV_ASSIGN, // "/=" 267 TokenTypes.PLUS_ASSIGN, // "+=" 268 TokenTypes.MINUS_ASSIGN, // "-=" 269 TokenTypes.STAR_ASSIGN, // "*=" 270 TokenTypes.MOD_ASSIGN, // "%=" 271 TokenTypes.SR_ASSIGN, // ">>=" 272 TokenTypes.BSR_ASSIGN, // ">>>=" 273 TokenTypes.SL_ASSIGN, // "<<=" 274 TokenTypes.BXOR_ASSIGN, // "^=" 275 TokenTypes.BOR_ASSIGN, // "|=" 276 TokenTypes.BAND_ASSIGN, // "&=" 277 TokenTypes.METHOD_REF, // "::" 278 }; 279 } 280 281 @Override 282 public int[] getRequiredTokens() { 283 return CommonUtil.EMPTY_INT_ARRAY; 284 } 285 286 @Override 287 public void visitToken(DetailAST ast) { 288 final DetailAST parent = ast.getParent(); 289 // we do not want to check colon for cases and defaults 290 if (parent.getType() != TokenTypes.LITERAL_DEFAULT 291 && parent.getType() != TokenTypes.LITERAL_CASE) { 292 final String text = ast.getText(); 293 final int colNo = ast.getColumnNo(); 294 final int lineNo = ast.getLineNo(); 295 final String currentLine = getLine(lineNo - 1); 296 297 // Check if rest of line is whitespace, and not just the operator 298 // by itself. This last bit is to handle the operator on a line by 299 // itself. 300 if (option == WrapOption.NL 301 && !text.equals(currentLine.trim()) 302 && CommonUtil.isBlank(currentLine.substring(colNo + text.length()))) { 303 log(ast, MSG_LINE_NEW, text); 304 } 305 else if (option == WrapOption.EOL 306 && CommonUtil.hasWhitespaceBefore(colNo - 1, currentLine)) { 307 log(ast, MSG_LINE_PREVIOUS, text); 308 } 309 } 310 } 311 312}