View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.whitespace;
21  
22  import java.util.ArrayList;
23  import java.util.List;
24  
25  import com.puppycrawl.tools.checkstyle.StatelessCheck;
26  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
27  import com.puppycrawl.tools.checkstyle.api.DetailAST;
28  import com.puppycrawl.tools.checkstyle.api.FileContents;
29  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
30  import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
31  import com.puppycrawl.tools.checkstyle.utils.JavadocUtils;
32  
33  /**
34   * Checks for empty line separators after header, package, all import declarations,
35   * fields, constructors, methods, nested classes,
36   * static initializers and instance initializers.
37   *
38   * <p> By default the check will check the following statements:
39   *  {@link TokenTypes#PACKAGE_DEF PACKAGE_DEF},
40   *  {@link TokenTypes#IMPORT IMPORT},
41   *  {@link TokenTypes#CLASS_DEF CLASS_DEF},
42   *  {@link TokenTypes#INTERFACE_DEF INTERFACE_DEF},
43   *  {@link TokenTypes#STATIC_INIT STATIC_INIT},
44   *  {@link TokenTypes#INSTANCE_INIT INSTANCE_INIT},
45   *  {@link TokenTypes#METHOD_DEF METHOD_DEF},
46   *  {@link TokenTypes#CTOR_DEF CTOR_DEF},
47   *  {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF}.
48   * </p>
49   *
50   * <p>
51   * Example of declarations without empty line separator:
52   * </p>
53   *
54   * <pre>
55   * ///////////////////////////////////////////////////
56   * //HEADER
57   * ///////////////////////////////////////////////////
58   * package com.puppycrawl.tools.checkstyle.whitespace;
59   * import java.io.Serializable;
60   * class Foo
61   * {
62   *     public static final int FOO_CONST = 1;
63   *     public void foo() {} //should be separated from previous statement.
64   * }
65   * </pre>
66   *
67   * <p> An example of how to configure the check with default parameters is:
68   * </p>
69   *
70   * <pre>
71   * &lt;module name="EmptyLineSeparator"/&gt;
72   * </pre>
73   *
74   * <p>
75   * Example of declarations with empty line separator
76   * that is expected by the Check by default:
77   * </p>
78   *
79   * <pre>
80   * ///////////////////////////////////////////////////
81   * //HEADER
82   * ///////////////////////////////////////////////////
83   *
84   * package com.puppycrawl.tools.checkstyle.whitespace;
85   *
86   * import java.io.Serializable;
87   *
88   * class Foo
89   * {
90   *     public static final int FOO_CONST = 1;
91   *
92   *     public void foo() {}
93   * }
94   * </pre>
95   * <p> An example how to check empty line after
96   * {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF} and
97   * {@link TokenTypes#METHOD_DEF METHOD_DEF}:
98   * </p>
99   *
100  * <pre>
101  * &lt;module name="EmptyLineSeparator"&gt;
102  *    &lt;property name="tokens" value="VARIABLE_DEF, METHOD_DEF"/&gt;
103  * &lt;/module&gt;
104  * </pre>
105  *
106  * <p>
107  * An example how to allow no empty line between fields:
108  * </p>
109  * <pre>
110  * &lt;module name="EmptyLineSeparator"&gt;
111  *    &lt;property name="allowNoEmptyLineBetweenFields" value="true"/&gt;
112  * &lt;/module&gt;
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  * &lt;module name="EmptyLineSeparator"&gt;
146  *    &lt;property name="allowMultipleEmptyLines" value="false"/&gt;
147  * &lt;/module&gt;
148  * </pre>
149  *
150  * <p>
151  * An example how to disallow multiple empty line inside methods, constructors, etc.:
152  * </p>
153  * <pre>
154  * &lt;module name="EmptyLineSeparator"&gt;
155  *    &lt;property name="allowMultipleEmptyLinesInsideClassMembers" value="false"/&gt;
156  * &lt;/module&gt;
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
193 public 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 }