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.Arrays; 023 024import com.puppycrawl.tools.checkstyle.api.DetailAST; 025import com.puppycrawl.tools.checkstyle.api.TokenTypes; 026import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 027import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 028 029/** 030 * <p> 031 * Checks the policy on the padding of parentheses; that is whether a space is required 032 * after a left parenthesis and before a right parenthesis, or such spaces are 033 * forbidden. No check occurs at the right parenthesis after an empty for 034 * iterator, at the left parenthesis before an empty for initialization, or at 035 * the right parenthesis of a try-with-resources resource specification where 036 * the last resource variable has a trailing semi-colon. 037 * Use Check <a href="https://checkstyle.org/config_whitespace.html#EmptyForIteratorPad"> 038 * EmptyForIteratorPad</a> to validate empty for iterators and 039 * <a href="https://checkstyle.org/config_whitespace.html#EmptyForInitializerPad"> 040 * EmptyForInitializerPad</a> to validate empty for initializers. 041 * Typecasts are also not checked, as there is 042 * <a href="https://checkstyle.org/config_whitespace.html#TypecastParenPad"> 043 * TypecastParenPad</a> to validate them. 044 * </p> 045 * <ul> 046 * <li> 047 * Property {@code option} - Specify policy on how to pad parentheses. 048 * Default value is {@code nospace}. 049 * </li> 050 * <li> 051 * Property {@code tokens} - tokens to check 052 * Default value is: 053 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION"> 054 * ANNOTATION</a>, 055 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_FIELD_DEF"> 056 * ANNOTATION_FIELD_DEF</a>, 057 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_CALL"> 058 * CTOR_CALL</a>, 059 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF"> 060 * CTOR_DEF</a>, 061 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DOT"> 062 * DOT</a>, 063 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_CONSTANT_DEF"> 064 * ENUM_CONSTANT_DEF</a>, 065 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#EXPR"> 066 * EXPR</a>, 067 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CATCH"> 068 * LITERAL_CATCH</a>, 069 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO"> 070 * LITERAL_DO</a>, 071 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FOR"> 072 * LITERAL_FOR</a>, 073 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_IF"> 074 * LITERAL_IF</a>, 075 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_NEW"> 076 * LITERAL_NEW</a>, 077 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SWITCH"> 078 * LITERAL_SWITCH</a>, 079 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SYNCHRONIZED"> 080 * LITERAL_SYNCHRONIZED</a>, 081 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHILE"> 082 * LITERAL_WHILE</a>, 083 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_CALL"> 084 * METHOD_CALL</a>, 085 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 086 * METHOD_DEF</a>, 087 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#QUESTION"> 088 * QUESTION</a>, 089 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RESOURCE_SPECIFICATION"> 090 * RESOURCE_SPECIFICATION</a>, 091 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SUPER_CTOR_CALL"> 092 * SUPER_CTOR_CALL</a>, 093 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAMBDA"> 094 * LAMBDA</a>. 095 * </li> 096 * </ul> 097 * <p> 098 * To configure the check: 099 * </p> 100 * <pre> 101 * <module name="ParenPad"/> 102 * </pre> 103 * <p> 104 * Example: 105 * </p> 106 * <pre> 107 * class Foo { 108 * 109 * int n; 110 * 111 * public void fun() { // OK 112 * bar( 1); // violation, space after left parenthesis 113 * } 114 * 115 * public void bar(int k ) { // violation, space before right parenthesis 116 * while (k > 0) { // OK 117 * } 118 * 119 * Test obj = new Test(k); // OK 120 * } 121 * 122 * public void fun2() { // OK 123 * switch( n) { // violation, space after left parenthesis 124 * case 2: 125 * bar(n); // OK 126 * default: 127 * break; 128 * } 129 * } 130 * 131 * } 132 * </pre> 133 * <p> 134 * To configure the check to require spaces for the 135 * parentheses of constructor, method, and super constructor calls: 136 * </p> 137 * <pre> 138 * <module name="ParenPad"> 139 * <property name="tokens" value="LITERAL_FOR, LITERAL_CATCH, 140 * SUPER_CTOR_CALL"/> 141 * <property name="option" value="space"/> 142 * </module> 143 * </pre> 144 * <p> 145 * Example: 146 * </p> 147 * <pre> 148 * class Foo { 149 * 150 * int x; 151 * 152 * public Foo(int n) { 153 * } 154 * 155 * public void fun() { 156 * try { 157 * System.out.println(x); 158 * } catch( IOException e) { // violation, no space before right parenthesis 159 * } catch( Exception e ) { // OK 160 * } 161 * 162 * for ( int i = 0; i < x; i++ ) { // OK 163 * } 164 * } 165 * 166 * } 167 * 168 * class Bar extends Foo { 169 * 170 * public Bar() { 171 * super(1 ); // violation, no space after left parenthesis 172 * } 173 * 174 * public Bar(int k) { 175 * super( k ); // OK 176 * 177 * for ( int i = 0; i < k; i++) { // violation, no space before right parenthesis 178 * } 179 * } 180 * 181 * } 182 * </pre> 183 * <p> 184 * The following cases are not checked: 185 * </p> 186 * <pre> 187 * for ( ; i < j; i++, j--) // no check after left parenthesis 188 * for (Iterator it = xs.iterator(); it.hasNext(); ) // no check before right parenthesis 189 * try (Closeable resource = acquire(); ) // no check before right parenthesis 190 * </pre> 191 * 192 * @since 3.0 193 */ 194public class ParenPadCheck extends AbstractParenPadCheck { 195 196 /** 197 * The array of Acceptable Tokens. 198 */ 199 private final int[] acceptableTokens; 200 201 /** 202 * Initializes and sorts acceptableTokens to make binary search over it possible. 203 */ 204 public ParenPadCheck() { 205 acceptableTokens = makeAcceptableTokens(); 206 Arrays.sort(acceptableTokens); 207 } 208 209 @Override 210 public int[] getDefaultTokens() { 211 return makeAcceptableTokens(); 212 } 213 214 @Override 215 public int[] getAcceptableTokens() { 216 return makeAcceptableTokens(); 217 } 218 219 @Override 220 public int[] getRequiredTokens() { 221 return CommonUtil.EMPTY_INT_ARRAY; 222 } 223 224 @Override 225 public void visitToken(DetailAST ast) { 226 switch (ast.getType()) { 227 case TokenTypes.METHOD_CALL: 228 processLeft(ast); 229 processRight(ast.findFirstToken(TokenTypes.RPAREN)); 230 break; 231 case TokenTypes.DOT: 232 case TokenTypes.EXPR: 233 case TokenTypes.QUESTION: 234 processExpression(ast); 235 break; 236 case TokenTypes.LITERAL_FOR: 237 visitLiteralFor(ast); 238 break; 239 case TokenTypes.ANNOTATION: 240 case TokenTypes.ENUM_CONSTANT_DEF: 241 case TokenTypes.LITERAL_NEW: 242 case TokenTypes.LITERAL_SYNCHRONIZED: 243 case TokenTypes.LAMBDA: 244 visitTokenWithOptionalParentheses(ast); 245 break; 246 case TokenTypes.RESOURCE_SPECIFICATION: 247 visitResourceSpecification(ast); 248 break; 249 default: 250 processLeft(ast.findFirstToken(TokenTypes.LPAREN)); 251 processRight(ast.findFirstToken(TokenTypes.RPAREN)); 252 } 253 } 254 255 /** 256 * Checks parens in token which may not contain parens, e.g. 257 * {@link TokenTypes#ENUM_CONSTANT_DEF}, {@link TokenTypes#ANNOTATION} 258 * {@link TokenTypes#LITERAL_SYNCHRONIZED}, {@link TokenTypes#LITERAL_NEW} and 259 * {@link TokenTypes#LAMBDA}. 260 * 261 * @param ast the token to check. 262 */ 263 private void visitTokenWithOptionalParentheses(DetailAST ast) { 264 final DetailAST parenAst = ast.findFirstToken(TokenTypes.LPAREN); 265 if (parenAst != null) { 266 processLeft(parenAst); 267 processRight(ast.findFirstToken(TokenTypes.RPAREN)); 268 } 269 } 270 271 /** 272 * Checks parens in {@link TokenTypes#RESOURCE_SPECIFICATION}. 273 * 274 * @param ast the token to check. 275 */ 276 private void visitResourceSpecification(DetailAST ast) { 277 processLeft(ast.findFirstToken(TokenTypes.LPAREN)); 278 final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN); 279 if (!hasPrecedingSemiColon(rparen)) { 280 processRight(rparen); 281 } 282 } 283 284 /** 285 * Checks that a token is preceded by a semi-colon. 286 * 287 * @param ast the token to check 288 * @return whether a token is preceded by a semi-colon 289 */ 290 private static boolean hasPrecedingSemiColon(DetailAST ast) { 291 return ast.getPreviousSibling().getType() == TokenTypes.SEMI; 292 } 293 294 /** 295 * Checks parens in {@link TokenTypes#LITERAL_FOR}. 296 * 297 * @param ast the token to check. 298 */ 299 private void visitLiteralFor(DetailAST ast) { 300 final DetailAST lparen = ast.findFirstToken(TokenTypes.LPAREN); 301 if (!isPrecedingEmptyForInit(lparen)) { 302 processLeft(lparen); 303 } 304 final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN); 305 if (!isFollowsEmptyForIterator(rparen)) { 306 processRight(rparen); 307 } 308 } 309 310 /** 311 * Checks parens inside {@link TokenTypes#EXPR}, {@link TokenTypes#QUESTION} 312 * and {@link TokenTypes#METHOD_CALL}. 313 * 314 * @param ast the token to check. 315 */ 316 private void processExpression(DetailAST ast) { 317 DetailAST childAst = ast.getFirstChild(); 318 while (childAst != null) { 319 if (childAst.getType() == TokenTypes.LPAREN) { 320 processLeft(childAst); 321 } 322 else if (childAst.getType() == TokenTypes.RPAREN && !isInTypecast(childAst)) { 323 processRight(childAst); 324 } 325 else if (!isAcceptableToken(childAst)) { 326 // Traverse all subtree tokens which will never be configured 327 // to be launched in visitToken() 328 processExpression(childAst); 329 } 330 childAst = childAst.getNextSibling(); 331 } 332 } 333 334 /** 335 * Checks whether AcceptableTokens contains the given ast. 336 * 337 * @param ast the token to check. 338 * @return true if the ast is in AcceptableTokens. 339 */ 340 private boolean isAcceptableToken(DetailAST ast) { 341 boolean result = false; 342 if (Arrays.binarySearch(acceptableTokens, ast.getType()) >= 0) { 343 result = true; 344 } 345 return result; 346 } 347 348 /** 349 * Returns array of acceptable tokens. 350 * 351 * @return acceptableTokens. 352 */ 353 private static int[] makeAcceptableTokens() { 354 return new int[] {TokenTypes.ANNOTATION, 355 TokenTypes.ANNOTATION_FIELD_DEF, 356 TokenTypes.CTOR_CALL, 357 TokenTypes.CTOR_DEF, 358 TokenTypes.DOT, 359 TokenTypes.ENUM_CONSTANT_DEF, 360 TokenTypes.EXPR, 361 TokenTypes.LITERAL_CATCH, 362 TokenTypes.LITERAL_DO, 363 TokenTypes.LITERAL_FOR, 364 TokenTypes.LITERAL_IF, 365 TokenTypes.LITERAL_NEW, 366 TokenTypes.LITERAL_SWITCH, 367 TokenTypes.LITERAL_SYNCHRONIZED, 368 TokenTypes.LITERAL_WHILE, 369 TokenTypes.METHOD_CALL, 370 TokenTypes.METHOD_DEF, 371 TokenTypes.QUESTION, 372 TokenTypes.RESOURCE_SPECIFICATION, 373 TokenTypes.SUPER_CTOR_CALL, 374 TokenTypes.LAMBDA, 375 }; 376 } 377 378 /** 379 * Checks whether {@link TokenTypes#RPAREN} is a closing paren 380 * of a {@link TokenTypes#TYPECAST}. 381 * 382 * @param ast of a {@link TokenTypes#RPAREN} to check. 383 * @return true if ast is a closing paren of a {@link TokenTypes#TYPECAST}. 384 */ 385 private static boolean isInTypecast(DetailAST ast) { 386 boolean result = false; 387 if (ast.getParent().getType() == TokenTypes.TYPECAST) { 388 final DetailAST firstRparen = ast.getParent().findFirstToken(TokenTypes.RPAREN); 389 if (TokenUtil.areOnSameLine(firstRparen, ast) 390 && firstRparen.getColumnNo() == ast.getColumnNo()) { 391 result = true; 392 } 393 } 394 return result; 395 } 396 397 /** 398 * Checks that a token follows an empty for iterator. 399 * 400 * @param ast the token to check 401 * @return whether a token follows an empty for iterator 402 */ 403 private static boolean isFollowsEmptyForIterator(DetailAST ast) { 404 boolean result = false; 405 final DetailAST parent = ast.getParent(); 406 // Only traditional for statements are examined, not for-each statements 407 if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) { 408 final DetailAST forIterator = 409 parent.findFirstToken(TokenTypes.FOR_ITERATOR); 410 result = !forIterator.hasChildren(); 411 } 412 return result; 413 } 414 415 /** 416 * Checks that a token precedes an empty for initializer. 417 * 418 * @param ast the token to check 419 * @return whether a token precedes an empty for initializer 420 */ 421 private static boolean isPrecedingEmptyForInit(DetailAST ast) { 422 boolean result = false; 423 final DetailAST parent = ast.getParent(); 424 // Only traditional for statements are examined, not for-each statements 425 if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) { 426 final DetailAST forIterator = 427 parent.findFirstToken(TokenTypes.FOR_INIT); 428 result = !forIterator.hasChildren(); 429 } 430 return result; 431 } 432 433}