Skip to content

Commit

Permalink
Issue #611: rewrote Javadoc HTML escaping, skip comment-less tags, si…
Browse files Browse the repository at this point in the history
…mplified type param skipping.

	Change on 2015/09/21 by tball <tball@google.com>
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=103570844
  • Loading branch information
tomball committed Sep 23, 2015
1 parent a9b2320 commit ba7e1ff
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 52 deletions.
Expand Up @@ -14,38 +14,25 @@


package com.google.devtools.j2objc.ast; package com.google.devtools.j2objc.ast;


import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.PackageDeclaration;

import java.util.List; import java.util.List;


/** /**
* Node for a javadoc-style comment. * Node for a javadoc-style comment.
*/ */
public class Javadoc extends Comment { public class Javadoc extends Comment {


public enum OwnerType {
FIELD, METHOD, PACKAGE, TYPE, OTHER
}

private ChildList<TagElement> tags = ChildList.create(TagElement.class, this); private ChildList<TagElement> tags = ChildList.create(TagElement.class, this);
private final OwnerType ownerType;


public Javadoc(org.eclipse.jdt.core.dom.Javadoc jdtNode) { public Javadoc(org.eclipse.jdt.core.dom.Javadoc jdtNode) {
super(jdtNode); super(jdtNode);
for (Object tag : jdtNode.tags()) { for (Object tag : jdtNode.tags()) {
tags.add((TagElement) TreeConverter.convert(tag)); tags.add((TagElement) TreeConverter.convert(tag));
} }
ownerType = ownerType(jdtNode);
} }


public Javadoc(Javadoc other) { public Javadoc(Javadoc other) {
super(other); super(other);
tags.copyFrom(other.getTags()); tags.copyFrom(other.getTags());
this.ownerType = other.ownerType;
} }


@Override @Override
Expand Down Expand Up @@ -74,25 +61,4 @@ protected void acceptInner(TreeVisitor visitor) {
public Javadoc copy() { public Javadoc copy() {
return new Javadoc(this); return new Javadoc(this);
} }

public OwnerType getOwnerType() {
return ownerType;
}

private OwnerType ownerType(org.eclipse.jdt.core.dom.Javadoc jdtNode) {
ASTNode parentNode = jdtNode.getParent();
if (parentNode instanceof PackageDeclaration) {
return OwnerType.PACKAGE;
} else if (parentNode instanceof AbstractTypeDeclaration) {
return OwnerType.TYPE;
} else if (parentNode instanceof MethodDeclaration) {
return OwnerType.METHOD;
} else if (parentNode instanceof FieldDeclaration) {
return OwnerType.FIELD;
}

// Other includes AnnotationTypeMemberDeclaration, and any future
// Javadoc types that aren't supported.
return OwnerType.OTHER;
}
} }
Expand Up @@ -26,8 +26,10 @@
import org.eclipse.jdt.core.dom.IVariableBinding; import org.eclipse.jdt.core.dom.IVariableBinding;


import java.text.BreakIterator; import java.text.BreakIterator;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;


/** /**
* Generates Javadoc comments. * Generates Javadoc comments.
Expand All @@ -39,6 +41,17 @@ public class JavadocGenerator extends AbstractSourceGenerator {
// True when a <pre> tag is in a Javadoc tag, but not the closing </pre>. // True when a <pre> tag is in a Javadoc tag, but not the closing </pre>.
boolean spanningPreTag = false; boolean spanningPreTag = false;


// All escapes are defined at "http://dev.w3.org/html5/html-author/charref".
private static final Map<Character, String> htmlEntities = new HashMap<>();
static {
htmlEntities.put('"', "&quot;");
htmlEntities.put('\'', "&apos;");
htmlEntities.put('<', "&lt;");
htmlEntities.put('>', "&gt;");
htmlEntities.put('&', "&amp;");
htmlEntities.put('@', "&commat;");
}

private JavadocGenerator(SourceBuilder builder) { private JavadocGenerator(SourceBuilder builder) {
super(builder); super(builder);
} }
Expand Down Expand Up @@ -110,14 +123,10 @@ private void printDocLine(String line) {
private String printTag(TagElement tag) { private String printTag(TagElement tag) {
String tagName = tag.getTagName(); String tagName = tag.getTagName();
if (tagName != null) { if (tagName != null) {
// Remove param tags from class comments. // Remove @param tags for parameterized types, such as "@param <T> the type".
// TODO(tball): update when (if) Xcode supports Objective C type parameter documenting. // TODO(tball): update when (if) Xcode supports Objective C type parameter documenting.
if (tagName.equals(TagElement.TAG_PARAM)) { if (tagName.equals(TagElement.TAG_PARAM) && hasTypeParam(tag.getFragments())) {
TreeNode parent = tag.getParent(); return "";
if (parent instanceof Javadoc
&& ((Javadoc) parent).getOwnerType() == Javadoc.OwnerType.TYPE) {
return "";
}
} }


// Xcode 7 compatible tags. // Xcode 7 compatible tags.
Expand All @@ -128,7 +137,9 @@ private String printTag(TagElement tag) {
|| tagName.equals(TagElement.TAG_SINCE) || tagName.equals(TagElement.TAG_SINCE)
|| tagName.equals(TagElement.TAG_THROWS) || tagName.equals(TagElement.TAG_THROWS)
|| tagName.equals(TagElement.TAG_VERSION)) { || tagName.equals(TagElement.TAG_VERSION)) {
return String.format("%s %s", tagName, printTagFragments(tag.getFragments()).trim()); // Skip
String comment = printTagFragments(tag.getFragments()).trim();
return comment.isEmpty() ? "" : String.format("%s %s", tagName, comment);
} }


if (tagName.equals(TagElement.TAG_DEPRECATED)) { if (tagName.equals(TagElement.TAG_DEPRECATED)) {
Expand Down Expand Up @@ -166,13 +177,21 @@ private String printTag(TagElement tag) {
if (spanningPreTag) { if (spanningPreTag) {
return text; return text;
} }
// return HtmlEscapers.htmlEscaper().escape(text); return escapeHtmlText(text);
return text;
} }
} }
return printTagFragments(tag.getFragments()); return printTagFragments(tag.getFragments());
} }


private boolean hasTypeParam(List<TreeNode> fragments) {
// The format for a @param tag with a type parameter is:
// [ "<", Name, ">", comment ].
return fragments.size() >= 3
&& "<".equals(fragments.get(0).toString())
&& (fragments.get(1) instanceof SimpleName)
&& ">".equals(fragments.get(2).toString());
}

private String printTagFragments(List<TreeNode> fragments) { private String printTagFragments(List<TreeNode> fragments) {
if (fragments.isEmpty()) { if (fragments.isEmpty()) {
return ""; return "";
Expand Down Expand Up @@ -248,4 +267,17 @@ private String escapeCodeText(String text) {
private String escapeDocText(String text) { private String escapeDocText(String text) {
return escapeCodeText(text.replace("@", "@@").replace("/*", "/\\*")); return escapeCodeText(text.replace("@", "@@").replace("/*", "/\\*"));
} }

private String escapeHtmlText(String text) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < text.length(); i++) {
Character c = text.charAt(i);
if (htmlEntities.containsKey(c)) {
sb.append(htmlEntities.get(c));
} else {
sb.append(c);
}
}
return sb.toString();
}
} }
Expand Up @@ -63,20 +63,22 @@ public void testLinkTag() throws IOException {
assertTranslation(translation, "@brief See <code>Test.bar</code>."); assertTranslation(translation, "@brief See <code>Test.bar</code>.");
} }


// TODO(tball): enable when we can use Guava's HtmlEscapers, or write custom escaping. public void testLiteralTag() throws IOException {
// public void testLiteralTag() throws IOException { String translation = translateSourceFile(
// String translation = translateSourceFile( "/** Class javadoc for {@literal <Test>}. */ class Test {}", "Test", "Test.h");
// "/** Class javadoc for {@literal <Test>}. */ class Test {}", "Test", "Test.h"); assertTranslation(translation, "@brief Class javadoc for &lt;Test&gt;.");
// assertTranslation(translation, "@brief Class javadoc for &lt;Test&gt;."); }
// }


// Javadoc supports @param tags on classes, to document type parameters. Since there's // Javadoc supports @param tags on classes, to document type parameters. Since there's
// no equivalent in Objective C, these tags need to be removed. // no equivalent in Objective C, these tags need to be removed.
public void testClassParamTagRemoval() throws IOException { public void testTypeParamTagRemoval() throws IOException {
String translation = translateSourceFile( String translation = translateSourceFile(
"/** Class javadoc for Test.\n" "/** Class javadoc for Test.\n"
+ " * @param <T> the test name\n" + " * @param <T> the test name\n"
+ " */ class Test <T> {}", "Test", "Test.h"); + " */ class Test <T> {\n"
+ " /** Method javadoc.\n"
+ " * @param <T> the type to be returned.\n"
+ " */ T test() { return null; }}", "Test", "Test.h");
assertTranslation(translation, "@brief Class javadoc for Test."); assertTranslation(translation, "@brief Class javadoc for Test.");
assertNotInTranslation(translation, "@param"); assertNotInTranslation(translation, "@param");
assertNotInTranslation(translation, "<T>"); assertNotInTranslation(translation, "<T>");
Expand Down Expand Up @@ -137,4 +139,23 @@ public void testReservedParamName() throws IOException {
assertTranslation(translation, "@param outArg Unused."); assertTranslation(translation, "@param outArg Unused.");
assertTranslation(translation, "@param description_ Unused."); assertTranslation(translation, "@param description_ Unused.");
} }

// Verify that tags without following text are skipped, such as "@param\n".
public void testSkipEmptyTags() throws IOException {
String translation = translateSourceFile(
"/** Class javadoc for Test.\n"
+ " * @see\n"
+ " */ class Test { \n"
+ "/** Method javadoc.\n"
+ " * @param \n"
+ " * @return\n"
+ " * @throws\n"
+ " */\n"
+ "boolean test(int foo) { return false; } }", "Test", "Test.h");
assertTranslation(translation, "@brief Class javadoc for Test.");
assertNotInTranslation(translation, "@since");
assertNotInTranslation(translation, "@param");
assertNotInTranslation(translation, "@return");
assertNotInTranslation(translation, "@throws");
}
} }

0 comments on commit ba7e1ff

Please sign in to comment.