blob: e565b63362d8eea8badc83ad7c5b45a2988c03f8 [file] [log] [blame]
/**
*******************************************************************************
* Copyright (C) 2002-2010, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
/**
* This is a tool to check the tags on ICU4J files. In particular, we're looking for:
*
* - methods that have no tags
* - custom tags: @draft, @stable, @internal?
* - standard tags: @since, @deprecated
*
* Syntax of tags:
* '@draft ICU X.X.X'
* '@stable ICU X.X.X'
* '@internal'
* '@since (don't use)'
* '@obsolete ICU X.X.X'
* '@deprecated to be removed in ICU X.X. [Use ...]'
*
* flags names of classes and their members that have no tags or incorrect syntax.
*
* Requires JDK 1.4 or later
*
* Use build.xml 'checktags' ant target, or
* run from directory containing CheckTags.class as follows:
* javadoc -classpath ${JAVA_HOME}/lib/tools.jar -doclet CheckTags -sourcepath ${ICU4J_src} [packagenames]
*/
package com.ibm.icu.dev.tool.docs;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.ConstructorDoc;
import com.sun.javadoc.ExecutableMemberDoc;
import com.sun.javadoc.ProgramElementDoc;
import com.sun.javadoc.RootDoc;
import com.sun.javadoc.Tag;
public class CheckTags {
RootDoc root;
boolean log;
boolean brief;
boolean isShort;
DocStack stack = new DocStack();
class DocNode {
private String header;
private boolean printed;
private boolean reportError;
private int errorCount;
public void reset(String header, boolean reportError) {
this.header = header;
this.printed = false;
this.errorCount = 0;
this.reportError = reportError;
}
public String toString() {
return header +
" printed: " + printed +
" reportError: " + reportError +
" errorCount: " + errorCount;
}
}
class DocStack {
private DocNode[] stack;
private int index;
private boolean newline;
public void push(String header, boolean reportError) {
if (stack == null) {
stack = new DocNode[5];
} else {
if (index == stack.length) {
DocNode[] temp = new DocNode[stack.length * 2];
System.arraycopy(stack, 0, temp, 0, index);
stack = temp;
}
}
if (stack[index] == null) {
stack[index] = new DocNode();
}
// System.out.println("reset [" + index + "] header: " + header + " report: " + reportError);
stack[index++].reset(header, reportError);
}
public void pop() {
if (index == 0) {
throw new IndexOutOfBoundsException();
}
--index;
int ec = stack[index].errorCount; // index already decremented
if (ec > 0 || index == 0) { // always report for outermost element
if (stack[index].reportError) {
output("(" + ec + (ec == 1 ? " error" : " errors") + ")", false, true, index);
}
// propagate to parent
if (index > 0) {
stack[index-1].errorCount += ec;
}
}
if (index == 0) {
System.out.println(); // always since we always report number of errors
}
}
public void output(String msg, boolean error, boolean newline) {
output(msg, error, newline, index-1);
}
void output(String msg, boolean error, boolean newline, int ix) {
DocNode last = stack[ix];
if (error) {
last.errorCount += 1;
}
boolean show = !brief || last.reportError;
// boolean nomsg = show && brief && error;
// System.out.println(">>> " + last + " error: " + error + " show: " + show + " nomsg: " + nomsg);
if (show) {
if (isShort || (brief && error)) {
msg = null; // nuke error messages if we're brief, just report headers and totals
}
for (int i = 0; i <= ix;) {
DocNode n = stack[i];
if (n.printed) {
if (msg != null || !last.printed) { // since index > 0 last is not null
if (this.newline && i == 0) {
System.out.println();
this.newline = false;
}
System.out.print(" ");
}
++i;
} else {
System.out.print(n.header);
n.printed = true;
this.newline = true;
i = 0;
}
}
if (msg != null) {
if (index == 0 && this.newline) {
System.out.println();
}
if (error) {
System.out.print("*** ");
}
System.out.print(msg);
}
}
this.newline = newline;
}
}
public static boolean start(RootDoc root) {
return new CheckTags(root).run();
}
public static int optionLength(String option) {
if (option.equals("-log")) {
return 1;
} else if (option.equals("-brief")) {
return 1;
} else if (option.equals("-short")) {
return 1;
}
return 0;
}
CheckTags(RootDoc root) {
this.root = root;
String[][] options = root.options();
for (int i = 0; i < options.length; ++i) {
String opt = options[i][0];
if (opt.equals("-log")) {
this.log = true;
} else if (opt.equals("-brief")) {
this.brief = true;
} else if (opt.equals("-short")) {
this.isShort = true;
}
}
}
boolean run() {
doDocs(root.classes(), "Package", true);
return false;
}
static final String[] tagKinds = {
"@internal", "@draft", "@stable", "@since", "@deprecated", "@author", "@see", "@version",
"@param", "@return", "@throws", "@obsolete", "@exception", "@serial", "@provisional"
};
static final int UNKNOWN = -1;
static final int INTERNAL = 0;
static final int DRAFT = 1;
static final int STABLE = 2;
static final int SINCE = 3;
static final int DEPRECATED = 4;
static final int AUTHOR = 5;
static final int SEE = 6;
static final int VERSION = 7;
static final int PARAM = 8;
static final int RETURN = 9;
static final int THROWS = 10;
static final int OBSOLETE = 11;
static final int EXCEPTION = 12;
static final int SERIAL = 13;
static final int PROVISIONAL = 14;
static int tagKindIndex(String kind) {
for (int i = 0; i < tagKinds.length; ++i) {
if (kind.equals(tagKinds[i])) {
return i;
}
}
return UNKNOWN;
}
static final String[] icuTagNames = {
"@icu", "@icunote", "@icuenhanced"
};
static final int ICU = 0;
static final int ICUNOTE = 1;
static final int ICUENHANCED = 2;
static int icuTagIndex(String name) {
for (int i = 0; i < icuTagNames.length; ++i) {
if (icuTagNames[i].equals(name)) {
return i;
}
}
return UNKNOWN;
}
boolean newline = false;
void output(String msg, boolean error, boolean newline) {
stack.output(msg, error, newline);
}
void log() {
output(null, false, false);
}
void logln() {
output(null, false, true);
}
void log(String msg) {
output(msg, false, false);
}
void logln(String msg) {
output(msg, false, true);
}
void err(String msg) {
output(msg, true, false);
}
void errln(String msg) {
output(msg, true, true);
}
void tagErr(String msg, Tag tag) {
// Tag.position() requires JDK 1.4, build.xml tests for this
if (msg.length() > 0) {
msg += ": ";
}
errln(msg + tag.toString() + " [" + tag.position() + "]");
};
void tagErr(Tag tag) {
tagErr("", tag);
}
void doDocs(ProgramElementDoc[] docs, String header, boolean reportError) {
if (docs != null && docs.length > 0) {
stack.push(header, reportError);
for (int i = 0; i < docs.length; ++i) {
doDoc(docs[i]);
}
stack.pop();
}
}
void doDoc(ProgramElementDoc doc) {
if (doc != null && (doc.isPublic() || doc.isProtected())
&& !(doc instanceof ConstructorDoc && ((ConstructorDoc)doc).isSynthetic())) {
// unfortunately, in JDK 1.4.1 MemberDoc.isSynthetic is not properly implemented for
// synthetic constructors. So you'll have to live with spurious errors or 'implement'
// the synthetic constructors...
boolean isClass = doc.isClass() || doc.isInterface();
String header;
if (!isShort || isClass) {
header = "--- ";
} else {
header = "";
}
header += (isClass ? doc.qualifiedName() : doc.name());
if (doc instanceof ExecutableMemberDoc) {
header += ((ExecutableMemberDoc)doc).flatSignature();
}
if (!isShort || isClass) {
header += " ---";
}
stack.push(header, isClass);
if (log) {
logln();
}
boolean recurse = doTags(doc);
if (recurse && isClass) {
ClassDoc cdoc = (ClassDoc)doc;
doDocs(cdoc.fields(), "Fields", !brief);
doDocs(cdoc.constructors(), "Constructors", !brief);
doDocs(cdoc.methods(), "Methods", !brief);
}
stack.pop();
}
}
/** Return true if subelements of this doc should be checked */
boolean doTags(ProgramElementDoc doc) {
boolean foundRequiredTag = false;
boolean foundDraftTag = false;
boolean foundProvisionalTag = false;
boolean foundDeprecatedTag = false;
boolean foundObsoleteTag = false;
boolean foundInternalTag = false;
boolean foundStableTag = false;
boolean retainAll = false;
// first check inline tags
for (Tag tag : doc.inlineTags()) {
int index = icuTagIndex(tag.name());
if (index >= 0) {
String text = tag.text().trim();
switch (index) {
case ICU: {
if (doc.isClass() || doc.isInterface()) {
tagErr("tag should appear only in member docs", tag);
}
} break;
case ICUNOTE: {
if (text.length() > 0) {
tagErr("tag should not contain text", tag);
}
} break;
case ICUENHANCED: {
if (text.length() == 0) {
tagErr("text should name related jdk class", tag);
}
if (!(doc.isClass() || doc.isInterface())) {
tagErr("tag should appear only in class/interface docs", tag);
}
} break;
default:
tagErr("unrecognized tag index for tag", tag);
break;
}
}
}
// next check regular tags
for (Tag tag : doc.tags()) {
String kind = tag.kind();
int ix = tagKindIndex(kind);
switch (ix) {
case UNKNOWN:
errln("unknown kind: " + kind);
break;
case INTERNAL:
foundRequiredTag = true;
foundInternalTag = true;
break;
case DRAFT:
foundRequiredTag = true;
foundDraftTag = true;
if (tag.text().indexOf("ICU 2.8") != -1 &&
tag.text().indexOf("(retain") == -1) { // catch both retain and retainAll
tagErr(tag);
break;
}
if (tag.text().indexOf("ICU") != 0) {
tagErr(tag);
break;
}
retainAll |= (tag.text().indexOf("(retainAll)") != -1);
break;
case PROVISIONAL:
foundProvisionalTag = true;
break;
case DEPRECATED:
foundDeprecatedTag = true;
if (tag.text().indexOf("ICU") == 0) {
foundRequiredTag = true;
}
break;
case OBSOLETE:
if (tag.text().indexOf("ICU") != 0) {
tagErr(tag);
}
foundObsoleteTag = true;
foundRequiredTag = true;
break;
case STABLE:
{
String text = tag.text();
if (text.length() != 0 && text.indexOf("ICU") != 0) {
tagErr(tag);
}
foundRequiredTag = true;
foundStableTag = true;
}
break;
case SINCE:
tagErr(tag);
break;
case EXCEPTION:
logln("You really ought to use @throws, you know... :-)");
case AUTHOR:
case SEE:
case PARAM:
case RETURN:
case THROWS:
case SERIAL:
break;
case VERSION:
tagErr(tag);
break;
default:
errln("unknown index: " + ix);
}
}
if (!foundRequiredTag) {
errln("missing required tag [" + doc.position() + "]");
}
if (foundInternalTag && !foundDeprecatedTag) {
errln("internal tag missing deprecated");
}
if (foundDraftTag && !(foundDeprecatedTag || foundProvisionalTag)) {
errln("draft tag missing deprecated or provisional");
}
if (foundObsoleteTag && !foundDeprecatedTag) {
errln("obsolete tag missing deprecated");
}
if (foundStableTag && foundDeprecatedTag) {
logln("stable deprecated");
}
return !retainAll;
}
}