8280488: doclint reference checks withstand warning suppression
Reviewed-by: darcy
This commit is contained in:
parent
74921e8422
commit
ee3be0bb56
src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclint
test/langtools/jdk/javadoc/tool/doclint
@ -41,6 +41,7 @@ import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.lang.model.SourceVersion;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ElementKind;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
@ -952,11 +953,31 @@ public class Checker extends DocTreePathScanner<Void, Void> {
|
||||
@Override @DefinedBy(Api.COMPILER_TREE)
|
||||
public Void visitReference(ReferenceTree tree, Void ignore) {
|
||||
Element e = env.trees.getElement(getCurrentPath());
|
||||
if (e == null)
|
||||
env.messages.error(REFERENCE, tree, "dc.ref.not.found");
|
||||
if (e == null) {
|
||||
reportBadReference(tree);
|
||||
}
|
||||
return super.visitReference(tree, ignore);
|
||||
}
|
||||
|
||||
private void reportBadReference(ReferenceTree tree) {
|
||||
if (!env.strictReferenceChecks) {
|
||||
String refSig = tree.getSignature();
|
||||
int sep = refSig.indexOf("/");
|
||||
if (sep > 0) {
|
||||
String moduleName = refSig.substring(0, sep);
|
||||
if (SourceVersion.isName(moduleName)) {
|
||||
Element m = env.elements.getModuleElement(moduleName);
|
||||
if (m == null) {
|
||||
env.messages.warning(REFERENCE, tree, "dc.ref.in.missing.module", moduleName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
env.messages.error(REFERENCE, tree, "dc.ref.not.found");
|
||||
}
|
||||
|
||||
@Override @DefinedBy(Api.COMPILER_TREE)
|
||||
public Void visitReturn(ReturnTree tree, Void ignore) {
|
||||
if (foundReturn) {
|
||||
|
@ -274,6 +274,14 @@ public class DocLint extends com.sun.tools.doclint.DocLint {
|
||||
|
||||
// <editor-fold defaultstate="collapsed" desc="Embedding API">
|
||||
|
||||
/**
|
||||
* Initialize DocLint for use with a {@code JavacTask}.
|
||||
* {@link Env#strictReferenceChecks Strict reference checks} are <em>not</em> enabled by default.
|
||||
*
|
||||
* @param task the task
|
||||
* @param args arguments to configure DocLint
|
||||
* @param addTaskListener whether or not to register a {@code TaskListener} to invoke DocLint
|
||||
*/
|
||||
public void init(JavacTask task, String[] args, boolean addTaskListener) {
|
||||
env = new Env();
|
||||
env.init(task);
|
||||
@ -319,9 +327,19 @@ public class DocLint extends com.sun.tools.doclint.DocLint {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize DocLint with the given utility objects and arguments.
|
||||
* {@link Env#strictReferenceChecks Strict reference checks} <em>are</em> enabled by default.
|
||||
*
|
||||
* @param trees the {@code DocTrees} utility class
|
||||
* @param elements the {@code Elements} utility class
|
||||
* @param types the {@code Types} utility class
|
||||
* @param args arguments to configure DocLint
|
||||
*/
|
||||
public void init(DocTrees trees, Elements elements, Types types, String... args) {
|
||||
env = new Env();
|
||||
env.init(trees, elements, types);
|
||||
env.strictReferenceChecks = true;
|
||||
processArgs(env, args);
|
||||
|
||||
checker = new Checker(env);
|
||||
|
@ -108,6 +108,20 @@ public class Env {
|
||||
Set<Pattern> includePackages;
|
||||
Set<Pattern> excludePackages;
|
||||
|
||||
/**
|
||||
* How to handle bad references.
|
||||
*
|
||||
* If {@code false}, a reference into a module that is not
|
||||
* in the module graph will just be reported as a warning.
|
||||
* All other bad references will be reported as errors.
|
||||
* This is the desired behavior for javac.
|
||||
*
|
||||
* If {@code true}, all bad references will be reported as
|
||||
* errors. This is the desired behavior for javadoc.
|
||||
*
|
||||
*/
|
||||
boolean strictReferenceChecks = false;
|
||||
|
||||
// Utility classes
|
||||
DocTrees trees;
|
||||
Elements elements;
|
||||
|
@ -61,6 +61,7 @@ dc.no.alt.attr.for.image = no "alt" attribute for image
|
||||
dc.no.summary.or.caption.for.table=no caption for table
|
||||
dc.param.name.not.found = @param name not found
|
||||
dc.ref.not.found = reference not found
|
||||
dc.ref.in.missing.module = module for reference not found: {0}
|
||||
dc.return.not.first = '{@return} not at beginning of description
|
||||
dc.service.not.found = service-type not found
|
||||
dc.tag.code.within.code = '{@code} within <code>
|
||||
|
@ -0,0 +1,255 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8280688
|
||||
* @summary doclint reference checks withstand warning suppression
|
||||
* @library /tools/lib ../../lib
|
||||
* @modules jdk.compiler/com.sun.tools.javac.api
|
||||
* jdk.compiler/com.sun.tools.javac.main
|
||||
* jdk.javadoc/jdk.javadoc.internal.api
|
||||
* jdk.javadoc/jdk.javadoc.internal.tool
|
||||
* @build toolbox.JavacTask toolbox.JavadocTask toolbox.TestRunner toolbox.ToolBox
|
||||
* @run main DocLintReferencesTest
|
||||
*/
|
||||
|
||||
import toolbox.JavacTask;
|
||||
import toolbox.JavadocTask;
|
||||
import toolbox.Task;
|
||||
import toolbox.TestRunner;
|
||||
import toolbox.ToolBox;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Combo test for how javac and javadoc handle {@code @see MODULE/TYPE}
|
||||
* for different combinations of MODULE and TYPE, with and without
|
||||
* {@code @SuppressWarnings("doclint") }.
|
||||
*
|
||||
* Generally, in javac, references to unknown elements are reported
|
||||
* as suppressible warnings if the module is not resolved in the module graph.
|
||||
* Otherwise, in both javac and javadoc, any issues with references
|
||||
* are reported as errors.
|
||||
*
|
||||
* This allows references to other modules to appear in documentation comments
|
||||
* without causing a hard error if the modules are not available at compile-time.
|
||||
*/
|
||||
public class DocLintReferencesTest extends TestRunner {
|
||||
|
||||
public static void main(String... args) throws Exception {
|
||||
DocLintReferencesTest t = new DocLintReferencesTest();
|
||||
t.runTests();
|
||||
}
|
||||
|
||||
DocLintReferencesTest() {
|
||||
super(System.err);
|
||||
}
|
||||
|
||||
private final ToolBox tb = new ToolBox();
|
||||
|
||||
enum SuppressKind { NO, YES }
|
||||
enum ModuleKind { NONE, BAD, NOT_FOUND, GOOD }
|
||||
enum TypeKind { NONE, BAD, NOT_FOUND, GOOD }
|
||||
|
||||
@Test
|
||||
public void comboTest () {
|
||||
for (SuppressKind sk : SuppressKind.values() ) {
|
||||
for (ModuleKind mk : ModuleKind.values() ) {
|
||||
for (TypeKind tk: TypeKind.values() ) {
|
||||
if (mk == ModuleKind.NONE && tk == TypeKind.NONE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
test(sk, mk, tk);
|
||||
} catch (Throwable e) {
|
||||
error("Exception " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void test(SuppressKind sk, ModuleKind mk, TypeKind tk) throws Exception {
|
||||
out.println();
|
||||
out.println("*** Test SuppressKind:" + sk + " ModuleKind: " + mk + " TypeKind: " + tk);
|
||||
Path base = Path.of(sk + "-" + mk + "-" + tk);
|
||||
|
||||
String sw = switch (sk) {
|
||||
case NO -> "";
|
||||
case YES -> "@SuppressWarnings(\"doclint\")";
|
||||
};
|
||||
String m = switch (mk) {
|
||||
case NONE -> "";
|
||||
case BAD -> "bad-name/";
|
||||
case NOT_FOUND -> "not.found/";
|
||||
case GOOD -> "java.base/";
|
||||
};
|
||||
String t = switch (tk) {
|
||||
case NONE -> "";
|
||||
case BAD -> "bad-name";
|
||||
case NOT_FOUND -> "java.lang.NotFound";
|
||||
case GOOD -> "java.lang.Object";
|
||||
};
|
||||
|
||||
Path src = base.resolve("src");
|
||||
tb.writeJavaFiles(src, """
|
||||
package p;
|
||||
/**
|
||||
* Comment.
|
||||
* @see #M##T#
|
||||
*/
|
||||
#SW#
|
||||
public class C {
|
||||
private C() { }
|
||||
}
|
||||
"""
|
||||
.replace("#M#", m)
|
||||
.replace("#T#", t)
|
||||
.replace("#SW#", sw));
|
||||
|
||||
testJavac(sk, mk, tk, base, src);
|
||||
testJavadoc(sk, mk, tk, base, src);
|
||||
}
|
||||
|
||||
void testJavac(SuppressKind sk, ModuleKind mk, TypeKind tk, Path base, Path src) throws Exception {
|
||||
Files.createDirectories(base.resolve("classes"));
|
||||
|
||||
out.println("javac:");
|
||||
try {
|
||||
String s = predictOutput(sk, mk, tk, false);
|
||||
Task.Expect e = s.isEmpty() ? Task.Expect.SUCCESS : Task.Expect.FAIL;
|
||||
|
||||
String o = new JavacTask(tb)
|
||||
.outdir(base.resolve("classes"))
|
||||
.options("-Xdoclint:all/protected", "-Werror")
|
||||
.files(tb.findJavaFiles(src))
|
||||
.run(e)
|
||||
.writeAll()
|
||||
.getOutput(Task.OutputKind.DIRECT);
|
||||
|
||||
checkOutput(s, o);
|
||||
|
||||
} catch (Throwable t) {
|
||||
error("Error: " + t);
|
||||
}
|
||||
out.println();
|
||||
}
|
||||
|
||||
void testJavadoc(SuppressKind sk, ModuleKind mk, TypeKind tk, Path base, Path src) throws Exception {
|
||||
Files.createDirectories(base.resolve("api"));
|
||||
|
||||
out.println("javadoc:");
|
||||
try {
|
||||
String s = predictOutput(sk, mk, tk, true);
|
||||
Task.Expect e = s.isEmpty() ? Task.Expect.SUCCESS : Task.Expect.FAIL;
|
||||
|
||||
String o = new JavadocTask(tb)
|
||||
.outdir(base.resolve("api"))
|
||||
.options("-Xdoclint", "-Werror", "-quiet", "-sourcepath", src.toString(), "p")
|
||||
.run(e)
|
||||
.writeAll()
|
||||
.getOutput(Task.OutputKind.DIRECT);
|
||||
|
||||
checkOutput(s, o);
|
||||
|
||||
} catch (Throwable t) {
|
||||
error("Error: " + t);
|
||||
}
|
||||
out.println();
|
||||
}
|
||||
|
||||
private static final String ERROR_UNEXPECTED_TEXT = "error: unexpected text";
|
||||
private static final String ERROR_REFERENCE_NOT_FOUND = "error: reference not found";
|
||||
private static final String WARNING_MODULE_FOR_REFERENCE_NOT_FOUND = "warning: module for reference not found: not.found";
|
||||
private static final String EMPTY = "";
|
||||
|
||||
/**
|
||||
* Returns the expected diagnostic, if any, based on the parameters of the test case.
|
||||
*
|
||||
* The "interesting" cases are those for which the module name is not found,
|
||||
* in which case an error for "reference not found" is reduced to warning,
|
||||
* which may be suppressed.
|
||||
*
|
||||
* @param sk whether @SuppressWarnings is present of not
|
||||
* @param mk the kind of module in the reference
|
||||
* @param tk the kind of class or interface name in the reference
|
||||
* @param strict whether all "not found" references are errors,
|
||||
* or just warnings if the module name is not found
|
||||
* @return a diagnostic string, or an empty string if no diagnostic should be generated
|
||||
*/
|
||||
String predictOutput(SuppressKind sk, ModuleKind mk, TypeKind tk, boolean strict) {
|
||||
return switch (mk) {
|
||||
case NONE -> switch(tk) {
|
||||
case NONE -> throw new Error("should not happen"); // filtered out in combo loops
|
||||
case BAD -> ERROR_UNEXPECTED_TEXT;
|
||||
case NOT_FOUND -> ERROR_REFERENCE_NOT_FOUND;
|
||||
case GOOD -> EMPTY;
|
||||
};
|
||||
|
||||
case BAD -> ERROR_UNEXPECTED_TEXT;
|
||||
|
||||
case NOT_FOUND -> switch(tk) {
|
||||
case BAD -> ERROR_UNEXPECTED_TEXT;
|
||||
case NONE, NOT_FOUND, GOOD -> strict
|
||||
? ERROR_REFERENCE_NOT_FOUND
|
||||
: sk == SuppressKind.YES
|
||||
? EMPTY
|
||||
: WARNING_MODULE_FOR_REFERENCE_NOT_FOUND;
|
||||
};
|
||||
|
||||
case GOOD -> switch(tk) {
|
||||
case BAD -> ERROR_UNEXPECTED_TEXT;
|
||||
case NOT_FOUND -> ERROR_REFERENCE_NOT_FOUND;
|
||||
case GOOD, NONE -> EMPTY;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the actual output against the expected string, generated by {@code predictError}.
|
||||
* If the expected string is empty, the output should be empty.
|
||||
* If the expected string is not empty, it should be present in the output.
|
||||
*
|
||||
* @param expect the expected string
|
||||
* @param found the output
|
||||
*/
|
||||
void checkOutput(String expect, String found) {
|
||||
if (expect.isEmpty()) {
|
||||
if (found.isEmpty()) {
|
||||
out.println("Output OK");
|
||||
} else {
|
||||
error("unexpected output");
|
||||
}
|
||||
} else {
|
||||
if (found.contains(expect)) {
|
||||
out.println("Output OK");
|
||||
} else {
|
||||
error("expected output not found: " + expect);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user