Skip to content

Commit aecbb1b

Browse files
committed
8314448: Coordinate DocLint and JavaDoc to report on unknown tags
Reviewed-by: jjg
1 parent bcba5e9 commit aecbb1b

File tree

4 files changed

+176
-68
lines changed

4 files changed

+176
-68
lines changed

src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/TagletManager.java

Lines changed: 65 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,10 @@
5050
import javax.tools.JavaFileManager;
5151
import javax.tools.StandardJavaFileManager;
5252

53+
import com.sun.source.doctree.BlockTagTree;
5354
import com.sun.source.doctree.DocTree;
5455

56+
import com.sun.source.doctree.InlineTagTree;
5557
import jdk.javadoc.doclet.Doclet;
5658
import jdk.javadoc.doclet.DocletEnvironment;
5759
import jdk.javadoc.doclet.Taglet.Location;
@@ -349,89 +351,88 @@ void seenTag(String name) {
349351
* @param trees the trees containing the comments
350352
*/
351353
public void checkTags(Element element, Iterable<? extends DocTree> trees) {
352-
CommentHelper ch = utils.getCommentHelper(element);
353354
for (DocTree tag : trees) {
354-
String name = tag.getKind().tagName;
355+
String name = switch (tag.getKind()) {
356+
case UNKNOWN_INLINE_TAG -> ((InlineTagTree) tag).getTagName();
357+
case UNKNOWN_BLOCK_TAG -> ((BlockTagTree) tag).getTagName();
358+
default -> tag.getKind().tagName;
359+
};
355360
if (name == null) {
356-
continue;
361+
continue; // not a tag
357362
}
358-
if (!name.isEmpty() && name.charAt(0) == '@') {
359-
name = name.substring(1);
360-
}
361-
if (! (standardTags.contains(name) || allTaglets.containsKey(name))) { // defunct, see 8314213
362-
if (standardTagsLowercase.contains(Utils.toLowerCase(name))) {
363-
messages.warning(ch.getDocTreePath(tag), "doclet.UnknownTagLowercase", ch.getTagName(tag));
364-
continue;
365-
} else {
366-
messages.warning(ch.getDocTreePath(tag), "doclet.UnknownTag", ch.getTagName(tag));
367-
continue;
363+
if (!allTaglets.containsKey(name)) {
364+
if (!config.isDocLintSyntaxGroupEnabled()) {
365+
var ch = utils.getCommentHelper(element);
366+
if (standardTagsLowercase.contains(Utils.toLowerCase(name))) {
367+
messages.warning(ch.getDocTreePath(tag), "doclet.UnknownTagLowercase", ch.getTagName(tag));
368+
} else {
369+
messages.warning(ch.getDocTreePath(tag), "doclet.UnknownTag", ch.getTagName(tag));
370+
}
368371
}
372+
continue; // unknown tag
369373
}
370374
final Taglet taglet = allTaglets.get(name);
371-
// Check and verify tag usage
372-
if (taglet != null) {
373-
if (taglet instanceof SimpleTaglet st && !st.isEnabled()) {
374-
// taglet has been disabled
375-
return;
376-
}
375+
if (taglet instanceof SimpleTaglet st && !st.isEnabled()) {
376+
continue; // taglet has been disabled
377+
}
377378

378-
new SimpleElementVisitor14<Void, Void>() {
379-
@Override
380-
public Void visitModule(ModuleElement e, Void p) {
381-
if (!taglet.inModule()) {
382-
printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "module");
383-
}
384-
return null;
379+
// Check and verify tag usage
380+
new SimpleElementVisitor14<Void, Void>() {
381+
@Override
382+
public Void visitModule(ModuleElement e, Void p) {
383+
if (!taglet.inModule()) {
384+
printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "module");
385385
}
386+
return null;
387+
}
386388

387-
@Override
388-
public Void visitPackage(PackageElement e, Void p) {
389-
if (!taglet.inPackage()) {
390-
printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "package");
391-
}
392-
return null;
389+
@Override
390+
public Void visitPackage(PackageElement e, Void p) {
391+
if (!taglet.inPackage()) {
392+
printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "package");
393393
}
394+
return null;
395+
}
394396

395-
@Override
396-
public Void visitType(TypeElement e, Void p) {
397-
if (!taglet.inType()) {
398-
printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "class");
399-
}
400-
return null;
397+
@Override
398+
public Void visitType(TypeElement e, Void p) {
399+
if (!taglet.inType()) {
400+
printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "class");
401401
}
402+
return null;
403+
}
402404

403-
@Override
404-
public Void visitExecutable(ExecutableElement e, Void p) {
405-
if (utils.isConstructor(e) && !taglet.inConstructor()) {
406-
printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "constructor");
407-
} else if (!taglet.inMethod()) {
408-
printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "method");
409-
}
410-
return null;
405+
@Override
406+
public Void visitExecutable(ExecutableElement e, Void p) {
407+
if (utils.isConstructor(e) && !taglet.inConstructor()) {
408+
printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "constructor");
409+
} else if (!taglet.inMethod()) {
410+
printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "method");
411411
}
412+
return null;
413+
}
412414

413-
@Override
414-
public Void visitVariable(VariableElement e, Void p) {
415-
if (utils.isField(e) && !taglet.inField()) {
416-
printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "field");
417-
}
418-
return null;
415+
@Override
416+
public Void visitVariable(VariableElement e, Void p) {
417+
if (utils.isField(e) && !taglet.inField()) {
418+
printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "field");
419419
}
420+
return null;
421+
}
420422

421-
@Override
422-
public Void visitUnknown(Element e, Void p) {
423-
if (utils.isOverviewElement(e) && !taglet.inOverview()) {
424-
printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "overview");
425-
}
426-
return null;
423+
@Override
424+
public Void visitUnknown(Element e, Void p) {
425+
if (utils.isOverviewElement(e) && !taglet.inOverview()) {
426+
printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "overview");
427427
}
428+
return null;
429+
}
428430

