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