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