429-
@Override
430-
protected Void defaultAction(Element e, Void p) {
431-
return null;
432-
}
433-
}.visit(element);
434-
}
431+
@Override
432+
protected Void defaultAction(Element e, Void p) {
433+
return null;
434+
}
435+
}.visit(element);
435436
}
436437
}
437438

src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclint/Checker.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1142,8 +1142,9 @@ public Void visitUnknownInlineTag(UnknownInlineTagTree tree, Void ignore) {
11421142
private void checkUnknownTag(DocTree tree, String tagName) {
11431143
// if it were a standard tag, this method wouldn't be called:
11441144
// a standard tag is never represented by Unknown{Block,Inline}TagTree
1145-
assert tree instanceof UnknownBlockTagTree
1146-
|| tree instanceof UnknownInlineTagTree;
1145+
var k = tree.getKind();
1146+
assert k == DocTree.Kind.UNKNOWN_BLOCK_TAG
1147+
|| k == DocTree.Kind.UNKNOWN_INLINE_TAG;
11471148
assert !getStandardTags().contains(tagName);
11481149
// report an unknown tag only if custom tags are set, see 8314213
11491150
if (env.customTags != null && !env.customTags.contains(tagName))

test/langtools/jdk/javadoc/doclet/testAutoLoadTaglets/TestAutoLoadTaglets.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -110,7 +110,7 @@ public class Taglet""" + i + """
110110
\simplements Taglet {
111111
@Override
112112
public Set<Location> getAllowedLocations() {
113-
return null;
113+
return Set.of();
114114
}
115115
@Override
116116
public boolean isInlineTag() {
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @bug 8314448
27+
* @library /tools/lib ../../lib
28+
* @modules jdk.compiler/com.sun.tools.javac.api
29+
* jdk.compiler/com.sun.tools.javac.main
30+
* jdk.javadoc/jdk.javadoc.internal.tool
31+
* @build toolbox.ToolBox javadoc.tester.*
32+
* @run main TestUnknownTags
33+
*/
34+
35+
import java.nio.file.Path;
36+
37+
import javadoc.tester.JavadocTester;
38+
import toolbox.ToolBox;
39+
40+
public class TestUnknownTags extends JavadocTester {
41+
42+
public static void main(String... args) throws Exception {
43+
new TestUnknownTags().runTests();
44+
}
45+
46+
private final ToolBox tb = new ToolBox();
47+
48+
// DocLint or not, there should be exactly one diagnostic message about
49+
// an unknown tag. No doubled, no "swallowed" messages, just one.
50+
@Test
51+
public void testExactlyOneMessage(Path base) throws Exception {
52+
var src = base.resolve("src");
53+
tb.writeJavaFiles(src, """
54+
package x;
55+
56+
/** @mytag */
57+
public class MyClass { }
58+
""");
59+
// don't check exit status: we don't care if it's an error or warning
60+
61+
// DocLint is explicit
62+
int i = 0;
63+
for (var check : new String[]{":all", ":none", ""}) {
64+
var outputDir = "out-DocLint-" + i++; // use separate output directories
65+
javadoc("-Xdoclint" + check,
66+
"-d", base.resolve(outputDir).toString(),
67+
"--source-path", src.toString(),
68+
"x");
69+
new OutputChecker(Output.OUT)
70+
.setExpectFound(true)
71+
.checkUnique("unknown tag");
72+
}
73+
// DocLint is default
74+
javadoc("-d", base.resolve("out").toString(),
75+
"--source-path", src.toString(),
76+
"x");
77+
new OutputChecker(Output.OUT)
78+
.setExpectFound(true)
79+
.checkUnique("unknown tag");
80+
}
81+
82+
// Disabled simple tags are treated as known tags, but aren't checked
83+
// for misuse. Additionally, they don't prevent other tags from being
84+
// checked.
85+
@Test
86+
public void testDisabledSimpleTags(Path base) throws Exception {
87+
var src = base.resolve("src");
88+
tb.writeJavaFiles(src, """
89+
package x;
90+
91+
/**
92+
* @myDisabledTag foo
93+
* @myEnabledTag bar
94+
*/
95+
public class MyClass extends RuntimeException { }
96+
""");
97+
javadoc("-d", base.resolve("out").toString(),
98+
"-sourcepath", src.toString(),
99+
"-tag", "myDisabledTag:mX:Disabled Tag", // may appear in methods; disabled
100+
"-tag", "myEnabledTag:mf:Enabled Tag", // may appear in method and fields; enabled
101+
"x");
102+
checkOutput(Output.OUT, false, "unknown tag");
103+
checkOutput(Output.OUT, false, "Tag @myDisabledTag cannot be used in class documentation");
104+
checkOutput(Output.OUT, true, "Tag @myEnabledTag cannot be used in class documentation");
105+
}
106+
}

0 commit comments

Comments
 (0)