475feb064b
Reviewed-by: cstein, iris, dholmes
298 lines
11 KiB
Java
298 lines
11 KiB
Java
/*
|
|
* Copyright (c) 2021, 2024, 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 8274211 8278538
|
|
* @summary Test man page that options are documented
|
|
* @modules jdk.javadoc/jdk.javadoc.internal.tool:+open
|
|
* @run main CheckManPageOptions
|
|
*/
|
|
|
|
import jdk.javadoc.doclet.Doclet;
|
|
import jdk.javadoc.doclet.StandardDoclet;
|
|
import jdk.javadoc.internal.tool.ToolOptions;
|
|
|
|
import java.io.IOException;
|
|
import java.io.PrintStream;
|
|
import java.lang.reflect.Constructor;
|
|
import java.lang.reflect.Field;
|
|
import java.lang.reflect.Method;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.TreeSet;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
import java.util.stream.Collectors;
|
|
|
|
/**
|
|
* Checks the set of options found by fuzzy-parsing the troff or Markdown versions
|
|
* of the javadoc man page against the set of options declared in the source code.
|
|
*/
|
|
public class CheckManPageOptions {
|
|
static class SourceDirNotFound extends Error { }
|
|
|
|
public static void main(String... args) throws Exception {
|
|
try {
|
|
new CheckManPageOptions().run(args);
|
|
} catch (SourceDirNotFound e) {
|
|
System.err.println("NOTE: Cannot find src directory; test skipped");
|
|
}
|
|
}
|
|
|
|
static final PrintStream out = System.err;
|
|
|
|
List<String> MISSING_IN_MAN_PAGE = List.of();
|
|
|
|
void run(String... args) throws Exception {
|
|
var file = args.length == 0 ? findDefaultFile() : Path.of(args[0]);
|
|
out.println("File: " + file);
|
|
out.println(Files.readAllLines(file).stream()
|
|
.filter(l -> l.contains("Automatically generated by Pandoc"))
|
|
.findFirst()
|
|
.map(l -> l.substring(l.indexOf("Auto")))
|
|
.orElse("version string not found"));
|
|
out.println();
|
|
|
|
var manPageOptions = getManPageOptions(file);
|
|
out.println("Man page options: " + manPageOptions);
|
|
out.println();
|
|
|
|
var toolOptions = getToolOptions();
|
|
out.println("ToolOptions: " + toolOptions);
|
|
out.println();
|
|
|
|
var docletOptions = getDocletOptions();
|
|
out.println("DocletOptions: " + docletOptions);
|
|
out.println();
|
|
|
|
var toolDocletOnly = new TreeSet<String>();
|
|
toolDocletOnly.addAll(toolOptions);
|
|
toolDocletOnly.addAll(docletOptions);
|
|
toolDocletOnly.removeAll(manPageOptions);
|
|
toolDocletOnly.removeAll(MISSING_IN_MAN_PAGE);
|
|
if (!toolDocletOnly.isEmpty()) {
|
|
error("The following options are defined by the tool or doclet, but not defined in the man page:\n"
|
|
+ toSimpleList(toolDocletOnly));
|
|
}
|
|
|
|
var manPageOnly = new TreeSet<String>();
|
|
manPageOnly.addAll(manPageOptions);
|
|
manPageOnly.removeAll(toolOptions);
|
|
manPageOnly.removeAll(docletOptions);
|
|
if (!manPageOnly.isEmpty()) {
|
|
error("The following options are defined in the man page, but not defined by the tool or doclet:\n"
|
|
+ toSimpleList(manPageOnly));
|
|
}
|
|
|
|
if (!MISSING_IN_MAN_PAGE.isEmpty()) {
|
|
var notMissing = new TreeSet<>(MISSING_IN_MAN_PAGE);
|
|
notMissing.retainAll(manPageOptions);
|
|
if (!notMissing.isEmpty()) {
|
|
error("The following options were declared as missing, but were found on the man page:\n"
|
|
+ toSimpleList(notMissing));
|
|
}
|
|
|
|
out.println("NOTE: the following options are currently excluded and need to be documented in the man page:");
|
|
out.println(toSimpleList(MISSING_IN_MAN_PAGE));
|
|
}
|
|
|
|
if (errors > 0) {
|
|
out.println(errors + " errors found");
|
|
throw new Exception(errors + " errors found");
|
|
}
|
|
}
|
|
|
|
int errors = 0;
|
|
void error(String message) {
|
|
("Error: " + message).lines().forEach(out::println);
|
|
errors++;
|
|
}
|
|
|
|
String toSimpleList(Collection<String> items) {
|
|
return items.stream().collect(Collectors.joining(", ", " ", ""));
|
|
}
|
|
|
|
Path findDefaultFile() {
|
|
return findRootDir().resolve("src/jdk.javadoc/share/man/javadoc.md");
|
|
}
|
|
|
|
Path findRootDir() {
|
|
Path dir = Path.of(System.getProperty("test.src", ".")).toAbsolutePath();
|
|
while (dir != null) {
|
|
if (Files.exists(dir.resolve("src"))) {
|
|
return dir;
|
|
} else {
|
|
Path openDir = dir.resolve("open");
|
|
if (Files.exists(openDir.resolve("src"))) {
|
|
return openDir;
|
|
}
|
|
}
|
|
dir = dir.getParent();
|
|
}
|
|
throw new SourceDirNotFound();
|
|
}
|
|
|
|
List<String> getToolOptions() throws Error {
|
|
try {
|
|
Class<ToolOptions> toolOptionsClass = ToolOptions.class;
|
|
|
|
Constructor<ToolOptions> constr = toolOptionsClass.getDeclaredConstructor();
|
|
constr.setAccessible(true);
|
|
|
|
Method getSupportedOptions = toolOptionsClass.getMethod("getSupportedOptions");
|
|
Class<?> toolOptionClass = List.of(toolOptionsClass.getDeclaredClasses()).stream()
|
|
.filter(c -> c.getSimpleName().equals("ToolOption"))
|
|
.findFirst()
|
|
.orElseThrow();
|
|
|
|
Field kindField = toolOptionClass.getDeclaredField("kind");
|
|
kindField.setAccessible(true);
|
|
Method getNames = toolOptionClass.getDeclaredMethod("getNames");
|
|
getNames.setAccessible(true);
|
|
|
|
ToolOptions t = constr.newInstance();
|
|
var list = new ArrayList<String>();
|
|
var options = (List<?>) getSupportedOptions.invoke(t);
|
|
for (var option : options) {
|
|
Object kind = kindField.get(option);
|
|
if (kind.toString().equals("HIDDEN")) {
|
|
continue;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
var oNames = (List<String>) getNames.invoke(option);
|
|
oNames.stream()
|
|
.filter(o -> !o.equals("@"))
|
|
.forEach(list::add);
|
|
}
|
|
return list;
|
|
} catch (ReflectiveOperationException e) {
|
|
throw new Error(e);
|
|
}
|
|
}
|
|
|
|
List<String> getDocletOptions() {
|
|
StandardDoclet d = new StandardDoclet();
|
|
d.init(Locale.getDefault(), null);
|
|
return getDocletOptions(d);
|
|
}
|
|
|
|
List<String> getDocletOptions(Doclet d) {
|
|
return d.getSupportedOptions().stream()
|
|
.filter(o -> o.getKind() != Doclet.Option.Kind.OTHER)
|
|
.flatMap(o -> o.getNames().stream())
|
|
.map(n -> n.replaceAll(":$", ""))
|
|
.toList();
|
|
}
|
|
|
|
List<String> getManPageOptions(Path file) throws IOException {
|
|
String page = Files.readString(file);
|
|
String name = file.getFileName().toString();
|
|
String extn = name.substring(name.lastIndexOf('.'));
|
|
return switch (extn) {
|
|
case ".1" -> parseNRoff(page);
|
|
case ".md" -> parseMarkdown(page);
|
|
default -> throw new IllegalArgumentException(file.toString());
|
|
};
|
|
}
|
|
|
|
List<String> parseNRoff(String page) {
|
|
var list = new ArrayList<String>();
|
|
|
|
// In the troff man page, options are defined in one of two forms:
|
|
// 1. options delegated to javac appear in pairs of lines of the form
|
|
// .IP \[bu] 2
|
|
// \f[V]-....
|
|
// 2. options implemented by the tool or doclet appear in pairs of lines of the form
|
|
// .TP
|
|
// \f[V]-...
|
|
|
|
Pattern p1 = Pattern.compile("\\R" + Pattern.quote(".IP \\[bu] 2") + "\\R" + Pattern.quote("\\f[V]-") + ".*");
|
|
Pattern p2 = Pattern.compile("\\R" + Pattern.quote(".TP") + "\\R" + Pattern.quote("\\f[V]-") + ".*");
|
|
Pattern outer = Pattern.compile("(" + p1.pattern() + "|" + p2.pattern() + ")");
|
|
Matcher outerMatcher = outer.matcher(page);
|
|
|
|
// In the defining areas, option names are represented as follows:
|
|
// \f[V]OPTION\f[R] or \f[V]OPTION:
|
|
// where OPTION is the shortest string not containing whitespace or colon,
|
|
// and in which all '-' characters are escaped with a single backslash.
|
|
|
|
Pattern inner = Pattern.compile("\\s\\\\f\\[V](-[^ :]+?)(:|\\\\f\\[R])");
|
|
|
|
while (outerMatcher.find()) {
|
|
String lines = outerMatcher.group();
|
|
out.println("found:" + lines);
|
|
|
|
Matcher innerMatcher = inner.matcher(lines);
|
|
while (innerMatcher.find()) {
|
|
String option = innerMatcher.group(1);
|
|
out.println(" found option:" + option);
|
|
list.add(option);
|
|
}
|
|
out.println();
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
List<String> parseMarkdown(String page) {
|
|
var list = new ArrayList<String>();
|
|
// In the Markdown man page, options are defined in one of two forms:
|
|
// 1. options delegated to javac appear in lines of the form
|
|
// * <span id="...">`-...</span>
|
|
// 2. options implemented by the tool or doclet appear in lines of the form
|
|
// <span id="...">`-...</span>
|
|
|
|
Pattern p1 = Pattern.compile("\\R* <span id=\"[^\"]+\">`-.*</span>");
|
|
Pattern p2 = Pattern.compile("\\R<span id=\"[^\"]+\">`-.*</span>");
|
|
Pattern outer = Pattern.compile("(" + p1.pattern() + "|" + p2.pattern() + ")");
|
|
Matcher outerMatcher = outer.matcher(page);
|
|
|
|
// In the defining areas, option names are represented as follows:
|
|
// `OPTION` or `OPTION`
|
|
// where OPTION is the shortest string not containing whitespace or colon,
|
|
// and in which all '-' characters are escaped with a single backslash.
|
|
Pattern inner = Pattern.compile("[>\\s]`(-[^ :]+?)(:|`)");
|
|
|
|
while (outerMatcher.find()) {
|
|
String lines = outerMatcher.group();
|
|
out.println("found:" + lines);
|
|
|
|
Matcher innerMatcher = inner.matcher(lines);
|
|
while (innerMatcher.find()) {
|
|
String option = innerMatcher.group(1);
|
|
out.println(" found option:" + option);
|
|
list.add(option);
|
|
}
|
|
out.println();
|
|
}
|
|
|
|
return list;
|
|
}
|
|
}
|