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.ArrayList;
11 import java.util.Arrays;
12 import java.util.Collection;
13 import java.util.List;
14 import java.util.regex.Pattern;
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 public final class JavadocTagsCheck extends AbstractCheck {
40
41
42
43
44 private final List<RequiredJavaDocTag> required = new ArrayList<>(1);
45
46
47
48
49 private final Collection<String> prohibited =
50 Arrays.asList("author", "version");
51
52 @Override
53 public int[] getDefaultTokens() {
54 return new int[]{
55 TokenTypes.CLASS_DEF,
56 TokenTypes.INTERFACE_DEF,
57 };
58 }
59
60 @Override
61 public int[] getAcceptableTokens() {
62 return this.getDefaultTokens();
63 }
64
65 @Override
66 public int[] getRequiredTokens() {
67 return this.getDefaultTokens();
68 }
69
70 @Override
71 public void init() {
72 this.required.add(
73 new RequiredJavaDocTag(
74 "since",
75 Pattern.compile(
76 "^\\d+(\\.\\d+){1,2}(\\.[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$"
77 ),
78 this::log
79 )
80 );
81 }
82
83 @Override
84 public void visitToken(final DetailAST ast) {
85 final String[] lines = this.getLines();
86 final int start = ast.getLineNo();
87 final int cstart = JavadocTagsCheck.findCommentStart(lines, start);
88 final int cend = JavadocTagsCheck.findCommentEnd(lines, start);
89 if (cend > cstart && cstart >= 0) {
90 for (final String tag : this.prohibited) {
91 this.findProhibited(lines, start, cstart, cend, tag);
92 }
93 for (final RequiredJavaDocTag tag : this.required) {
94 tag.matchTagFormat(lines, cstart, cend);
95 }
96 } else {
97 this.log(0, "Problem finding class/interface comment");
98 }
99 }
100
101
102
103
104
105
106
107
108 private static int findTrimmedTextUp(
109 final String[] lines,
110 final int start,
111 final String text
112 ) {
113 int found = -1;
114 for (int pos = start - 1; pos >= 0; pos -= 1) {
115 if (lines[pos].trim().equals(text)) {
116 found = pos;
117 break;
118 }
119 }
120 return found;
121 }
122
123
124
125
126
127
128
129 private static int findCommentStart(final String[] lines, final int start) {
130 return JavadocTagsCheck.findTrimmedTextUp(lines, start, "/**");
131 }
132
133
134
135
136
137
138
139 private static int findCommentEnd(final String[] lines, final int start) {
140 return JavadocTagsCheck.findTrimmedTextUp(lines, start, "*/");
141 }
142
143
144
145
146
147
148
149
150
151
152 private void findProhibited(
153 final String[] lines,
154 final int start,
155 final int cstart,
156 final int cend,
157 final String tag
158 ) {
159 final List<Integer> found =
160 this.findTagLineNum(lines, cstart, cend, tag);
161 if (!found.isEmpty()) {
162 this.log(
163 start + 1,
164 "Prohibited ''@{0}'' tag in class/interface comment",
165 tag
166 );
167 }
168 }
169
170
171
172
173
174
175
176
177
178
179 private List<Integer> findTagLineNum(
180 final String[] lines,
181 final int start,
182 final int end,
183 final String tag
184 ) {
185 final String prefix = String.format(" * @%s ", tag);
186 final List<Integer> found = new ArrayList<>(1);
187 for (int pos = start; pos <= end; pos += 1) {
188 final String line = lines[pos];
189 if (line.contains(String.format("@%s ", tag))) {
190 if (!line.trim().startsWith(prefix.trim())) {
191 this.log(
192 start + pos + 1,
193 "Line with ''@{0}'' does not start with a ''{1}''",
194 tag,
195 prefix
196 );
197 break;
198 }
199 found.add(pos);
200 }
201 }
202 return found;
203 }
204 }