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
11
12
13
14
15
16
17
18
19
20
21
22
23 public final class JavadocLocationCheck extends AbstractCheck {
24
25 @Override
26 public int[] getDefaultTokens() {
27 return new int[] {
28 TokenTypes.CLASS_DEF,
29 TokenTypes.INTERFACE_DEF,
30 TokenTypes.VARIABLE_DEF,
31 TokenTypes.CTOR_DEF,
32 TokenTypes.METHOD_DEF,
33 };
34 }
35
36 @Override
37 public int[] getAcceptableTokens() {
38 return this.getDefaultTokens();
39 }
40
41 @Override
42 public int[] getRequiredTokens() {
43 return this.getDefaultTokens();
44 }
45
46 @Override
47 public void visitToken(final DetailAST ast) {
48 if (!JavadocLocationCheck.isField(ast)) {
49 return;
50 }
51 final String[] lines = this.getLines();
52 this.checkEmptyLines(ast, lines);
53 this.checkAnnotationAboveJavadoc(ast, lines);
54 }
55
56
57
58
59
60
61 private void checkEmptyLines(final DetailAST ast, final String... lines) {
62 final int current = JavadocLocationCheck.javadocEnd(
63 ast.getLineNo() - 1, lines
64 );
65 if (current > 0) {
66 final int diff = ast.getLineNo() - current;
67 for (int pos = 1; pos < diff; pos += 1) {
68 this.log(
69 current + pos,
70 "Empty line between javadoc and subject"
71 );
72 }
73 }
74 }
75
76
77
78
79
80
81
82
83
84 private static int javadocEnd(final int from, final String... lines) {
85 int current = from;
86 int result = 0;
87 while (current > 0) {
88 final String line = lines[current - 1].trim();
89 if (line.endsWith("*/")) {
90 result = current;
91 break;
92 }
93 if (!line.isEmpty()) {
94 break;
95 }
96 current -= 1;
97 }
98 return result;
99 }
100
101
102
103
104
105
106 private void checkAnnotationAboveJavadoc(
107 final DetailAST ast, final String... lines
108 ) {
109 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
110 if (modifiers != null) {
111 final DetailAST after = modifiers.getNextSibling();
112 final int annotation = JavadocLocationCheck.firstAnnotationLine(
113 modifiers
114 );
115 if (after != null && annotation != Integer.MAX_VALUE
116 && JavadocLocationCheck.javadocBetween(
117 annotation, after.getLineNo(), lines
118 )) {
119 this.log(annotation, "Annotation must be placed after Javadoc");
120 }
121 }
122 }
123
124
125
126
127
128
129
130 private static int firstAnnotationLine(final DetailAST modifiers) {
131 int line = Integer.MAX_VALUE;
132 DetailAST child = modifiers.getFirstChild();
133 while (child != null) {
134 if (child.getType() == TokenTypes.ANNOTATION
135 && child.getLineNo() < line) {
136 line = child.getLineNo();
137 }
138 child = child.getNextSibling();
139 }
140 return line;
141 }
142
143
144
145
146
147
148
149
150
151
152 private static boolean javadocBetween(final int start, final int end,
153 final String... lines) {
154 boolean found = false;
155 for (int pos = start + 1; pos < end; pos += 1) {
156 final String line = lines[pos - 1].trim();
157 if (line.startsWith("/**") || line.endsWith("*/")) {
158 found = true;
159 break;
160 }
161 }
162 return found;
163 }
164
165
166
167
168
169
170
171
172 private static boolean isField(final DetailAST node) {
173 boolean yes = true;
174 if (TokenTypes.VARIABLE_DEF == node.getType()) {
175 yes = TokenTypes.OBJBLOCK == node.getParent().getType();
176 }
177 return yes;
178 }
179 }