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 this.methods.add(
82 new LineRange(
83 opening.getLineNo(),
84 opening.findFirstToken(TokenTypes.RCURLY).getLineNo()
85 )
86 );
87 }
88 }
89 }
90
91 @Override
92 public void finishTree(final DetailAST root) {
93 final String[] lines = this.getLines();
94 for (int line = 0; line < lines.length; ++line) {
95 if (this.methods.inRange(line + 1)
96 && EmptyLinesCheck.PATTERN.matcher(lines[line]).find()
97 && this.insideMethod(line + 1)) {
98 this.log(line + 1, "Empty line inside method");
99 }
100 }
101 this.methods.clear();
102 this.anons.clear();
103 super.finishTree(root);
104 }
105
106
107
108
109
110
111
112
113
114 private boolean insideMethod(final int line) {
115 return EmptyLinesCheck.linesBetweenBraces(
116 line, this.methods::iterator, Integer.MIN_VALUE
117 ) < EmptyLinesCheck.linesBetweenBraces(
118 line, this.anons::iterator, Integer.MAX_VALUE
119 );
120 }
121
122
123
124
125
126
127
128
129 private static int linesBetweenBraces(final int line,
130 final Iterable<LineRange> iterator, final int def) {
131 return StreamSupport.stream(iterator.spliterator(), false)
132 .filter(r -> r.within(line))
133 .min(Comparator.comparingInt(r -> r.last() - r.first()))
134 .map(r -> r.last() - r.first())
135 .orElse(def);
136 }
137 }