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