Skip to content
Permalink
Browse files
8164408: Add module support for @see, @link and @linkplain javadoc tags
Reviewed-by: jjg
  • Loading branch information
hns committed Jun 9, 2020
1 parent 9a8ace2 commit ac2828ddf123ced7405ae34ba24678ecc6732041
Showing 9 changed files with 649 additions and 123 deletions.
@@ -464,31 +464,50 @@ public java.util.List<DocTree> getFirstSentence(java.util.List<? extends DocTree
private Symbol attributeDocReference(TreePath path, DCReference ref) {
Env<AttrContext> env = getAttrContext(path);
if (env == null) return null;

if (ref.moduleName != null && ref.qualifierExpression == null && ref.memberName != null) {
// module name and member name without type
return null;
}
Log.DeferredDiagnosticHandler deferredDiagnosticHandler =
new Log.DeferredDiagnosticHandler(log);
try {
final TypeSymbol tsym;
final Name memberName;
final ModuleSymbol mdlsym;

if (ref.moduleName != null) {
mdlsym = modules.modulesInitialized() ?
modules.getObservableModule(names.fromString(ref.moduleName.toString()))
: null;
if (mdlsym == null) {
return null;
} else if (ref.qualifierExpression == null) {
return mdlsym;
}
} else {
mdlsym = modules.getDefaultModule();
}

if (ref.qualifierExpression == null) {
tsym = env.enclClass.sym;
memberName = (Name) ref.memberName;
} else {
// newSeeTree if the qualifierExpression is a type or package name.
// javac does not provide the exact method required, so
// we first check if qualifierExpression identifies a type,
// and if not, then we check to see if it identifies a package.
Type t = attr.attribType(ref.qualifierExpression, env);
if (t.isErroneous()) {
// Check if qualifierExpression is a type or package, using the methods javac provides.
// If no module name is given we check if qualifierExpression identifies a type.
// If that fails or we have a module name, use that to resolve qualifierExpression to
// a package or type.
Type t = ref.moduleName == null ? attr.attribType(ref.qualifierExpression, env) : null;

if (t == null || t.isErroneous()) {
JCCompilationUnit toplevel =
treeMaker.TopLevel(List.nil());
final ModuleSymbol msym = modules.getDefaultModule();
toplevel.modle = msym;
toplevel.packge = msym.unnamedPackage;
toplevel.modle = mdlsym;
toplevel.packge = mdlsym.unnamedPackage;
Symbol sym = attr.attribIdent(ref.qualifierExpression, toplevel);

if (sym == null)
if (sym == null) {
return null;
}

sym.complete();

@@ -500,7 +519,15 @@ private Symbol attributeDocReference(TreePath path, DCReference ref) {
return null;
}
} else {
if (ref.qualifierExpression.hasTag(JCTree.Tag.IDENT)) {
if (modules.modulesInitialized() && ref.moduleName == null && ref.memberName == null) {
// package/type does not exist, check if there is a matching module
ModuleSymbol moduleSymbol = modules.getObservableModule(names.fromString(ref.signature));
if (moduleSymbol != null) {
return moduleSymbol;
}
}
if (ref.qualifierExpression.hasTag(JCTree.Tag.IDENT) && ref.moduleName == null
&& ref.memberName == null) {
// fixup: allow "identifier" instead of "#identifier"
// for compatibility with javadoc
tsym = env.enclClass.sym;
@@ -513,7 +540,7 @@ private Symbol attributeDocReference(TreePath path, DCReference ref) {
Type e = t;
// If this is an array type convert to element type
while (e instanceof ArrayType)
e = ((ArrayType)e).elemtype;
e = ((ArrayType) e).elemtype;
tsym = e.tsym;
memberName = (Name) ref.memberName;
}
@@ -428,9 +428,9 @@ private DCTree inlineText(WhitespaceRetentionPolicy whitespacePolicy) throws Par
* Matching pairs of {@literal < >} are skipped. The text is terminated by the first
* unmatched }. It is an error if the beginning of the next tag is detected.
*/
// TODO: allowMember is currently ignored
// TODO: boolean allowMember should be enum FORBID, ALLOW, REQUIRE
// TODO: improve quality of parse to forbid bad constructions.
// TODO: update to use ReferenceParser
@SuppressWarnings("fallthrough")
protected DCReference reference(boolean allowMember) throws ParseException {
int pos = bp;
@@ -485,91 +485,17 @@ protected DCReference reference(boolean allowMember) throws ParseException {

String sig = newString(pos, bp);

// Break sig apart into qualifiedExpr member paramTypes.
JCTree qualExpr;
Name member;
List<JCTree> paramTypes;

Log.DeferredDiagnosticHandler deferredDiagnosticHandler
= new Log.DeferredDiagnosticHandler(fac.log);

try {
int hash = sig.indexOf("#");
int lparen = sig.indexOf("(", hash + 1);
if (hash == -1) {
if (lparen == -1) {
qualExpr = parseType(sig);
member = null;
} else {
qualExpr = null;
member = parseMember(sig.substring(0, lparen));
}
} else {
qualExpr = (hash == 0) ? null : parseType(sig.substring(0, hash));
if (lparen == -1)
member = parseMember(sig.substring(hash + 1));
else
member = parseMember(sig.substring(hash + 1, lparen));
}

if (lparen < 0) {
paramTypes = null;
} else {
int rparen = sig.indexOf(")", lparen);
if (rparen != sig.length() - 1)
throw new ParseException("dc.ref.bad.parens");
paramTypes = parseParams(sig.substring(lparen + 1, rparen));
}

if (!deferredDiagnosticHandler.getDiagnostics().isEmpty())
throw new ParseException("dc.ref.syntax.error");

} finally {
fac.log.popDiagnosticHandler(deferredDiagnosticHandler);
}

return m.at(pos).newReferenceTree(sig, qualExpr, member, paramTypes).setEndPos(bp);
}

JCTree parseType(String s) throws ParseException {
JavacParser p = fac.newParser(s, false, false, false);
JCTree tree = p.parseType();
if (p.token().kind != TokenKind.EOF)
throw new ParseException("dc.ref.unexpected.input");
return tree;
}

Name parseMember(String s) throws ParseException {
JavacParser p = fac.newParser(s, false, false, false);
Name name = p.ident();
if (p.token().kind != TokenKind.EOF)
throw new ParseException("dc.ref.unexpected.input");
return name;
}

List<JCTree> parseParams(String s) throws ParseException {
if (s.trim().isEmpty())
return List.nil();

JavacParser p = fac.newParser(s.replace("...", "[]"), false, false, false);
ListBuffer<JCTree> paramTypes = new ListBuffer<>();
paramTypes.add(p.parseType());

if (p.token().kind == TokenKind.IDENTIFIER)
p.nextToken();

while (p.token().kind == TokenKind.COMMA) {
p.nextToken();
paramTypes.add(p.parseType());

if (p.token().kind == TokenKind.IDENTIFIER)
p.nextToken();
ReferenceParser.Reference ref = new ReferenceParser(fac).parse(sig);
return m.at(pos).newReferenceTree(sig,
ref.moduleName, ref.qualExpr,
ref.member, ref.paramTypes)
.setEndPos(bp);
} catch (ReferenceParser.ParseException parseException) {
throw new ParseException(parseException.getMessage());
}

if (p.token().kind != TokenKind.EOF)
throw new ParseException("dc.ref.unexpected.input");

return paramTypes.toList();
}

/**
@@ -47,14 +47,16 @@ public class ReferenceParser {
* Any, but not all, of the member fields may be null.
*/
static public class Reference {
public final JCTree.JCExpression moduleName;
/** The type, if any, in the signature. */
public final JCTree qualExpr;
/** The member name, if any, in the signature. */
public final Name member;
/** The parameter types, if any, in the signature. */
public final List<JCTree> paramTypes;

Reference(JCTree qualExpr, Name member, List<JCTree> paramTypes) {
Reference(JCTree.JCExpression moduleName, JCTree qualExpr, Name member, List<JCTree> paramTypes) {
this.moduleName = moduleName;
this.qualExpr = qualExpr;
this.member = member;
this.paramTypes = paramTypes;
@@ -89,7 +91,8 @@ public ReferenceParser(ParserFactory fac) {
*/
public Reference parse(String sig) throws ParseException {

// Break sig apart into qualifiedExpr member paramTypes.
// Break sig apart into moduleName qualifiedExpr member paramTypes.
JCTree.JCExpression moduleName;
JCTree qualExpr;
Name member;
List<JCTree> paramTypes;
@@ -98,18 +101,27 @@ public Reference parse(String sig) throws ParseException {
= new Log.DeferredDiagnosticHandler(fac.log);

try {
int hash = sig.indexOf("#");
int lparen = sig.indexOf("(", hash + 1);
if (hash == -1) {
int slash = sig.indexOf("/");
int hash = sig.indexOf("#", slash + 1);
int lparen = sig.indexOf("(", Math.max(slash, hash) + 1);
if (slash > -1) {
moduleName = parseModule(sig.substring(0, slash));
} else {
moduleName = null;
}
if (slash > 0 && sig.length() == slash + 1) {
qualExpr = null;
member = null;
} else if (hash == -1) {
if (lparen == -1) {
qualExpr = parseType(sig);
qualExpr = parseType(sig.substring(slash + 1));
member = null;
} else {
qualExpr = null;
member = parseMember(sig.substring(0, lparen));
member = parseMember(sig.substring(slash + 1, lparen));
}
} else {
qualExpr = (hash == 0) ? null : parseType(sig.substring(0, hash));
qualExpr = (hash == slash + 1) ? null : parseType(sig.substring(slash + 1, hash));
if (lparen == -1)
member = parseMember(sig.substring(hash + 1));
else
@@ -132,7 +144,15 @@ public Reference parse(String sig) throws ParseException {
fac.log.popDiagnosticHandler(deferredDiagnosticHandler);
}

return new Reference(qualExpr, member, paramTypes);
return new Reference(moduleName, qualExpr, member, paramTypes);
}

private JCTree.JCExpression parseModule(String s) throws ParseException {
JavacParser p = fac.newParser(s, false, false, false);
JCTree.JCExpression expr = p.qualident(false);
if (p.token().kind != TokenKind.EOF)
throw new ParseException("dc.ref.unexpected.input");
return expr;
}

private JCTree parseType(String s) throws ParseException {
@@ -647,13 +647,15 @@ public static class DCReference extends DCEndPosTree<DCReference> implements Ref

// The following are not directly exposed through ReferenceTree
// use DocTrees.getElement(DocTreePath)
public final JCTree.JCExpression moduleName;
public final JCTree qualifierExpression;
public final Name memberName;
public final List<JCTree> paramTypes;


DCReference(String signature, JCTree qualExpr, Name member, List<JCTree> paramTypes) {
DCReference(String signature, JCTree.JCExpression moduleName, JCTree qualExpr, Name member, List<JCTree> paramTypes) {
this.signature = signature;
this.moduleName = moduleName;
qualifierExpression = qualExpr;
memberName = member;
this.paramTypes = paramTypes;
@@ -378,16 +378,16 @@ public DCProvides newProvidesTree(ReferenceTree name, List<? extends DocTree> de
public DCReference newReferenceTree(String signature) {
try {
ReferenceParser.Reference ref = referenceParser.parse(signature);
DCReference tree = new DCReference(signature, ref.qualExpr, ref.member, ref.paramTypes);
DCReference tree = new DCReference(signature, ref.moduleName, ref.qualExpr, ref.member, ref.paramTypes);
tree.pos = pos;
return tree;
} catch (ReferenceParser.ParseException e) {
throw new IllegalArgumentException("invalid signature", e);
}
}

public DCReference newReferenceTree(String signature, JCTree qualExpr, Name member, List<JCTree> paramTypes) {
DCReference tree = new DCReference(signature, qualExpr, member, paramTypes);
public DCReference newReferenceTree(String signature, JCTree.JCExpression moduleName, JCTree qualExpr, Name member, List<JCTree> paramTypes) {
DCReference tree = new DCReference(signature, moduleName, qualExpr, member, paramTypes);
tree.pos = pos;
return tree;
}
@@ -998,7 +998,7 @@ public Content seeTagToContent(Element element, DocTree see) {

CommentHelper ch = utils.getCommentHelper(element);
String tagName = ch.getTagName(see);
String seetext = replaceDocRootDir(utils.normalizeNewlines(ch.getText(see)).toString());
String seetext = replaceDocRootDir(removeTrailingSlash(utils.normalizeNewlines(ch.getText(see)).toString()));
// Check if @see is an href or "string"
if (seetext.startsWith("<") || seetext.startsWith("\"")) {
return new RawHtml(seetext);
@@ -1010,14 +1010,17 @@ public Content seeTagToContent(Element element, DocTree see) {
Content text = plainOrCode(kind == LINK_PLAIN, new RawHtml(seetext));

TypeElement refClass = ch.getReferencedClass(see);
String refClassName = ch.getReferencedClassName(see);
Element refMem = ch.getReferencedMember(see);
String refMemName = ch.getReferencedMemberName(see);

if (refMemName == null && refMem != null) {
refMemName = refMem.toString();
}
if (refClass == null) {
ModuleElement refModule = ch.getReferencedModule(see);
if (refModule != null && utils.isIncluded(refModule)) {
return getModuleLink(refModule, label.isEmpty() ? text : label);
}
//@see is not referencing an included class
PackageElement refPackage = ch.getReferencedPackage(see);
if (refPackage != null && utils.isIncluded(refPackage)) {
@@ -1028,9 +1031,11 @@ public Content seeTagToContent(Element element, DocTree see) {
return getPackageLink(refPackage, label);
} else {
// @see is not referencing an included class, module or package. Check for cross links.
DocLink elementCrossLink = (configuration.extern.isModule(refClassName))
? getCrossModuleLink(utils.elementUtils.getModuleElement(refClassName)) :
(refPackage != null) ? getCrossPackageLink(refPackage) : null;
String refModuleName = ch.getReferencedModuleName(see);
DocLink elementCrossLink = (refPackage != null) ? getCrossPackageLink(refPackage) :
(configuration.extern.isModule(refModuleName))
? getCrossModuleLink(utils.elementUtils.getModuleElement(refModuleName))
: null;
if (elementCrossLink != null) {
// Element cross link found
return links.createLink(elementCrossLink,
@@ -1118,6 +1123,10 @@ public Content seeTagToContent(Element element, DocTree see) {
}
}

private String removeTrailingSlash(String s) {
return s.endsWith("/") ? s.substring(0, s.length() -1) : s;
}

private Content plainOrCode(boolean plain, Content body) {
return (plain || body.isEmpty()) ? body : HtmlTree.CODE(body);
}

0 comments on commit ac2828d

Please sign in to comment.