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 (astType != TokenTypes.CLASS_DEF && 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 // -1 as count starts from 0 364 // -2 as last token line cannot be empty, because it is a RCURLY 365 lastTokenLineNo = lastToken.getLineNo() - 2; 366 } 367 final List<Integer> emptyLines = new ArrayList<>(); 368 final FileContents fileContents = getFileContents(); 369 370 for (int lineNo = ast.getLineNo(); lineNo <= lastTokenLineNo; lineNo++) { 371 if (fileContents.lineIsBlank(lineNo)) { 372 emptyLines.add(lineNo); 373 } 374 } 375 return emptyLines; 376 } 377 378 /** 379 * Get list of empty lines to log. 380 * @param emptyLines list of empty lines. 381 * @return list of empty lines to log. 382 */ 383 private static List<Integer> getEmptyLinesToLog(List<Integer> emptyLines) { 384 final List<Integer> emptyLinesToLog = new ArrayList<>(); 385 if (emptyLines.size() >= 2) { 386 int previousEmptyLineNo = emptyLines.get(0); 387 for (int emptyLineNo : emptyLines) { 388 if (previousEmptyLineNo + 1 == emptyLineNo) { 389 emptyLinesToLog.add(emptyLineNo); 390 } 391 previousEmptyLineNo = emptyLineNo; 392 } 393 } 394 return emptyLinesToLog; 395 } 396 397 /** 398 * Whether the token has not allowed multiple empty lines before. 399 * @param ast the ast to check. 400 * @return true if the token has not allowed multiple empty lines before. 401 */ 402 private boolean hasMultipleLinesBefore(DetailAST ast) { 403 boolean result = false; 404 if ((ast.getType() != TokenTypes.VARIABLE_DEF 405 || isTypeField(ast)) 406 && hasNotAllowedTwoEmptyLinesBefore(ast)) { 407 result = true; 408 } 409 return result; 410 } 411 412 /** 413 * Process Package. 414 * @param ast token 415 * @param nextToken next token 416 */ 417 private void processPackage(DetailAST ast, DetailAST nextToken) { 418 if (ast.getLineNo() > 1 && !hasEmptyLineBefore(ast)) { 419 if (getFileContents().getFileName().endsWith("package-info.java")) { 420 if (ast.getFirstChild().getChildCount() == 0 && !isPrecededByJavadoc(ast)) { 421 log(ast.getLineNo(), MSG_SHOULD_BE_SEPARATED, ast.getText()); 422 } 423 } 424 else { 425 log(ast.getLineNo(), MSG_SHOULD_BE_SEPARATED, ast.getText()); 426 } 427 } 428 if (!hasEmptyLineAfter(ast)) { 429 log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, nextToken.getText()); 430 } 431 } 432 433 /** 434 * Process Import. 435 * @param ast token 436 * @param nextToken next token 437 * @param astType token Type 438 */ 439 private void processImport(DetailAST ast, DetailAST nextToken, int astType) { 440 if (astType != nextToken.getType() && !hasEmptyLineAfter(ast)) { 441 log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, nextToken.getText()); 442 } 443 } 444 445 /** 446 * Process Variable. 447 * @param ast token 448 * @param nextToken next Token 449 */ 450 private void processVariableDef(DetailAST ast, DetailAST nextToken) { 451 if (isTypeField(ast) && !hasEmptyLineAfter(ast) 452 && isViolatingEmptyLineBetweenFieldsPolicy(nextToken)) { 453 log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, 454 nextToken.getText()); 455 } 456 } 457 458 /** 459 * Checks whether token placement violates policy of empty line between fields. 460 * @param detailAST token to be analyzed 461 * @return true if policy is violated and warning should be raised; false otherwise 462 */ 463 private boolean isViolatingEmptyLineBetweenFieldsPolicy(DetailAST detailAST) { 464 return allowNoEmptyLineBetweenFields 465 && detailAST.getType() != TokenTypes.VARIABLE_DEF 466 && detailAST.getType() != TokenTypes.RCURLY 467 || !allowNoEmptyLineBetweenFields 468 && detailAST.getType() != TokenTypes.RCURLY; 469 } 470 471 /** 472 * Checks if a token has empty two previous lines and multiple empty lines is not allowed. 473 * @param token DetailAST token 474 * @return true, if token has empty two lines before and allowMultipleEmptyLines is false 475 */ 476 private boolean hasNotAllowedTwoEmptyLinesBefore(DetailAST token) { 477 return !allowMultipleEmptyLines && hasEmptyLineBefore(token) 478 && isPrePreviousLineEmpty(token); 479 } 480 481 /** 482 * Checks if a token has empty pre-previous line. 483 * @param token DetailAST token. 484 * @return true, if token has empty lines before. 485 */ 486 private boolean isPrePreviousLineEmpty(DetailAST token) { 487 boolean result = false; 488 final int lineNo = token.getLineNo(); 489 // 3 is the number of the pre-previous line because the numbering starts from zero. 490 final int number = 3; 491 if (lineNo >= number) { 492 final String prePreviousLine = getLines()[lineNo - number]; 493 result = CommonUtils.isBlank(prePreviousLine); 494 } 495 return result; 496 } 497 498 /** 499 * Checks if token have empty line after. 500 * @param token token. 501 * @return true if token have empty line after. 502 */ 503 private boolean hasEmptyLineAfter(DetailAST token) { 504 DetailAST lastToken = token.getLastChild().getLastChild(); 505 if (lastToken == null) { 506 lastToken = token.getLastChild(); 507 } 508 DetailAST nextToken = token.getNextSibling(); 509 if (isComment(nextToken)) { 510 nextToken = nextToken.getNextSibling(); 511 } 512 // Start of the next token 513 final int nextBegin = nextToken.getLineNo(); 514 // End of current token. 515 final int currentEnd = lastToken.getLineNo(); 516 return hasEmptyLine(currentEnd + 1, nextBegin - 1); 517 } 518 519 /** 520 * Checks, whether there are empty lines within the specified line range. Line numbering is 521 * started from 1 for parameter values 522 * @param startLine number of the first line in the range 523 * @param endLine number of the second line in the range 524 * @return {@code true} if found any blank line within the range, {@code false} 525 * otherwise 526 */ 527 private boolean hasEmptyLine(int startLine, int endLine) { 528 // Initial value is false - blank line not found 529 boolean result = false; 530 if (startLine <= endLine) { 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 } 540 return result; 541 } 542 543 /** 544 * Checks if a token has a empty line before. 545 * @param token token. 546 * @return true, if token have empty line before. 547 */ 548 private boolean hasEmptyLineBefore(DetailAST token) { 549 boolean result = false; 550 final int lineNo = token.getLineNo(); 551 if (lineNo != 1) { 552 // [lineNo - 2] is the number of the previous line as the numbering starts from zero. 553 final String lineBefore = getLines()[lineNo - 2]; 554 result = CommonUtils.isBlank(lineBefore); 555 } 556 return result; 557 } 558 559 /** 560 * Check if token is preceded by javadoc comment. 561 * @param token token for check. 562 * @return true, if token is preceded by javadoc comment. 563 */ 564 private static boolean isPrecededByJavadoc(DetailAST token) { 565 boolean result = false; 566 final DetailAST previous = token.getPreviousSibling(); 567 if (previous.getType() == TokenTypes.BLOCK_COMMENT_BEGIN 568 && JavadocUtils.isJavadocComment(previous.getFirstChild().getText())) { 569 result = true; 570 } 571 return result; 572 } 573 574 /** 575 * Check if token is a comment. 576 * @param ast ast node 577 * @return true, if given ast is comment. 578 */ 579 private static boolean isComment(DetailAST ast) { 580 return ast.getType() == TokenTypes.SINGLE_LINE_COMMENT 581 || ast.getType() == TokenTypes.BLOCK_COMMENT_BEGIN; 582 } 583 584 /** 585 * If variable definition is a type field. 586 * @param variableDef variable definition. 587 * @return true variable definition is a type field. 588 */ 589 private static boolean isTypeField(DetailAST variableDef) { 590 final int parentType = variableDef.getParent().getParent().getType(); 591 return parentType == TokenTypes.CLASS_DEF; 592 } 593}