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 static final Collection<String> PROHIBITED =
41 Arrays.asList("author", "version");
42
43
44
45
46 private final List<RequiredJavaDocTag> required;
47
48
49
50
51 public JavadocTagsCheck() {
52 this.required = new ArrayList<>(1);
53 }
54
55 @Override
56 public int[] getDefaultTokens() {
57 return new int[]{
58 TokenTypes.CLASS_DEF,
59 TokenTypes.INTERFACE_DEF,
60 };
61 }
62
63 @Override
64 public int[] getAcceptableTokens() {
65 return this.getDefaultTokens();
66 }
67
68 @Override
69 public int[] getRequiredTokens() {
70 return this.getDefaultTokens();
71 }
72
73 @Override
74 public void init() {
75 this.required.add(
76 new RequiredJavaDocTag(
77 "since",
78 Pattern.compile("(?<name>^ +\\* +@since)( +)(?<cont>.*)"),
79 Pattern.compile(
80 "^\\d+(\\.\\d+){1,2}(\\.[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$"
81 ),
82 this::log
83 )
84 );
85 }
86
87 @Override
88 public void visitToken(final DetailAST ast) {
89 final String[] lines = this.getLines();
90 final int start = ast.getLineNo();
91 final int cstart = JavadocTagsCheck.findCommentStart(lines, start);
92 final int cend = JavadocTagsCheck.findCommentEnd(lines, start);
93 if (cend > cstart && cstart >= 0) {
94 for (final String tag : JavadocTagsCheck.PROHIBITED) {
95 this.findProhibited(lines, start, cstart, cend, tag);
96 }
97 for (final RequiredJavaDocTag tag : this.required) {
98 tag.matchTagFormat(lines, cstart, cend);
99 }
100 } else {
101 this.log(0, "Problem finding class/interface comment");
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 private static int findCommentStart(final String[] lines, final int start) {
121 return JavadocTagsCheck.findTrimmedTextUp(lines, start, "/**");
122 }
123
124 private static int findCommentEnd(final String[] lines, final int start) {
125 return JavadocTagsCheck.findTrimmedTextUp(lines, start, "*/");
126 }
127
128 private void findProhibited(
129 final String[] lines,
130 final int start,
131 final int cstart,
132 final int cend,
133 final String tag
134 ) {
135 final List<Integer> found =
136 this.findTagLineNum(lines, cstart, cend, tag);
137 if (!found.isEmpty()) {
138 this.log(
139 start + 1,
140 "Prohibited ''@{0}'' tag in class/interface comment",
141 tag
142 );
143 }
144 }
145
146 private List<Integer> findTagLineNum(
147 final String[] lines,
148 final int start,
149 final int end,
150 final String tag
151 ) {
152 final String prefix = String.format(" * @%s ", tag);
153 final List<Integer> found = new ArrayList<>(1);
154 for (int pos = start; pos <= end; pos += 1) {
155 final String line = lines[pos];
156 if (line.contains(String.format("@%s ", tag))) {
157 if (!line.trim().startsWith(prefix.trim())) {
158 this.log(
159 start + pos + 1,
160 "Line with ''@{0}'' does not start with a ''{1}''",
161 tag,
162 prefix
163 );
164 break;
165 }
166 found.add(pos);
167 }
168 }
169 return found;
170 }
171 }