You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2020/05/07 22:47:42 UTC

[groovy] branch master updated: GROOVY-9543: Groovydoc generics improvements(closes #1239)

This is an automated email from the ASF dual-hosted git repository.

sunlan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/master by this push:
     new 285d3ab  GROOVY-9543: Groovydoc generics improvements(closes #1239)
285d3ab is described below

commit 285d3ab3b49d4a4971ed6d13dcd08e8ac177e9f7
Author: Mikko Värri <vm...@linuxbox.fi>
AuthorDate: Fri May 8 06:46:46 2020 +0800

    GROOVY-9543: Groovydoc generics improvements(closes #1239)
---
 .../org/apache/groovy/antlr/GroovydocVisitor.java  |  33 +++---
 .../tools/groovydoc/SimpleGroovyClassDoc.java      |  10 +-
 .../groovydoc/antlr4/GroovydocJavaVisitor.java     |   5 +-
 .../gstringTemplates/classLevel/classDocName.html  |   3 +-
 .../groovy/tools/groovydoc/GroovyDocToolTest.java  | 131 ++++++++++++++++++++-
 .../groovydoc/testfiles/generics/Groovy.groovy     |  22 ++++
 .../tools/groovydoc/testfiles/generics/Java.java   |  21 ++++
 7 files changed, 206 insertions(+), 19 deletions(-)

diff --git a/subprojects/groovy-groovydoc/src/main/java/org/apache/groovy/antlr/GroovydocVisitor.java b/subprojects/groovy-groovydoc/src/main/java/org/apache/groovy/antlr/GroovydocVisitor.java
index 5fac475..4275e03 100644
--- a/subprojects/groovy-groovydoc/src/main/java/org/apache/groovy/antlr/GroovydocVisitor.java
+++ b/subprojects/groovy-groovydoc/src/main/java/org/apache/groovy/antlr/GroovydocVisitor.java
@@ -33,9 +33,11 @@ import org.codehaus.groovy.ast.Parameter;
 import org.codehaus.groovy.ast.PropertyNode;
 import org.codehaus.groovy.ast.expr.DeclarationExpression;
 import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.control.ResolveVisitor;
 import org.codehaus.groovy.control.SourceUnit;
 import org.codehaus.groovy.groovydoc.GroovyClassDoc;
 import org.codehaus.groovy.groovydoc.GroovyMethodDoc;
+import org.codehaus.groovy.runtime.DefaultGroovyMethods;
 import org.codehaus.groovy.tools.groovydoc.LinkArgument;
 import org.codehaus.groovy.tools.groovydoc.SimpleGroovyAnnotationRef;
 import org.codehaus.groovy.tools.groovydoc.SimpleGroovyClassDoc;
@@ -100,7 +102,7 @@ public class GroovydocVisitor extends ClassCodeVisitorSupport {
         if (node instanceof InnerClassNode) {
             name = name.replace('$', '.');
         }
-        currentClassDoc = new SimpleGroovyClassDoc(imports, aliases, name, links);
+        currentClassDoc = new SimpleGroovyClassDoc(withDefaultImports(imports), aliases, name, links);
         if (node.isEnum()) {
             currentClassDoc.setTokenType(SimpleGroovyDoc.ENUM_DEF);
         } else if (node.isAnnotationDefinition()) {
@@ -158,6 +160,15 @@ public class GroovydocVisitor extends ClassCodeVisitorSupport {
         }
     }
 
+    private List<String> withDefaultImports(List<String> imports) {
+        imports = imports != null ? imports : new ArrayList<>();
+        imports.add(packagePath + "/*");  // everything in this package
+        for (String pkg : ResolveVisitor.DEFAULT_IMPORTS) {
+            imports.add(pkg.replace('.', '/') + "*");
+        }
+        return imports;
+    }
+
     private static final Pattern JAVADOC_COMMENT_PATTERN = Pattern.compile("(?s)/\\*\\*(.*?)\\*/");
 
     private String getDocContent(Groovydoc groovydoc) {
@@ -205,19 +216,9 @@ public class GroovydocVisitor extends ClassCodeVisitorSupport {
     }
 
     private String genericTypesAsString(GenericsType[] genericsTypes) {
-        if (genericsTypes == null) return "";
-        StringBuilder result = new StringBuilder("<");
-        boolean first = true;
-        for (GenericsType genericsType : genericsTypes) {
-            if (!first) {
-                result.append(", ");
-            } else {
-                first = false;
-            }
-            result.append(genericsType.getName());
-        }
-        result.append(">");
-        return result.toString();
+        if (genericsTypes == null || genericsTypes.length == 0)
+            return "";
+        return "<" + DefaultGroovyMethods.join(genericsTypes, ", ") + ">";
     }
 
     private void processPropertiesFromGetterSetter(SimpleGroovyMethodDoc currentMethodDoc) {
@@ -298,7 +299,9 @@ public class GroovydocVisitor extends ClassCodeVisitorSupport {
     }
 
     private String makeType(ClassNode node) {
-        return node.getName().replace('.', '/').replace('$', '.');
+        return node.getName().replace('.', '/').replace('$', '.')
+                + genericTypesAsString(node.getGenericsTypes())
+                ;
     }
 
     @Override
diff --git a/subprojects/groovy-groovydoc/src/main/java/org/codehaus/groovy/tools/groovydoc/SimpleGroovyClassDoc.java b/subprojects/groovy-groovydoc/src/main/java/org/codehaus/groovy/tools/groovydoc/SimpleGroovyClassDoc.java
index ef29024..d0ab5d9 100644
--- a/subprojects/groovy-groovydoc/src/main/java/org/codehaus/groovy/tools/groovydoc/SimpleGroovyClassDoc.java
+++ b/subprojects/groovy-groovydoc/src/main/java/org/codehaus/groovy/tools/groovydoc/SimpleGroovyClassDoc.java
@@ -74,6 +74,8 @@ public class SimpleGroovyClassDoc extends SimpleGroovyAbstractableElementDoc imp
         TAG_TEXT.put("author", "Authors");
         TAG_TEXT.put("version", "Version");
         TAG_TEXT.put("default", "Default");
+        // typeparam is used internally as a specialization of param to separate type params from regular params.
+        TAG_TEXT.put("typeparam", "Type Parameters");
     }
     private final List<GroovyConstructorDoc> constructors;
     private final List<GroovyFieldDoc> fields;
@@ -930,7 +932,13 @@ public class SimpleGroovyClassDoc extends SimpleGroovyAbstractableElementDoc imp
                     } else if ("param".equals(tagname)) {
                         int index = content.indexOf(' ');
                         if (index >= 0) {
-                            content = "<code>" + content.substring(0, index) + "</code> - " + content.substring(index);
+                            String paramName = content.substring(0, index);
+                            String paramDesc = content.substring(index);
+                            if (paramName.startsWith("<") && paramName.endsWith(">")) {
+                                paramName = paramName.substring(1, paramName.length() - 1);
+                                tagname = "typeparam";
+                            }
+                            content = "<code>" + paramName + "</code> - " + paramDesc;
                         }
                     }
                     if (TAG_TEXT.containsKey(tagname)) {
diff --git a/subprojects/groovy-groovydoc/src/main/java/org/codehaus/groovy/tools/groovydoc/antlr4/GroovydocJavaVisitor.java b/subprojects/groovy-groovydoc/src/main/java/org/codehaus/groovy/tools/groovydoc/antlr4/GroovydocJavaVisitor.java
index d06f4ca..834c068 100644
--- a/subprojects/groovy-groovydoc/src/main/java/org/codehaus/groovy/tools/groovydoc/antlr4/GroovydocJavaVisitor.java
+++ b/subprojects/groovy-groovydoc/src/main/java/org/codehaus/groovy/tools/groovydoc/antlr4/GroovydocJavaVisitor.java
@@ -185,7 +185,9 @@ public class GroovydocJavaVisitor extends VoidVisitorAdapter<Object> {
     }
 
     private String genericTypesAsString(NodeList<TypeParameter> typeParameters) {
-        return DefaultGroovyMethods.join(typeParameters, ", ");
+        if (typeParameters == null || typeParameters.size() == 0)
+            return "";
+        return "<" + DefaultGroovyMethods.join(typeParameters, ", ") + ">";
     }
 
     private SimpleGroovyClassDoc visit(TypeDeclaration<?> n) {
@@ -254,6 +256,7 @@ public class GroovydocJavaVisitor extends VoidVisitorAdapter<Object> {
     @Override
     public void visit(MethodDeclaration m, Object arg) {
         SimpleGroovyMethodDoc meth = new SimpleGroovyMethodDoc(m.getNameAsString(), currentClassDoc);
+        meth.setTypeParameters(genericTypesAsString(m.getTypeParameters()));
         meth.setReturnType(makeType(m.getType()));
         setConstructorOrMethodCommon(m, meth);
         currentClassDoc.add(meth);
diff --git a/subprojects/groovy-groovydoc/src/main/resources/org/codehaus/groovy/tools/groovydoc/gstringTemplates/classLevel/classDocName.html b/subprojects/groovy-groovydoc/src/main/resources/org/codehaus/groovy/tools/groovydoc/gstringTemplates/classLevel/classDocName.html
index acb3c89..2b8f5ea 100644
--- a/subprojects/groovy-groovydoc/src/main/resources/org/codehaus/groovy/tools/groovydoc/gstringTemplates/classLevel/classDocName.html
+++ b/subprojects/groovy-groovydoc/src/main/resources/org/codehaus/groovy/tools/groovydoc/gstringTemplates/classLevel/classDocName.html
@@ -80,7 +80,8 @@
     def isRequired = { f, v -> def req = f.constantValueExpression() == null; req.toString() == v }
     def upcase = { n -> n[0].toUpperCase() + n[1..-1] }
     def paramsOf = { n, boolean brief -> n.parameters().collect{ param -> (brief?'':annotations(param, ' ')) + linkable(param.isTypeAvailable()?param.type():param.typeName()) + (param.vararg()?'... ':' ') + param.name() + (param.defaultValue() ? " = " + param.defaultValue():"") }.join(", ") }
-    def nameFromParams = { n -> n.name() + '(' + n.parameters().collect{ param -> param.isTypeAvailable()?param.type().qualifiedTypeName():param.typeName() }.join(', ') + ')' }
+    def rawType = { n -> def g = n.indexOf("<"); g >= 0 ? n.substring(0, g) : n }
+    def nameFromParams = { n -> n.name() + '(' + n.parameters().collect{ param -> rawType(param.isTypeAvailable()?param.type().qualifiedTypeName():param.typeName()) }.join(', ') + ')' }
     def nameFromJavaParams = { n -> n.getName() + '(' + n.parameterTypes.collect{ param -> param.getName() }.join(', ') + ')' }
 %>
 <html>
diff --git a/subprojects/groovy-groovydoc/src/test/groovy/org/codehaus/groovy/tools/groovydoc/GroovyDocToolTest.java b/subprojects/groovy-groovydoc/src/test/groovy/org/codehaus/groovy/tools/groovydoc/GroovyDocToolTest.java
index 1f8c138..6fcec54 100644
--- a/subprojects/groovy-groovydoc/src/test/groovy/org/codehaus/groovy/tools/groovydoc/GroovyDocToolTest.java
+++ b/subprojects/groovy-groovydoc/src/test/groovy/org/codehaus/groovy/tools/groovydoc/GroovyDocToolTest.java
@@ -31,7 +31,6 @@ import java.util.List;
 import java.util.Properties;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-import java.util.stream.Collectors;
 
 public class GroovyDocToolTest extends GroovyTestCase {
     private static final String MOCK_DIR = "mock/doc";
@@ -721,6 +720,136 @@ public class GroovyDocToolTest extends GroovyTestCase {
         assertEquals("The constructor parameter link text should be Foo", "Foo", constructor.group(3));
     }
 
+    public void testJavaGenericsTitle() throws Exception {
+        final String base = "org/codehaus/groovy/tools/groovydoc/testfiles/generics";
+        htmlTool.add(Arrays.asList(
+                base + "/Java.java"
+        ));
+
+        final MockOutputTool output = new MockOutputTool();
+        htmlTool.renderToOutput(output, MOCK_DIR);
+
+        final String javadoc = output.getText(MOCK_DIR + "/" + base + "/Java.html");
+
+        final Matcher title = Pattern.compile(Pattern.quote(
+                "<h2 title=\"[Java] Class Java&lt;N extends Number & Comparable&lt;? extends Number&gt;&gt;\" class=\"title\">"+
+                "[Java] Class Java&lt;N extends Number & Comparable&lt;? extends Number&gt;&gt;</h2>"
+        )).matcher(javadoc);
+
+        assertTrue("The title should have the generics information", title.find());
+    }
+
+    public void testGroovyGenericsTitle() throws Exception {
+        final String base = "org/codehaus/groovy/tools/groovydoc/testfiles/generics";
+        htmlTool.add(Arrays.asList(
+                base + "/Groovy.groovy"
+        ));
+
+        final MockOutputTool output = new MockOutputTool();
+        htmlTool.renderToOutput(output, MOCK_DIR);
+
+        final String groovydoc = output.getText(MOCK_DIR + "/" + base + "/Groovy.html");
+
+        final Matcher title = Pattern.compile(Pattern.quote(
+                "<h2 title=\"[Groovy] Trait Groovy&lt;N extends Number & Comparable&lt;? extends Number&gt;&gt;\" class=\"title\">"+
+                        "[Groovy] Trait Groovy&lt;N extends Number & Comparable&lt;? extends Number&gt;&gt;</h2>"
+        )).matcher(groovydoc);
+
+        assertTrue("The title should have the generics information", title.find());
+    }
+
+    public void testParamTagForTypeParams() throws Exception {
+        final String base = "org/codehaus/groovy/tools/groovydoc/testfiles/generics";
+        htmlTool.add(Arrays.asList(
+                base + "/Java.java",
+                base + "/Groovy.groovy"
+        ));
+
+        final MockOutputTool output = new MockOutputTool();
+        htmlTool.renderToOutput(output, MOCK_DIR);
+
+        final String javadoc = output.getText(MOCK_DIR + "/" + base + "/Java.html");
+        final String groovydoc = output.getText(MOCK_DIR + "/" + base + "/Groovy.html");
+
+        final Pattern classTypeParams = Pattern.compile(
+                "<DL><DT><B>Type Parameters:</B></DT><DD><code>N</code> -  Doc.</DD></DL>"
+        );
+        final Pattern methodTypeParams = Pattern.compile(
+                "<DL><DT><B>Type Parameters:</B></DT><DD><code>A</code> -  Doc.</DD><DD><code>B</code> -  Doc.</DD></DL>"
+        );
+
+        assertTrue("The Java class doc should have type parameters definitions", classTypeParams.matcher(javadoc).find());
+        assertTrue("The Groovy class doc should have type parameters definitions", classTypeParams.matcher(groovydoc).find());
+        assertTrue("The Java method doc should have type parameters definitions", methodTypeParams.matcher(javadoc).find());
+        assertTrue("The Groovy method doc should have type parameters definitions", methodTypeParams.matcher(groovydoc).find());
+    }
+
+    public void testMethodTypeParams() throws Exception {
+        final String base = "org/codehaus/groovy/tools/groovydoc/testfiles/generics";
+        htmlTool.add(Arrays.asList(
+                base + "/Java.java",
+                base + "/Groovy.groovy"
+        ));
+
+        final MockOutputTool output = new MockOutputTool();
+        htmlTool.renderToOutput(output, MOCK_DIR);
+
+        final String javadoc = output.getText(MOCK_DIR + "/" + base + "/Java.html");
+        final String groovydoc = output.getText(MOCK_DIR + "/" + base + "/Groovy.html");
+
+        final Pattern methodSummaryTypeParams = Pattern.compile(
+                "<td class=\"colFirst\"><code>&lt;A, B&gt;</code></td>"
+        );
+        final Pattern methodDetailsTypeParams = Pattern.compile(
+                "<h4>&lt;A, B&gt; (public&nbsp;)?static&nbsp;int <strong>compare</strong>"
+        );
+
+        assertTrue("The Java method summary should have type parameters", methodSummaryTypeParams.matcher(javadoc).find());
+        assertTrue("The Groovy method summary should have type parameters", methodSummaryTypeParams.matcher(groovydoc).find());
+        assertTrue("The Java method details should have type parameters", methodDetailsTypeParams.matcher(javadoc).find());
+        assertTrue("The Groovy method details should have type parameters", methodDetailsTypeParams.matcher(groovydoc).find());
+    }
+
+    public void testMethodParamTypeParams() throws Exception {
+        final String base = "org/codehaus/groovy/tools/groovydoc/testfiles/generics";
+        htmlTool.add(Arrays.asList(
+                base + "/Java.java",
+                base + "/Groovy.groovy"
+        ));
+
+        final MockOutputTool output = new MockOutputTool();
+        htmlTool.renderToOutput(output, MOCK_DIR);
+
+        final String javadoc = output.getText(MOCK_DIR + "/" + base + "/Java.html");
+        final String groovydoc = output.getText(MOCK_DIR + "/" + base + "/Groovy.html");
+
+        final Pattern methodSummary = Pattern.compile(Pattern.quote(
+                "<code><strong><a href=\"#compare(Class, Class)\">compare</a></strong>"
+                        + "("
+                        + "<a href='https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html' title='Class'>Class</a>&lt;A&gt; a, "
+                        + "<a href='https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html' title='Class'>Class</a>&lt;B&gt; b"
+                        + ")"
+                        + "</code>"
+        ));
+        final Pattern methodDetailAnchor = Pattern.compile(Pattern.quote(
+                "<a name=\"compare(Class, Class)\"><!-- --></a>"
+        ));
+        final Pattern methodDetailTitle = Pattern.compile(Pattern.quote(
+                "<strong>compare</strong>" +
+                        "(" +
+                        "<a href='https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html' title='Class'>Class</a>&lt;A&gt; a, " +
+                        "<a href='https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html' title='Class'>Class</a>&lt;B&gt; b" +
+                        ")"
+        ));
+
+        assertTrue("The Java method summary should include type parameters", methodSummary.matcher(javadoc).find());
+        assertTrue("The Java method detail anchor should NOT include type parameters", methodDetailAnchor.matcher(javadoc).find());
+        assertTrue("The Java method detail title should include type parameters", methodDetailTitle.matcher(javadoc).find());
+        assertTrue("The Groovy method summary should include type parameters", methodSummary.matcher(groovydoc).find());
+        assertTrue("The Groovy method detail anchor should NOT include type parameters", methodDetailAnchor.matcher(groovydoc).find());
+        assertTrue("The Groovy method detail title should include type parameters", methodDetailTitle.matcher(groovydoc).find());
+    }
+
     public void testScript() throws Exception {
         List<String> srcList = new ArrayList<String>();
         srcList.add("org/codehaus/groovy/tools/groovydoc/testfiles/Script.groovy");
diff --git a/subprojects/groovy-groovydoc/src/test/groovy/org/codehaus/groovy/tools/groovydoc/testfiles/generics/Groovy.groovy b/subprojects/groovy-groovydoc/src/test/groovy/org/codehaus/groovy/tools/groovydoc/testfiles/generics/Groovy.groovy
new file mode 100644
index 0000000..0afe01b
--- /dev/null
+++ b/subprojects/groovy-groovydoc/src/test/groovy/org/codehaus/groovy/tools/groovydoc/testfiles/generics/Groovy.groovy
@@ -0,0 +1,22 @@
+package org.codehaus.groovy.tools.groovydoc.testfiles.generics
+
+/**
+ * Generic class.
+ *
+ * @param <N> Doc.
+ */
+trait Groovy<N extends Number & Comparable<? extends Number>> {
+
+    /**
+     * Generic method.
+     *
+     * @param <A> Doc.
+     * @param <B> Doc.
+     * @param a Doc.
+     * @param b Doc.
+     * @return Doc.
+     */
+    static <A, B> int compare(Class<A> a, Class<B> b) {
+        0
+    }
+}
diff --git a/subprojects/groovy-groovydoc/src/test/groovy/org/codehaus/groovy/tools/groovydoc/testfiles/generics/Java.java b/subprojects/groovy-groovydoc/src/test/groovy/org/codehaus/groovy/tools/groovydoc/testfiles/generics/Java.java
new file mode 100644
index 0000000..e2a1d3d
--- /dev/null
+++ b/subprojects/groovy-groovydoc/src/test/groovy/org/codehaus/groovy/tools/groovydoc/testfiles/generics/Java.java
@@ -0,0 +1,21 @@
+package org.codehaus.groovy.tools.groovydoc.testfiles.generics;
+
+/**
+ * Generic class.
+ *
+ * @param <N> Doc.
+ */
+public abstract class Java<N extends Number & Comparable<? extends Number>> {
+    /**
+     * Generic method.
+     *
+     * @param <A> Doc.
+     * @param <B> Doc.
+     * @param a Doc.
+     * @param b Doc.
+     * @return Doc.
+     */
+    public static <A, B> int compare(Class<A> a, Class<B> b) {
+        return 0;
+    }
+}