1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle.checks.whitespace;
21
22 import java.util.ArrayList;
23 import java.util.LinkedList;
24 import java.util.List;
25 import java.util.Optional;
26
27 import com.puppycrawl.tools.checkstyle.StatelessCheck;
28 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
29 import com.puppycrawl.tools.checkstyle.api.DetailAST;
30 import com.puppycrawl.tools.checkstyle.api.FileContents;
31 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
32 import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
33 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
34 import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
35 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127 @StatelessCheck
128 public class EmptyLineSeparatorCheck extends AbstractCheck {
129
130
131
132
133
134 public static final String MSG_SHOULD_BE_SEPARATED = "empty.line.separator";
135
136
137
138
139
140
141 public static final String MSG_MULTIPLE_LINES = "empty.line.separator.multiple.lines";
142
143
144
145
146
147 public static final String MSG_MULTIPLE_LINES_AFTER =
148 "empty.line.separator.multiple.lines.after";
149
150
151
152
153
154 public static final String MSG_MULTIPLE_LINES_INSIDE =
155 "empty.line.separator.multiple.lines.inside";
156
157
158 private boolean allowNoEmptyLineBetweenFields;
159
160
161 private boolean allowMultipleEmptyLines = true;
162
163
164 private boolean allowMultipleEmptyLinesInsideClassMembers = true;
165
166
167
168
169
170
171
172
173 public final void setAllowNoEmptyLineBetweenFields(boolean allow) {
174 allowNoEmptyLineBetweenFields = allow;
175 }
176
177
178
179
180
181
182
183 public void setAllowMultipleEmptyLines(boolean allow) {
184 allowMultipleEmptyLines = allow;
185 }
186
187
188
189
190
191
192
193 public void setAllowMultipleEmptyLinesInsideClassMembers(boolean allow) {
194 allowMultipleEmptyLinesInsideClassMembers = allow;
195 }
196
197 @Override
198 public boolean isCommentNodesRequired() {
199 return true;
200 }
201
202 @Override
203 public int[] getDefaultTokens() {
204 return getAcceptableTokens();
205 }
206
207 @Override
208 public int[] getAcceptableTokens() {
209 return new int[] {
210 TokenTypes.PACKAGE_DEF,
211 TokenTypes.IMPORT,
212 TokenTypes.STATIC_IMPORT,
213 TokenTypes.CLASS_DEF,
214 TokenTypes.INTERFACE_DEF,
215 TokenTypes.ENUM_DEF,
216 TokenTypes.STATIC_INIT,
217 TokenTypes.INSTANCE_INIT,
218 TokenTypes.METHOD_DEF,
219 TokenTypes.CTOR_DEF,
220 TokenTypes.VARIABLE_DEF,
221 TokenTypes.RECORD_DEF,
222 TokenTypes.COMPACT_CTOR_DEF,
223 };
224 }
225
226 @Override
227 public int[] getRequiredTokens() {
228 return CommonUtil.EMPTY_INT_ARRAY;
229 }
230
231 @Override
232 public void visitToken(DetailAST ast) {
233 checkComments(ast);
234 if (hasMultipleLinesBefore(ast)) {
235 log(ast, MSG_MULTIPLE_LINES, ast.getText());
236 }
237 if (!allowMultipleEmptyLinesInsideClassMembers) {
238 processMultipleLinesInside(ast);
239 }
240 if (ast.getType() == TokenTypes.PACKAGE_DEF) {
241 checkCommentInModifiers(ast);
242 }
243 DetailAST nextToken = ast.getNextSibling();
244 while (nextToken != null && TokenUtil.isCommentType(nextToken.getType())) {
245 nextToken = nextToken.getNextSibling();
246 }
247 if (nextToken != null) {
248 checkToken(ast, nextToken);
249 }
250 }
251
252
253
254
255
256
257
258 private void checkToken(DetailAST ast, DetailAST nextToken) {
259 final int astType = ast.getType();
260 switch (astType) {
261 case TokenTypes.VARIABLE_DEF:
262 processVariableDef(ast, nextToken);
263 break;
264 case TokenTypes.IMPORT:
265 case TokenTypes.STATIC_IMPORT:
266 processImport(ast, nextToken);
267 break;
268 case TokenTypes.PACKAGE_DEF:
269 processPackage(ast, nextToken);
270 break;
271 default:
272 if (nextToken.getType() == TokenTypes.RCURLY) {
273 if (hasNotAllowedTwoEmptyLinesBefore(nextToken)) {
274 final DetailAST result = getLastElementBeforeEmptyLines(ast,
275 nextToken.getLineNo());
276 log(result, MSG_MULTIPLE_LINES_AFTER, result.getText());
277 }
278 }
279 else if (!hasEmptyLineAfter(ast)) {
280 log(nextToken, MSG_SHOULD_BE_SEPARATED,
281 nextToken.getText());
282 }
283 }
284 }
285
286
287
288
289
290
291 private void checkCommentInModifiers(DetailAST packageDef) {
292 final Optional<DetailAST> comment = findCommentUnder(packageDef);
293 comment.ifPresent(commentValue -> {
294 log(commentValue, MSG_SHOULD_BE_SEPARATED, commentValue.getText());
295 });
296 }
297
298
299
300
301
302
303
304 private void processMultipleLinesInside(DetailAST ast) {
305 final int astType = ast.getType();
306 if (isClassMemberBlock(astType)) {
307 final List<Integer> emptyLines = getEmptyLines(ast);
308 final List<Integer> emptyLinesToLog = getEmptyLinesToLog(emptyLines);
309 for (Integer lineNo : emptyLinesToLog) {
310 log(getLastElementBeforeEmptyLines(ast, lineNo), MSG_MULTIPLE_LINES_INSIDE);
311 }
312 }
313 }
314
315
316
317
318
319
320
321
322 private static DetailAST getLastElementBeforeEmptyLines(DetailAST ast, int line) {
323 DetailAST result = ast;
324 if (ast.getFirstChild().getLineNo() <= line) {
325 result = ast.getFirstChild();
326 while (result.getNextSibling() != null
327 && result.getNextSibling().getLineNo() <= line) {
328 result = result.getNextSibling();
329 }
330 if (result.hasChildren()) {
331 result = getLastElementBeforeEmptyLines(result, line);
332 }
333 }
334
335 if (result.getNextSibling() != null) {
336 final Optional<DetailAST> postFixNode = getPostFixNode(result.getNextSibling());
337 if (postFixNode.isPresent()) {
338
339
340
341
342
343 final DetailAST firstChildAfterPostFix = postFixNode.orElseThrow();
344 result = getLastElementBeforeEmptyLines(firstChildAfterPostFix, line);
345 }
346 }
347 return result;
348 }
349
350
351
352
353
354
355
356 private static Optional<DetailAST> getPostFixNode(DetailAST ast) {
357 Optional<DetailAST> result = Optional.empty();
358 if (ast.getType() == TokenTypes.EXPR
359
360 && ast.getFirstChild().getType() == TokenTypes.METHOD_CALL) {
361
362 final DetailAST node = ast.getFirstChild().getFirstChild();
363 if (node.getType() == TokenTypes.DOT) {
364 result = Optional.of(node);
365 }
366 }
367 return result;
368 }
369
370
371
372
373
374
375
376 private static boolean isClassMemberBlock(int astType) {
377 return TokenUtil.isOfType(astType,
378 TokenTypes.STATIC_INIT, TokenTypes.INSTANCE_INIT, TokenTypes.METHOD_DEF,
379 TokenTypes.CTOR_DEF, TokenTypes.COMPACT_CTOR_DEF);
380 }
381
382
383
384
385
386
387
388
389 @SuppressWarnings("deprecation")
390 private List<Integer> getEmptyLines(DetailAST ast) {
391 final DetailAST lastToken = ast.getLastChild().getLastChild();
392 int lastTokenLineNo = 0;
393 if (lastToken != null) {
394
395
396 lastTokenLineNo = lastToken.getLineNo() - 2;
397 }
398 final List<Integer> emptyLines = new ArrayList<>();
399 final FileContents fileContents = getFileContents();
400
401 for (int lineNo = ast.getLineNo(); lineNo <= lastTokenLineNo; lineNo++) {
402 if (fileContents.lineIsBlank(lineNo)) {
403 emptyLines.add(lineNo);
404 }
405 }
406 return emptyLines;
407 }
408
409
410
411
412
413
414
415 private static List<Integer> getEmptyLinesToLog(Iterable<Integer> emptyLines) {
416 final List<Integer> emptyLinesToLog = new ArrayList<>();
417 int previousEmptyLineNo = -1;
418 for (int emptyLineNo : emptyLines) {
419 if (previousEmptyLineNo + 1 == emptyLineNo) {
420 emptyLinesToLog.add(previousEmptyLineNo);
421 }
422 previousEmptyLineNo = emptyLineNo;
423 }
424 return emptyLinesToLog;
425 }
426
427
428
429
430
431
432
433 private boolean hasMultipleLinesBefore(DetailAST ast) {
434 return (ast.getType() != TokenTypes.VARIABLE_DEF || isTypeField(ast))
435 && hasNotAllowedTwoEmptyLinesBefore(ast);
436 }
437
438
439
440
441
442
443
444 private void processPackage(DetailAST ast, DetailAST nextToken) {
445 if (ast.getLineNo() > 1 && !hasEmptyLineBefore(ast)) {
446 if (CheckUtil.isPackageInfo(getFilePath())) {
447 if (!ast.getFirstChild().hasChildren() && !isPrecededByJavadoc(ast)) {
448 log(ast, MSG_SHOULD_BE_SEPARATED, ast.getText());
449 }
450 }
451 else {
452 log(ast, MSG_SHOULD_BE_SEPARATED, ast.getText());
453 }
454 }
455 if (isLineEmptyAfterPackage(ast)) {
456 final DetailAST elementAst = getViolationAstForPackage(ast);
457 log(elementAst, MSG_SHOULD_BE_SEPARATED, elementAst.getText());
458 }
459 else if (!hasEmptyLineAfter(ast)) {
460 log(nextToken, MSG_SHOULD_BE_SEPARATED, nextToken.getText());
461 }
462 }
463
464
465
466
467
468
469
470 private static boolean isLineEmptyAfterPackage(DetailAST ast) {
471 DetailAST nextElement = ast;
472 final int lastChildLineNo = ast.getLastChild().getLineNo();
473 while (nextElement.getLineNo() < lastChildLineNo + 1
474 && nextElement.getNextSibling() != null) {
475 nextElement = nextElement.getNextSibling();
476 }
477 return nextElement.getLineNo() == lastChildLineNo + 1;
478 }
479
480
481
482
483
484
485
486 private static DetailAST getViolationAstForPackage(DetailAST ast) {
487 DetailAST nextElement = ast;
488 final int lastChildLineNo = ast.getLastChild().getLineNo();
489 while (nextElement.getLineNo() < lastChildLineNo + 1) {
490 nextElement = nextElement.getNextSibling();
491 }
492 return nextElement;
493 }
494
495
496
497
498
499
500
501 private void processImport(DetailAST ast, DetailAST nextToken) {
502 if (!TokenUtil.isOfType(nextToken, TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT)
503 && !hasEmptyLineAfter(ast)) {
504 log(nextToken, MSG_SHOULD_BE_SEPARATED, nextToken.getText());
505 }
506 }
507
508
509
510
511
512
513
514 private void processVariableDef(DetailAST ast, DetailAST nextToken) {
515 if (isTypeField(ast) && !hasEmptyLineAfter(ast)
516 && isViolatingEmptyLineBetweenFieldsPolicy(nextToken)) {
517 log(nextToken, MSG_SHOULD_BE_SEPARATED,
518 nextToken.getText());
519 }
520 }
521
522
523
524
525
526
527
528 private boolean isViolatingEmptyLineBetweenFieldsPolicy(DetailAST detailAST) {
529 return detailAST.getType() != TokenTypes.RCURLY
530 && (!allowNoEmptyLineBetweenFields
531 || !TokenUtil.isOfType(detailAST, TokenTypes.COMMA, TokenTypes.VARIABLE_DEF));
532 }
533
534
535
536
537
538
539
540 private boolean hasNotAllowedTwoEmptyLinesBefore(DetailAST token) {
541 return !allowMultipleEmptyLines && hasEmptyLineBefore(token)
542 && isPrePreviousLineEmpty(token);
543 }
544
545
546
547
548
549
550 private void checkComments(DetailAST token) {
551 if (!allowMultipleEmptyLines) {
552 if (TokenUtil.isOfType(token,
553 TokenTypes.PACKAGE_DEF, TokenTypes.IMPORT,
554 TokenTypes.STATIC_IMPORT, TokenTypes.STATIC_INIT)) {
555 DetailAST previousNode = token.getPreviousSibling();
556 while (isCommentInBeginningOfLine(previousNode)) {
557 if (hasEmptyLineBefore(previousNode) && isPrePreviousLineEmpty(previousNode)) {
558 log(previousNode, MSG_MULTIPLE_LINES, previousNode.getText());
559 }
560 previousNode = previousNode.getPreviousSibling();
561 }
562 }
563 else {
564 checkCommentsInsideToken(token);
565 }
566 }
567 }
568
569
570
571
572
573
574
575 private void checkCommentsInsideToken(DetailAST token) {
576 final List<DetailAST> childNodes = new LinkedList<>();
577 DetailAST childNode = token.getLastChild();
578 while (childNode != null) {
579 if (childNode.getType() == TokenTypes.MODIFIERS) {
580 for (DetailAST node = token.getFirstChild().getLastChild();
581 node != null;
582 node = node.getPreviousSibling()) {
583 if (isCommentInBeginningOfLine(node)) {
584 childNodes.add(node);
585 }
586 }
587 }
588 else if (isCommentInBeginningOfLine(childNode)) {
589 childNodes.add(childNode);
590 }
591 childNode = childNode.getPreviousSibling();
592 }
593 for (DetailAST node : childNodes) {
594 if (hasEmptyLineBefore(node) && isPrePreviousLineEmpty(node)) {
595 log(node, MSG_MULTIPLE_LINES, node.getText());
596 }
597 }
598 }
599
600
601
602
603
604
605
606 private boolean isPrePreviousLineEmpty(DetailAST token) {
607 boolean result = false;
608 final int lineNo = token.getLineNo();
609
610 final int number = 3;
611 if (lineNo >= number) {
612 final String prePreviousLine = getLine(lineNo - number);
613 result = CommonUtil.isBlank(prePreviousLine);
614 }
615 return result;
616 }
617
618
619
620
621
622
623
624 private boolean hasEmptyLineAfter(DetailAST token) {
625 DetailAST lastToken = token.getLastChild().getLastChild();
626 if (lastToken == null) {
627 lastToken = token.getLastChild();
628 }
629 DetailAST nextToken = token.getNextSibling();
630 if (TokenUtil.isCommentType(nextToken.getType())) {
631 nextToken = nextToken.getNextSibling();
632 }
633
634 final int nextBegin = nextToken.getLineNo();
635
636 final int currentEnd = lastToken.getLineNo();
637 return hasEmptyLine(currentEnd + 1, nextBegin - 1);
638 }
639
640
641
642
643
644
645
646 private static Optional<DetailAST> findCommentUnder(DetailAST packageDef) {
647 return Optional.ofNullable(packageDef.getNextSibling())
648 .map(sibling -> sibling.findFirstToken(TokenTypes.MODIFIERS))
649 .map(DetailAST::getFirstChild)
650 .filter(token -> TokenUtil.isCommentType(token.getType()))
651 .filter(comment -> comment.getLineNo() == packageDef.getLineNo() + 1);
652 }
653
654
655
656
657
658
659
660
661
662
663
664 @SuppressWarnings("deprecation")
665 private boolean hasEmptyLine(int startLine, int endLine) {
666
667 boolean result = false;
668 final FileContents fileContents = getFileContents();
669 for (int line = startLine; line <= endLine; line++) {
670
671 if (fileContents.lineIsBlank(line - 1)) {
672 result = true;
673 break;
674 }
675 }
676 return result;
677 }
678
679
680
681
682
683
684
685 private boolean hasEmptyLineBefore(DetailAST token) {
686 boolean result = false;
687 final int lineNo = token.getLineNo();
688 if (lineNo != 1) {
689
690 final String lineBefore = getLine(lineNo - 2);
691 result = CommonUtil.isBlank(lineBefore);
692 }
693 return result;
694 }
695
696
697
698
699
700
701
702 private boolean isCommentInBeginningOfLine(DetailAST comment) {
703
704
705 boolean result = false;
706 if (comment != null) {
707 final String lineWithComment = getLine(comment.getLineNo() - 1).trim();
708 result = lineWithComment.startsWith("//") || lineWithComment.startsWith("/*");
709 }
710 return result;
711 }
712
713
714
715
716
717
718
719 private static boolean isPrecededByJavadoc(DetailAST token) {
720 boolean result = false;
721 final DetailAST previous = token.getPreviousSibling();
722 if (previous.getType() == TokenTypes.BLOCK_COMMENT_BEGIN
723 && JavadocUtil.isJavadocComment(previous.getFirstChild().getText())) {
724 result = true;
725 }
726 return result;
727 }
728
729
730
731
732
733
734
735 private static boolean isTypeField(DetailAST variableDef) {
736 return TokenUtil.isTypeDeclaration(variableDef.getParent().getParent().getType());
737 }
738
739 }