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