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
24
25
26
27
28 public final class JavadocFirstLineCheck extends AbstractCheck {
29
30 @Override
31 public int[] getDefaultTokens() {
32 return new int[] {
33 TokenTypes.PACKAGE_DEF,
34 TokenTypes.CLASS_DEF,
35 TokenTypes.INTERFACE_DEF,
36 TokenTypes.ANNOTATION_DEF,
37 TokenTypes.ANNOTATION_FIELD_DEF,
38 TokenTypes.ENUM_DEF,
39 TokenTypes.ENUM_CONSTANT_DEF,
40 TokenTypes.VARIABLE_DEF,
41 TokenTypes.CTOR_DEF,
42 TokenTypes.METHOD_DEF,
43 };
44 }
45
46 @Override
47 public int[] getAcceptableTokens() {
48 return this.getDefaultTokens();
49 }
50
51 @Override
52 public int[] getRequiredTokens() {
53 return this.getDefaultTokens();
54 }
55
56 @Override
57 public void visitToken(final DetailAST ast) {
58 final String[] lines = this.getLines();
59 final int start = JavadocFirstLineCheck.findOpeningLine(
60 lines, ast.getLineNo() - 1
61 );
62 if (start >= 0 && JavadocFirstLineCheck.belongsToNode(ast, start)
63 && JavadocFirstLineCheck.hasTextAfterOpening(lines[start])
64 && !JavadocFirstLineCheck.hasClosingOnSameLine(lines[start])) {
65 this.log(start + 1, "No text allowed on the first line of Javadoc");
66 }
67 }
68
69
70
71
72
73
74
75 private static int findOpeningLine(final String[] lines, final int below) {
76 int found = -1;
77 for (int pos = below - 1; pos >= 0; pos -= 1) {
78 final String trimmed = lines[pos].trim();
79 if (trimmed.startsWith("/**")) {
80 found = pos;
81 break;
82 }
83 if (!trimmed.isEmpty() && !trimmed.startsWith("*")
84 && !trimmed.endsWith("*/")) {
85 break;
86 }
87 }
88 return found;
89 }
90
91
92
93
94
95
96
97 private static boolean belongsToNode(final DetailAST node, final int start) {
98 final DetailAST previous = node.getPreviousSibling();
99 boolean owns = true;
100 if (previous != null) {
101 owns = start + 1 > previous.getLineNo();
102 }
103 return owns;
104 }
105
106
107
108
109
110
111 private static boolean hasTextAfterOpening(final String line) {
112 final String trimmed = line.trim();
113 final String rest = trimmed.substring("/**".length()).trim();
114 return !rest.isEmpty() && !"/".equals(rest);
115 }
116
117
118
119
120
121
122 private static boolean hasClosingOnSameLine(final String line) {
123 return line.trim().endsWith("*/");
124 }
125 }