1
2
3
4
5 package com.qulice.checkstyle;
6
7 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
8 import com.puppycrawl.tools.checkstyle.api.DetailAST;
9 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
10 import java.util.Comparator;
11 import java.util.regex.Pattern;
12 import java.util.stream.StreamSupport;
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 public final class EmptyLinesCheck extends AbstractCheck {
30
31
32
33
34 private static final Pattern PATTERN = Pattern.compile("^\\s*$");
35
36
37
38
39 private final LineRanges anons;
40
41
42
43
44 private final LineRanges methods;
45
46
47
48
49 public EmptyLinesCheck() {
50 this.anons = new LineRanges();
51 this.methods = new LineRanges();
52 }
53
54 @Override
55 public int[] getDefaultTokens() {
56 return new int[] {
57 TokenTypes.METHOD_DEF,
58 TokenTypes.CTOR_DEF,
59 TokenTypes.OBJBLOCK,
60 };
61 }
62
63 @Override
64 public int[] getAcceptableTokens() {
65 return this.getDefaultTokens();
66 }
67
68 @Override
69 public int[] getRequiredTokens() {
70 return this.getDefaultTokens();
71 }
72
73 @Override
74 public void visitToken(final DetailAST ast) {
75 this.getLine(ast.getLastChild().getLineNo() - 1);
76 if (ast.getType() == TokenTypes.OBJBLOCK
77 && ast.getParent() != null
78 && ast.getParent().getType() == TokenTypes.LITERAL_NEW) {
79 final DetailAST left = ast.getFirstChild();
80 final DetailAST right = ast.getLastChild();
81 if (left != null && right != null) {
82 this.anons.add(
83 new LineRange(left.getLineNo(), right.getLineNo())
84 );
85 }
86 } else if (ast.getType() == TokenTypes.METHOD_DEF
87 || ast.getType() == TokenTypes.CTOR_DEF) {
88 final DetailAST opening = ast.findFirstToken(TokenTypes.SLIST);
89 if (opening != null) {
90 this.methods.add(
91 new LineRange(
92 opening.getLineNo(),
93 opening.findFirstToken(TokenTypes.RCURLY).getLineNo()
94 )
95 );
96 }
97 }
98 }
99
100 @Override
101 public void finishTree(final DetailAST root) {
102 final String[] lines = this.getLines();
103 for (int line = 0; line < lines.length; ++line) {
104 if (this.methods.inRange(line + 1)
105 && EmptyLinesCheck.PATTERN.matcher(lines[line]).find()
106 && this.insideMethod(line + 1)) {
107 this.log(line + 1, "Empty line inside method");
108 }
109 }
110 this.methods.clear();
111 this.anons.clear();
112 super.finishTree(root);
113 }
114
115 private boolean insideMethod(final int line) {
116 return EmptyLinesCheck.linesBetweenBraces(
117 line, this.methods::iterator, Integer.MIN_VALUE
118 ) < EmptyLinesCheck.linesBetweenBraces(
119 line, this.anons::iterator, Integer.MAX_VALUE
120 );
121 }
122
123 private static int linesBetweenBraces(final int line,
124 final Iterable<LineRange> iterator, final int def) {
125 return StreamSupport.stream(iterator.spliterator(), false)
126 .filter(r -> r.within(line))
127 .min(Comparator.comparingInt(r -> r.last() - r.first()))
128 .map(r -> r.last() - r.first())
129 .orElse(def);
130 }
131 }