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