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