2 *******************************************************************************
\r
3 * Copyright (C) 2002-2010, International Business Machines Corporation and *
\r
4 * others. All Rights Reserved. *
\r
5 *******************************************************************************
\r
8 * This is a tool to check the tags on ICU4J files. In particular, we're looking for:
\r
10 * - methods that have no tags
\r
11 * - custom tags: @draft, @stable, @internal?
\r
12 * - standard tags: @since, @deprecated
\r
15 * '@draft ICU X.X.X'
\r
16 * '@stable ICU X.X.X'
\r
18 * '@since (don't use)'
\r
19 * '@obsolete ICU X.X.X'
\r
20 * '@deprecated to be removed in ICU X.X. [Use ...]'
\r
22 * flags names of classes and their members that have no tags or incorrect syntax.
\r
24 * Requires JDK 1.4 or later
\r
26 * Use build.xml 'checktags' ant target, or
\r
27 * run from directory containing CheckTags.class as follows:
\r
28 * javadoc -classpath ${JAVA_HOME}/lib/tools.jar -doclet CheckTags -sourcepath ${ICU4J_src} [packagenames]
\r
31 package com.ibm.icu.dev.tool.docs;
\r
33 import com.sun.javadoc.ClassDoc;
\r
34 import com.sun.javadoc.ConstructorDoc;
\r
35 import com.sun.javadoc.ExecutableMemberDoc;
\r
36 import com.sun.javadoc.ProgramElementDoc;
\r
37 import com.sun.javadoc.RootDoc;
\r
38 import com.sun.javadoc.Tag;
\r
40 public class CheckTags {
\r
45 DocStack stack = new DocStack();
\r
48 private String header;
\r
49 private boolean printed;
\r
50 private boolean reportError;
\r
51 private int errorCount;
\r
53 public void reset(String header, boolean reportError) {
\r
54 this.header = header;
\r
55 this.printed = false;
\r
56 this.errorCount = 0;
\r
57 this.reportError = reportError;
\r
59 public String toString() {
\r
61 " printed: " + printed +
\r
62 " reportError: " + reportError +
\r
63 " errorCount: " + errorCount;
\r
68 private DocNode[] stack;
\r
70 private boolean newline;
\r
72 public void push(String header, boolean reportError) {
\r
73 if (stack == null) {
\r
74 stack = new DocNode[5];
\r
76 if (index == stack.length) {
\r
77 DocNode[] temp = new DocNode[stack.length * 2];
\r
78 System.arraycopy(stack, 0, temp, 0, index);
\r
82 if (stack[index] == null) {
\r
83 stack[index] = new DocNode();
\r
85 // System.out.println("reset [" + index + "] header: " + header + " report: " + reportError);
\r
86 stack[index++].reset(header, reportError);
\r
91 throw new IndexOutOfBoundsException();
\r
95 int ec = stack[index].errorCount; // index already decremented
\r
96 if (ec > 0 || index == 0) { // always report for outermost element
\r
97 if (stack[index].reportError) {
\r
98 output("(" + ec + (ec == 1 ? " error" : " errors") + ")", false, true, index);
\r
101 // propagate to parent
\r
103 stack[index-1].errorCount += ec;
\r
107 System.out.println(); // always since we always report number of errors
\r
111 public void output(String msg, boolean error, boolean newline) {
\r
112 output(msg, error, newline, index-1);
\r
115 void output(String msg, boolean error, boolean newline, int ix) {
\r
116 DocNode last = stack[ix];
\r
118 last.errorCount += 1;
\r
121 boolean show = !brief || last.reportError;
\r
122 // boolean nomsg = show && brief && error;
\r
123 // System.out.println(">>> " + last + " error: " + error + " show: " + show + " nomsg: " + nomsg);
\r
126 if (isShort || (brief && error)) {
\r
127 msg = null; // nuke error messages if we're brief, just report headers and totals
\r
129 for (int i = 0; i <= ix;) {
\r
130 DocNode n = stack[i];
\r
132 if (msg != null || !last.printed) { // since index > 0 last is not null
\r
133 if (this.newline && i == 0) {
\r
134 System.out.println();
\r
135 this.newline = false;
\r
137 System.out.print(" ");
\r
141 System.out.print(n.header);
\r
143 this.newline = true;
\r
149 if (index == 0 && this.newline) {
\r
150 System.out.println();
\r
153 System.out.print("*** ");
\r
155 System.out.print(msg);
\r
159 this.newline = newline;
\r
163 public static boolean start(RootDoc root) {
\r
164 return new CheckTags(root).run();
\r
167 public static int optionLength(String option) {
\r
168 if (option.equals("-log")) {
\r
170 } else if (option.equals("-brief")) {
\r
172 } else if (option.equals("-short")) {
\r
178 CheckTags(RootDoc root) {
\r
181 String[][] options = root.options();
\r
182 for (int i = 0; i < options.length; ++i) {
\r
183 String opt = options[i][0];
\r
184 if (opt.equals("-log")) {
\r
186 } else if (opt.equals("-brief")) {
\r
188 } else if (opt.equals("-short")) {
\r
189 this.isShort = true;
\r
195 doDocs(root.classes(), "Package", true);
\r
199 static final String[] tagKinds = {
\r
200 "@internal", "@draft", "@stable", "@since", "@deprecated", "@author", "@see", "@version",
\r
201 "@param", "@return", "@throws", "@obsolete", "@exception", "@serial", "@provisional"
\r
204 static final int UNKNOWN = -1;
\r
205 static final int INTERNAL = 0;
\r
206 static final int DRAFT = 1;
\r
207 static final int STABLE = 2;
\r
208 static final int SINCE = 3;
\r
209 static final int DEPRECATED = 4;
\r
210 static final int AUTHOR = 5;
\r
211 static final int SEE = 6;
\r
212 static final int VERSION = 7;
\r
213 static final int PARAM = 8;
\r
214 static final int RETURN = 9;
\r
215 static final int THROWS = 10;
\r
216 static final int OBSOLETE = 11;
\r
217 static final int EXCEPTION = 12;
\r
218 static final int SERIAL = 13;
\r
219 static final int PROVISIONAL = 14;
\r
221 static int tagKindIndex(String kind) {
\r
222 for (int i = 0; i < tagKinds.length; ++i) {
\r
223 if (kind.equals(tagKinds[i])) {
\r
230 static final String[] icuTagNames = {
\r
231 "@icu", "@icunote", "@icuenhanced"
\r
233 static final int ICU = 0;
\r
234 static final int ICUNOTE = 1;
\r
235 static final int ICUENHANCED = 2;
\r
236 static int icuTagIndex(String name) {
\r
237 for (int i = 0; i < icuTagNames.length; ++i) {
\r
238 if (icuTagNames[i].equals(name)) {
\r
245 boolean newline = false;
\r
247 void output(String msg, boolean error, boolean newline) {
\r
248 stack.output(msg, error, newline);
\r
252 output(null, false, false);
\r
256 output(null, false, true);
\r
259 void log(String msg) {
\r
260 output(msg, false, false);
\r
263 void logln(String msg) {
\r
264 output(msg, false, true);
\r
267 void err(String msg) {
\r
268 output(msg, true, false);
\r
271 void errln(String msg) {
\r
272 output(msg, true, true);
\r
275 void tagErr(String msg, Tag tag) {
\r
276 // Tag.position() requires JDK 1.4, build.xml tests for this
\r
277 if (msg.length() > 0) {
\r
280 errln(msg + tag.toString() + " [" + tag.position() + "]");
\r
283 void tagErr(Tag tag) {
\r
287 void doDocs(ProgramElementDoc[] docs, String header, boolean reportError) {
\r
288 if (docs != null && docs.length > 0) {
\r
289 stack.push(header, reportError);
\r
290 for (int i = 0; i < docs.length; ++i) {
\r
297 void doDoc(ProgramElementDoc doc) {
\r
298 if (doc != null && (doc.isPublic() || doc.isProtected())
\r
299 && !(doc instanceof ConstructorDoc && ((ConstructorDoc)doc).isSynthetic())) {
\r
301 // unfortunately, in JDK 1.4.1 MemberDoc.isSynthetic is not properly implemented for
\r
302 // synthetic constructors. So you'll have to live with spurious errors or 'implement'
\r
303 // the synthetic constructors...
\r
305 boolean isClass = doc.isClass() || doc.isInterface();
\r
307 if (!isShort || isClass) {
\r
312 header += (isClass ? doc.qualifiedName() : doc.name());
\r
313 if (doc instanceof ExecutableMemberDoc) {
\r
314 header += ((ExecutableMemberDoc)doc).flatSignature();
\r
316 if (!isShort || isClass) {
\r
319 stack.push(header, isClass);
\r
323 boolean recurse = doTags(doc);
\r
324 if (recurse && isClass) {
\r
325 ClassDoc cdoc = (ClassDoc)doc;
\r
326 doDocs(cdoc.fields(), "Fields", !brief);
\r
327 doDocs(cdoc.constructors(), "Constructors", !brief);
\r
328 doDocs(cdoc.methods(), "Methods", !brief);
\r
334 /** Return true if subelements of this doc should be checked */
\r
335 boolean doTags(ProgramElementDoc doc) {
\r
336 boolean foundRequiredTag = false;
\r
337 boolean foundDraftTag = false;
\r
338 boolean foundProvisionalTag = false;
\r
339 boolean foundDeprecatedTag = false;
\r
340 boolean foundObsoleteTag = false;
\r
341 boolean foundInternalTag = false;
\r
342 boolean foundStableTag = false;
\r
343 boolean retainAll = false;
\r
345 // first check inline tags
\r
346 for (Tag tag : doc.inlineTags()) {
\r
347 int index = icuTagIndex(tag.name());
\r
349 String text = tag.text().trim();
\r
352 if (doc.isClass() || doc.isInterface()) {
\r
353 tagErr("tag should appear only in member docs", tag);
\r
357 if (text.length() > 0) {
\r
358 tagErr("tag should not contain text", tag);
\r
361 case ICUENHANCED: {
\r
362 if (text.length() == 0) {
\r
363 tagErr("text should name related jdk class", tag);
\r
365 if (!(doc.isClass() || doc.isInterface())) {
\r
366 tagErr("tag should appear only in class/interface docs", tag);
\r
370 tagErr("unrecognized tag index for tag", tag);
\r
376 // next check regular tags
\r
377 for (Tag tag : doc.tags()) {
\r
378 String kind = tag.kind();
\r
379 int ix = tagKindIndex(kind);
\r
383 errln("unknown kind: " + kind);
\r
387 foundRequiredTag = true;
\r
388 foundInternalTag = true;
\r
392 foundRequiredTag = true;
\r
393 foundDraftTag = true;
\r
394 if (tag.text().indexOf("ICU 2.8") != -1 &&
\r
395 tag.text().indexOf("(retain") == -1) { // catch both retain and retainAll
\r
399 if (tag.text().indexOf("ICU") != 0) {
\r
403 retainAll |= (tag.text().indexOf("(retainAll)") != -1);
\r
407 foundProvisionalTag = true;
\r
411 foundDeprecatedTag = true;
\r
412 if (tag.text().indexOf("ICU") == 0) {
\r
413 foundRequiredTag = true;
\r
418 if (tag.text().indexOf("ICU") != 0) {
\r
421 foundObsoleteTag = true;
\r
422 foundRequiredTag = true;
\r
427 String text = tag.text();
\r
428 if (text.length() != 0 && text.indexOf("ICU") != 0) {
\r
431 foundRequiredTag = true;
\r
432 foundStableTag = true;
\r
441 logln("You really ought to use @throws, you know... :-)");
\r
456 errln("unknown index: " + ix);
\r
459 if (!foundRequiredTag) {
\r
460 errln("missing required tag [" + doc.position() + "]");
\r
462 if (foundInternalTag && !foundDeprecatedTag) {
\r
463 errln("internal tag missing deprecated");
\r
465 if (foundDraftTag && !(foundDeprecatedTag || foundProvisionalTag)) {
\r
466 errln("draft tag missing deprecated or provisional");
\r
468 if (foundObsoleteTag && !foundDeprecatedTag) {
\r
469 errln("obsolete tag missing deprecated");
\r
471 if (foundStableTag && foundDeprecatedTag) {
\r
472 logln("stable deprecated");
\r