001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2017 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.ArrayList; 023import java.util.List; 024 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.FileContents; 028import com.puppycrawl.tools.checkstyle.api.TokenTypes; 029import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 030import com.puppycrawl.tools.checkstyle.utils.JavadocUtils; 031 032/** 033 * Checks for empty line separators after header, package, all import declarations, 034 * fields, constructors, methods, nested classes, 035 * static initializers and instance initializers. 036 * 037 * <p> By default the check will check the following statements: 038 * {@link TokenTypes#PACKAGE_DEF PACKAGE_DEF}, 039 * {@link TokenTypes#IMPORT IMPORT}, 040 * {@link TokenTypes#CLASS_DEF CLASS_DEF}, 041 * {@link TokenTypes#INTERFACE_DEF INTERFACE_DEF}, 042 * {@link TokenTypes#STATIC_INIT STATIC_INIT}, 043 * {@link TokenTypes#INSTANCE_INIT INSTANCE_INIT}, 044 * {@link TokenTypes#METHOD_DEF METHOD_DEF}, 045 * {@link TokenTypes#CTOR_DEF CTOR_DEF}, 046 * {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF}. 047 * </p> 048 * 049 * <p> 050 * Example of declarations without empty line separator: 051 * </p> 052 * 053 * <pre> 054 * /////////////////////////////////////////////////// 055 * //HEADER 056 * /////////////////////////////////////////////////// 057 * package com.puppycrawl.tools.checkstyle.whitespace; 058 * import java.io.Serializable; 059 * class Foo 060 * { 061 * public static final int FOO_CONST = 1; 062 * public void foo() {} //should be separated from previous statement. 063 * } 064 * </pre> 065 * 066 * <p> An example of how to configure the check with default parameters is: 067 * </p> 068 * 069 * <pre> 070 * <module name="EmptyLineSeparator"/> 071 * </pre> 072 * 073 * <p> 074 * Example of declarations with empty line separator 075 * that is expected by the Check by default: 076 * </p> 077 * 078 * <pre> 079 * /////////////////////////////////////////////////// 080 * //HEADER 081 * /////////////////////////////////////////////////// 082 * 083 * package com.puppycrawl.tools.checkstyle.whitespace; 084 * 085 * import java.io.Serializable; 086 * 087 * class Foo 088 * { 089 * public static final int FOO_CONST = 1; 090 * 091 * public void foo() {} 092 * } 093 * </pre> 094 * <p> An example how to check empty line after 095 * {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF} and 096 * {@link TokenTypes#METHOD_DEF METHOD_DEF}: 097 * </p> 098 * 099 * <pre> 100 * <module name="EmptyLineSeparator"> 101 * <property name="tokens" value="VARIABLE_DEF, METHOD_DEF"/> 102 * </module> 103 * </pre> 104 * 105 * <p> 106 * An example how to allow no empty line between fields: 107 * </p> 108 * <pre> 109 * <module name="EmptyLineSeparator"> 110 * <property name="allowNoEmptyLineBetweenFields" value="true"/> 111 * </module> 112 * </pre> 113 * 114 * <p> 115 * Example of declarations with multiple empty lines between class members (allowed by default): 116 * </p> 117 * 118 * <pre> 119 * /////////////////////////////////////////////////// 120 * //HEADER 121 * /////////////////////////////////////////////////// 122 * 123 * 124 * package com.puppycrawl.tools.checkstyle.whitespace; 125 * 126 * 127 * 128 * import java.io.Serializable; 129 * 130 * 131 * class Foo 132 * { 133 * public static final int FOO_CONST = 1; 134 * 135 * 136 * 137 * public void foo() {} 138 * } 139 * </pre> 140 * <p> 141 * An example how to disallow multiple empty lines between class members: 142 * </p> 143 * <pre> 144 * <module name="EmptyLineSeparator"> 145 * <property name="allowMultipleEmptyLines" value="false"/> 146 * </module> 147 * </pre> 148 * 149 * <p> 150 * An example how to disallow multiple empty line inside methods, constructors, etc.: 151 * </p> 152 * <pre> 153 * <module name="EmptyLineSeparator"> 154 * <property name="allowMultipleEmptyLinesInsideClassMembers" value="false"/> 155 * </module> 156 * </pre> 157 * 158 * <p> The check is valid only for statements that have body: 159 * {@link TokenTypes#CLASS_DEF}, 160 * {@link TokenTypes#INTERFACE_DEF}, 161 * {@link TokenTypes#ENUM_DEF}, 162 * {@link TokenTypes#STATIC_INIT}, 163 * {@link TokenTypes#INSTANCE_INIT}, 164 * {@link TokenTypes#METHOD_DEF}, 165 * {@link TokenTypes#CTOR_DEF} 166 * </p> 167 * <p> 168 * Example of declarations with multiple empty lines inside method: 169 * </p> 170 * 171 * <pre> 172 * /////////////////////////////////////////////////// 173 * //HEADER 174 * /////////////////////////////////////////////////// 175 * 176 * package com.puppycrawl.tools.checkstyle.whitespace; 177 * 178 * class Foo 179 * { 180 * 181 * public void foo() { 182 * 183 * 184 * System.out.println(1); // violation since method has 2 empty lines subsequently 185 * } 186 * } 187 * </pre> 188 * @author maxvetrenko 189 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a> 190 */ 191public class EmptyLineSeparatorCheck extends AbstractCheck { 192 193 /** 194 * A key is pointing to the warning message empty.line.separator in "messages.properties" 195 * file. 196 */ 197 public static final String MSG_SHOULD_BE_SEPARATED = "empty.line.separator"; 198 199 /** 200 * A key is pointing to the warning message empty.line.separator.multiple.lines 201 * in "messages.properties" 202 * file. 203 */ 204 public static final String MSG_MULTIPLE_LINES = "empty.line.separator.multiple.lines"; 205 206 /** 207 * A key is pointing to the warning message empty.line.separator.lines.after 208 * in "messages.properties" file. 209 */ 210 public static final String MSG_MULTIPLE_LINES_AFTER = 211 "empty.line.separator.multiple.lines.after"; 212 213 /** 214 * A key is pointing to the warning message empty.line.separator.multiple.lines.inside 215 * in "messages.properties" file. 216 */ 217 public static final String MSG_MULTIPLE_LINES_INSIDE = 218 "empty.line.separator.multiple.lines.inside"; 219 220 /** Allows no empty line between fields. */ 221 private boolean allowNoEmptyLineBetweenFields; 222 223 /** Allows multiple empty lines between class members. */ 224 private boolean allowMultipleEmptyLines = true; 225 226 /** Allows multiple empty lines inside class members. */ 227 private boolean allowMultipleEmptyLinesInsideClassMembers = true; 228 229 /** 230 * Allow no empty line between fields. 231 * @param allow 232 * User's value. 233 */ 234 public final void setAllowNoEmptyLineBetweenFields(boolean allow) { 235 allowNoEmptyLineBetweenFields = allow; 236 } 237 238 /** 239 * Allow multiple empty lines between class members. 240 * @param allow User's value. 241 */ 242 public void setAllowMultipleEmptyLines(boolean allow) { 243 allowMultipleEmptyLines = allow; 244 } 245 246 /** 247 * Allow multiple empty lines inside class members. 248 * @param allow User's value. 249 */ 250 public void setAllowMultipleEmptyLinesInsideClassMembers(boolean allow) { 251 allowMultipleEmptyLinesInsideClassMembers = allow; 252 } 253 254 @Override 255 public boolean isCommentNodesRequired() { 256 return true; 257 } 258 259 @Override 260 public int[] getDefaultTokens() { 261 return getAcceptableTokens(); 262 } 263 264 @Override 265 public int[] getAcceptableTokens() { 266 return new int[] { 267 TokenTypes.PACKAGE_DEF, 268 TokenTypes.IMPORT, 269 TokenTypes.CLASS_DEF, 270 TokenTypes.INTERFACE_DEF, 271 TokenTypes.ENUM_DEF, 272 TokenTypes.STATIC_INIT, 273 TokenTypes.INSTANCE_INIT, 274 TokenTypes.METHOD_DEF, 275 TokenTypes.CTOR_DEF, 276 TokenTypes.VARIABLE_DEF, 277 }; 278 } 279 280 @Override 281 public int[] getRequiredTokens() { 282 return CommonUtils.EMPTY_INT_ARRAY; 283 } 284 285 @Override 286 public void visitToken(DetailAST ast) { 287 if (hasMultipleLinesBefore(ast)) { 288 log(ast.getLineNo(), MSG_MULTIPLE_LINES, ast.getText()); 289 } 290 if (!allowMultipleEmptyLinesInsideClassMembers) { 291 processMultipleLinesInside(ast); 292 } 293 294 DetailAST nextToken = ast.getNextSibling(); 295 while (nextToken != null && isComment(nextToken)) { 296 nextToken = nextToken.getNextSibling(); 297 } 298 if (nextToken != null) { 299 final int astType = ast.getType(); 300 switch (astType) { 301 case TokenTypes.VARIABLE_DEF: 302 processVariableDef(ast, nextToken); 303 break; 304 case TokenTypes.IMPORT: 305 processImport(ast, nextToken, astType); 306 break; 307 case TokenTypes.PACKAGE_DEF: 308 processPackage(ast, nextToken); 309 break; 310 default: 311 if (nextToken.getType() == TokenTypes.RCURLY) { 312 if (hasNotAllowedTwoEmptyLinesBefore(nextToken)) { 313 log(ast.getLineNo(), MSG_MULTIPLE_LINES_AFTER, ast.getText()); 314 } 315 } 316 else if (!hasEmptyLineAfter(ast)) { 317 log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, 318 nextToken.getText()); 319 } 320 } 321 } 322 } 323 324 /** 325 * Log violation in case there are multiple empty lines inside constructor, 326 * initialization block or method. 327 * @param ast the ast to check. 328 */ 329 private void processMultipleLinesInside(DetailAST ast) { 330 final int astType = ast.getType(); 331 if (isClassMemberBlock(astType)) { 332 final List<Integer> emptyLines = getEmptyLines(ast); 333 final List<Integer> emptyLinesToLog = getEmptyLinesToLog(emptyLines); 334 335 for (Integer lineNo : emptyLinesToLog) { 336 // Checkstyle counts line numbers from 0 but IDE from 1 337 log(lineNo + 1, MSG_MULTIPLE_LINES_INSIDE); 338 } 339 } 340 } 341 342 /** 343 * Whether the AST is a class member block. 344 * @param astType the AST to check. 345 * @return true if the AST is a class member block. 346 */ 347 private static boolean isClassMemberBlock(int astType) { 348 return astType == TokenTypes.STATIC_INIT 349 || astType == TokenTypes.INSTANCE_INIT 350 || astType == TokenTypes.METHOD_DEF 351 || astType == TokenTypes.CTOR_DEF; 352 } 353 354 /** 355 * Get list of empty lines. 356 * @param ast the ast to check. 357 * @return list of line numbers for empty lines. 358 */ 359 private List<Integer> getEmptyLines(DetailAST ast) { 360 final DetailAST lastToken = ast.getLastChild().getLastChild(); 361 int lastTokenLineNo = 0; 362 if (lastToken != null) { 363 lastTokenLineNo = lastToken.getLineNo(); 364 } 365 final List<Integer> emptyLines = new ArrayList<>(); 366 final FileContents fileContents = getFileContents(); 367 368 for (int lineNo = ast.getLineNo(); lineNo < lastTokenLineNo; lineNo++) { 369 if (fileContents.lineIsBlank(lineNo)) { 370 emptyLines.add(lineNo); 371 } 372 } 373 return emptyLines; 374 } 375 376 /** 377 * Get list of empty lines to log. 378 * @param emptyLines list of empty lines. 379 * @return list of empty lines to log. 380 */ 381 private static List<Integer> getEmptyLinesToLog(List<Integer> emptyLines) { 382 final List<Integer> emptyLinesToLog = new ArrayList<>(); 383 if (emptyLines.size() > 1) { 384 int previousEmptyLineNo = emptyLines.get(0); 385 for (int emptyLineNo : emptyLines) { 386 if (previousEmptyLineNo + 1 == emptyLineNo) { 387 emptyLinesToLog.add(emptyLineNo); 388 } 389 previousEmptyLineNo = emptyLineNo; 390 } 391 } 392 return emptyLinesToLog; 393 } 394 395 /** 396 * Whether the token has not allowed multiple empty lines before. 397 * @param ast the ast to check. 398 * @return true if the token has not allowed multiple empty lines before. 399 */ 400 private boolean hasMultipleLinesBefore(DetailAST ast) { 401 boolean result = false; 402 if ((ast.getType() != TokenTypes.VARIABLE_DEF 403 || isTypeField(ast)) 404 && hasNotAllowedTwoEmptyLinesBefore(ast)) { 405 result = true; 406 } 407 return result; 408 } 409 410 /** 411 * Process Package. 412 * @param ast token 413 * @param nextToken next token 414 */ 415 private void processPackage(DetailAST ast, DetailAST nextToken) { 416 if (ast.getLineNo() > 1 && !hasEmptyLineBefore(ast)) { 417 if (getFileContents().getFileName().endsWith("package-info.java")) { 418 if (ast.getFirstChild().getChildCount() == 0 && !isPrecededByJavadoc(ast)) { 419 log(ast.getLineNo(), MSG_SHOULD_BE_SEPARATED, ast.getText()); 420 } 421 } 422 else { 423 log(ast.getLineNo(), MSG_SHOULD_BE_SEPARATED, ast.getText()); 424 } 425 } 426 if (!hasEmptyLineAfter(ast)) { 427 log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, nextToken.getText()); 428 } 429 } 430 431 /** 432 * Process Import. 433 * @param ast token 434 * @param nextToken next token 435 * @param astType token Type 436 */ 437 private void processImport(DetailAST ast, DetailAST nextToken, int astType) { 438 if (astType != nextToken.getType() && !hasEmptyLineAfter(ast)) { 439 log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, nextToken.getText()); 440 } 441 } 442 443 /** 444 * Process Variable. 445 * @param ast token 446 * @param nextToken next Token 447 */ 448 private void processVariableDef(DetailAST ast, DetailAST nextToken) { 449 if (isTypeField(ast) && !hasEmptyLineAfter(ast) 450 && isViolatingEmptyLineBetweenFieldsPolicy(nextToken)) { 451 log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, 452 nextToken.getText()); 453 } 454 } 455 456 /** 457 * Checks whether token placement violates policy of empty line between fields. 458 * @param detailAST token to be analyzed 459 * @return true if policy is violated and warning should be raised; false otherwise 460 */ 461 private boolean isViolatingEmptyLineBetweenFieldsPolicy(DetailAST detailAST) { 462 return allowNoEmptyLineBetweenFields 463 && detailAST.getType() != TokenTypes.VARIABLE_DEF 464 && detailAST.getType() != TokenTypes.RCURLY 465 || !allowNoEmptyLineBetweenFields 466 && detailAST.getType() != TokenTypes.RCURLY; 467 } 468 469 /** 470 * Checks if a token has empty two previous lines and multiple empty lines is not allowed. 471 * @param token DetailAST token 472 * @return true, if token has empty two lines before and allowMultipleEmptyLines is false 473 */ 474 private boolean hasNotAllowedTwoEmptyLinesBefore(DetailAST token) { 475 return !allowMultipleEmptyLines && hasEmptyLineBefore(token) 476 && isPrePreviousLineEmpty(token); 477 } 478 479 /** 480 * Checks if a token has empty pre-previous line. 481 * @param token DetailAST token. 482 * @return true, if token has empty lines before. 483 */ 484 private boolean isPrePreviousLineEmpty(DetailAST token) { 485 boolean result = false; 486 final int lineNo = token.getLineNo(); 487 // 3 is the number of the pre-previous line because the numbering starts from zero. 488 final int number = 3; 489 if (lineNo >= number) { 490 final String prePreviousLine = getLines()[lineNo - number]; 491 result = CommonUtils.isBlank(prePreviousLine); 492 } 493 return result; 494 } 495 496 /** 497 * Checks if token have empty line after. 498 * @param token token. 499 * @return true if token have empty line after. 500 */ 501 private boolean hasEmptyLineAfter(DetailAST token) { 502 DetailAST lastToken = token.getLastChild().getLastChild(); 503 if (lastToken == null) { 504 lastToken = token.getLastChild(); 505 } 506 DetailAST nextToken = token.getNextSibling(); 507 if (isComment(nextToken)) { 508 nextToken = nextToken.getNextSibling(); 509 } 510 // Start of the next token 511 final int nextBegin = nextToken.getLineNo(); 512 // End of current token. 513 final int currentEnd = lastToken.getLineNo(); 514 return hasEmptyLine(currentEnd + 1, nextBegin - 1); 515 } 516 517 /** 518 * Checks, whether there are empty lines within the specified line range. Line numbering is 519 * started from 1 for parameter values 520 * @param startLine number of the first line in the range 521 * @param endLine number of the second line in the range 522 * @return <code>true</code> if found any blank line within the range, <code>false</code> 523 * otherwise 524 */ 525 private boolean hasEmptyLine(int startLine, int endLine) { 526 // Initial value is false - blank line not found 527 boolean result = false; 528 if (startLine <= endLine) { 529 final FileContents fileContents = getFileContents(); 530 for (int line = startLine; line <= endLine; line++) { 531 // Check, if the line is blank. Lines are numbered from 0, so subtract 1 532 if (fileContents.lineIsBlank(line - 1)) { 533 result = true; 534 break; 535 } 536 } 537 } 538 return result; 539 } 540 541 /** 542 * Checks if a token has a empty line before. 543 * @param token token. 544 * @return true, if token have empty line before. 545 */ 546 private boolean hasEmptyLineBefore(DetailAST token) { 547 boolean result = false; 548 final int lineNo = token.getLineNo(); 549 if (lineNo != 1) { 550 // [lineNo - 2] is the number of the previous line as the numbering starts from zero. 551 final String lineBefore = getLines()[lineNo - 2]; 552 result = CommonUtils.isBlank(lineBefore); 553 } 554 return result; 555 } 556 557 /** 558 * Check if token is preceded by javadoc comment. 559 * @param token token for check. 560 * @return true, if token is preceded by javadoc comment. 561 */ 562 private static boolean isPrecededByJavadoc(DetailAST token) { 563 boolean result = false; 564 final DetailAST previous = token.getPreviousSibling(); 565 if (previous.getType() == TokenTypes.BLOCK_COMMENT_BEGIN 566 && JavadocUtils.isJavadocComment(previous.getFirstChild().getText())) { 567 result = true; 568 } 569 return result; 570 } 571 572 /** 573 * Check if token is a comment. 574 * @param ast ast node 575 * @return true, if given ast is comment. 576 */ 577 private static boolean isComment(DetailAST ast) { 578 return ast.getType() == TokenTypes.SINGLE_LINE_COMMENT 579 || ast.getType() == TokenTypes.BLOCK_COMMENT_BEGIN; 580 } 581 582 /** 583 * If variable definition is a type field. 584 * @param variableDef variable definition. 585 * @return true variable definition is a type field. 586 */ 587 private static boolean isTypeField(DetailAST variableDef) { 588 final int parentType = variableDef.getParent().getParent().getType(); 589 return parentType == TokenTypes.CLASS_DEF; 590 } 591}