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