8286470: Support searching for sections in class/package javadoc

Reviewed-by: jjg
This commit is contained in:
Hannes Wallnöfer 2023-05-26 18:36:45 +00:00
parent 55d297fdda
commit a92363461d
5 changed files with 123 additions and 67 deletions
src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets
test/langtools/jdk/javadoc/doclet/testAutoHeaderId

@ -104,6 +104,7 @@ import jdk.javadoc.internal.doclets.toolkit.util.DocFileIOException;
import jdk.javadoc.internal.doclets.toolkit.util.DocLink;
import jdk.javadoc.internal.doclets.toolkit.util.DocPath;
import jdk.javadoc.internal.doclets.toolkit.util.DocPaths;
import jdk.javadoc.internal.doclets.toolkit.util.IndexItem;
import jdk.javadoc.internal.doclets.toolkit.util.Utils;
import jdk.javadoc.internal.doclets.toolkit.util.Utils.DeclarationPreviewLanguageFeatures;
import jdk.javadoc.internal.doclets.toolkit.util.Utils.ElementFlag;
@ -1400,8 +1401,8 @@ public class HtmlDocletWriter {
@Override
public Boolean visitStartElement(StartElementTree node, Content content) {
Content attrs = new ContentBuilder();
if (node.getName().toString().matches("(?i)h[1-6]") && !hasIdAttribute(node)) {
generateHeadingId(node, trees, attrs);
if (node.getName().toString().matches("(?i)h[1-6]")) {
createSectionIdAndIndex(node, trees, attrs, element, context);
}
for (DocTree dt : node.getAttributes()) {
dt.accept(this, attrs);
@ -1476,14 +1477,20 @@ public class HtmlDocletWriter {
return name != null && name.toString().equalsIgnoreCase(s);
}
private boolean hasIdAttribute(StartElementTree node) {
return node.getAttributes().stream().anyMatch(
dt -> dt instanceof AttributeTree at && equalsIgnoreCase(at.getName(), "id"));
private Optional<String> getIdAttributeValue(StartElementTree node) {
return node.getAttributes().stream()
.filter(dt -> dt instanceof AttributeTree at && equalsIgnoreCase(at.getName(), "id"))
.map(dt -> ((AttributeTree)dt).getValue().toString())
.findFirst();
}
private void generateHeadingId(StartElementTree node, List<? extends DocTree> trees, Content content) {
private void createSectionIdAndIndex(StartElementTree node, List<? extends DocTree> trees, Content attrs,
Element element, TagletWriterImpl.Context context) {
// Use existing id attribute if available
String id = getIdAttributeValue(node).orElse(null);
StringBuilder sb = new StringBuilder();
String tagName = node.getName().toString().toLowerCase(Locale.ROOT);
// Go through heading content to collect content and look for existing id
for (DocTree docTree : trees.subList(trees.indexOf(node) + 1, trees.size())) {
if (docTree instanceof TextTree text) {
sb.append(text.getBody());
@ -1492,17 +1499,31 @@ public class HtmlDocletWriter {
} else if (docTree instanceof LinkTree link) {
var label = link.getLabel();
sb.append(label.isEmpty() ? link.getReference().getSignature() : label.toString());
} else if (id == null && docTree instanceof StartElementTree nested
&& equalsIgnoreCase(nested.getName(), "a")) {
// Use id of embedded anchor element if present
id = getIdAttributeValue(nested).orElse(null);
} else if (docTree instanceof EndElementTree endElement
&& equalsIgnoreCase(endElement.getName(), tagName)) {
break;
} else if (docTree instanceof StartElementTree nested
&& equalsIgnoreCase(nested.getName(), "a")
&& hasIdAttribute(nested)) {
return; // Avoid generating id if embedded <a id=...> is present
}
}
HtmlId htmlId = htmlIds.forHeading(sb, headingIds);
content.add("id=\"").add(htmlId.name()).add("\"");
String headingContent = sb.toString().trim();
if (id == null) {
// Generate id attribute
HtmlId htmlId = htmlIds.forHeading(headingContent, headingIds);
id = htmlId.name();
attrs.add("id=\"").add(htmlId.name()).add("\"");
}
// Generate index item
if (!headingContent.isEmpty() && configuration.mainIndex != null) {
String tagText = headingContent.replaceAll("\\s+", " ");
IndexItem item = IndexItem.of(element, node, tagText,
getTagletWriterInstance(context).getHolderName(element),
resources.getText("doclet.Section"),
new DocLink(path, id));
configuration.mainIndex.add(item);
}
}
/**

@ -908,56 +908,7 @@ public class TagletWriterImpl extends TagletWriter {
HtmlId id = HtmlIds.forText(tagText, htmlWriter.indexAnchorTable);
result = HtmlTree.SPAN(id, HtmlStyle.searchTagResult, tagContent);
if (options.createIndex() && !tagText.isEmpty()) {
String holder = new SimpleElementVisitor14<String, Void>() {
@Override
public String visitModule(ModuleElement e, Void p) {
return resources.getText("doclet.module")
+ " " + utils.getFullyQualifiedName(e);
}
@Override
public String visitPackage(PackageElement e, Void p) {
return resources.getText("doclet.package")
+ " " + utils.getFullyQualifiedName(e);
}
@Override
public String visitType(TypeElement e, Void p) {
return utils.getTypeElementKindName(e, true)
+ " " + utils.getFullyQualifiedName(e);
}
@Override
public String visitExecutable(ExecutableElement e, Void p) {
return utils.getFullyQualifiedName(utils.getEnclosingTypeElement(e))
+ "." + utils.getSimpleName(e)
+ utils.flatSignature(e, htmlWriter.getCurrentPageElement());
}
@Override
public String visitVariable(VariableElement e, Void p) {
return utils.getFullyQualifiedName(utils.getEnclosingTypeElement(e))
+ "." + utils.getSimpleName(e);
}
@Override
public String visitUnknown(Element e, Void p) {
if (e instanceof DocletElement de) {
return switch (de.getSubKind()) {
case OVERVIEW -> resources.getText("doclet.Overview");
case DOCFILE -> getHolderName(de);
};
} else {
return super.visitUnknown(e, p);
}
}
@Override
protected String defaultAction(Element e, Void p) {
return utils.getFullyQualifiedName(e);
}
}.visit(element);
String holder = getHolderName(element);
IndexItem item = IndexItem.of(element, tree, tagText, holder, desc,
new DocLink(htmlWriter.path, id.name()));
configuration.mainIndex.add(item);
@ -966,6 +917,59 @@ public class TagletWriterImpl extends TagletWriter {
return result;
}
String getHolderName(Element element) {
return new SimpleElementVisitor14<String, Void>() {
@Override
public String visitModule(ModuleElement e, Void p) {
return resources.getText("doclet.module")
+ " " + utils.getFullyQualifiedName(e);
}
@Override
public String visitPackage(PackageElement e, Void p) {
return resources.getText("doclet.package")
+ " " + utils.getFullyQualifiedName(e);
}
@Override
public String visitType(TypeElement e, Void p) {
return utils.getTypeElementKindName(e, true)
+ " " + utils.getFullyQualifiedName(e);
}
@Override
public String visitExecutable(ExecutableElement e, Void p) {
return utils.getFullyQualifiedName(utils.getEnclosingTypeElement(e))
+ "." + utils.getSimpleName(e)
+ utils.flatSignature(e, htmlWriter.getCurrentPageElement());
}
@Override
public String visitVariable(VariableElement e, Void p) {
return utils.getFullyQualifiedName(utils.getEnclosingTypeElement(e))
+ "." + utils.getSimpleName(e);
}
@Override
public String visitUnknown(Element e, Void p) {
if (e instanceof DocletElement de) {
return switch (de.getSubKind()) {
case OVERVIEW -> resources.getText("doclet.Overview");
case DOCFILE -> getHolderName(de);
};
} else {
return super.visitUnknown(e, p);
}
}
@Override
protected String defaultAction(Element e, Void p) {
return utils.getFullyQualifiedName(e);
}
}.visit(element);
}
private String getHolderName(DocletElement de) {
PackageElement pe = de.getPackageElement();
if (pe.isUnnamed()) {

@ -166,6 +166,7 @@ doclet.Enclosing_Class=Enclosing class:
doclet.Enclosing_Interface=Enclosing interface:
doclet.Inheritance_Tree=Inheritance Tree
doclet.ReferencedIn=Referenced In
doclet.Section=Section
doclet.External_Specification=External Specification
doclet.External_Specifications=External Specifications
doclet.External_Specifications.All_Specifications=All Specifications

@ -209,7 +209,7 @@ public class IndexItem {
Objects.requireNonNull(link);
switch (docTree.getKind()) {
case INDEX, SPEC, SYSTEM_PROPERTY -> { }
case INDEX, SPEC, SYSTEM_PROPERTY, START_ELEMENT -> { }
default -> throw new IllegalArgumentException(docTree.getKind().toString());
}
@ -340,7 +340,7 @@ public class IndexItem {
protected Category getCategory(DocTree docTree) {
return switch (docTree.getKind()) {
case INDEX, SPEC, SYSTEM_PROPERTY -> Category.TAGS;
case INDEX, SPEC, SYSTEM_PROPERTY, START_ELEMENT -> Category.TAGS;
default -> throw new IllegalArgumentException(docTree.getKind().toString());
};
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022, 2023, 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
@ -23,7 +23,7 @@
/*
* @test
* @bug 8289332
* @bug 8289332 8286470
* @summary Auto-generate ids for user-defined headings
* @library /tools/lib ../../lib
* @modules jdk.javadoc/jdk.javadoc.internal.tool
@ -79,6 +79,10 @@ public class TestAutoHeaderId extends JavadocTester {
*
* <h4></h4>
*
* <h2> Multi-line
* heading with extra
* whitespace</h2>
*
* Last sentence.
*/
public class C {
@ -121,6 +125,32 @@ public class TestAutoHeaderId extends JavadocTester {
""",
"""
<h4 id="-heading"></h4>
""");
""",
"""
<h2 id="multi-line-heading-with-extra-whitespace-heading"> Multi-line
heading with extra
whitespace</h2>""");
checkOutput("tag-search-index.js", true,
"""
{"l":"Duplicate Text","h":"class p.C","d":"Section","u":"p/C.html#duplicate-text-heading"}""",
"""
{"l":"Duplicate Text","h":"class p.C","d":"Section","u":"p/C.html#duplicate-text-heading1"}""",
"""
{"l":"Embedded A-Tag with ID","h":"class p.C","d":"Section","u":"p/C.html#fixed-id-2"}""",
"""
{"l":"Embedded Code Tag","h":"class p.C","d":"Section","u":"p/C.html#embedded-code-tag-heading"}""",
"""
{"l":"Embedded Link Tag","h":"class p.C","d":"Section","u":"p/C.html#embedded-link-tag-heading"}""",
"""
{"l":"Extra (#*!. chars","h":"class p.C","d":"Section","u":"p/C.html#extra-chars-heading"}""",
"""
{"l":"First Header","h":"class p.C","d":"Section","u":"p/C.html#first-header-heading"}""",
"""
{"l":"Header with ID","h":"class p.C","d":"Section","u":"p/C.html#fixed-id-1"}""",
"""
{"l":"Multi-line heading with extra whitespace","h":"class p.C","d":"Section","u":"p/C.html\
#multi-line-heading-with-extra-whitespace-heading"}""",
"""
{"l":"Other attributes","h":"class p.C","d":"Section","u":"p/C.html#other-attributes-heading"}""");
}
}