You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2026/04/21 11:24:30 UTC

(groovy) 01/02: groovydoc support -prelanguage

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

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

commit 8d9731096d4c814541ffa0858f078c9384b33480
Author: Paul King <pa...@asert.com.au>
AuthorDate: Tue Apr 21 21:20:14 2026 +1000

    groovydoc support -prelanguage
---
 .../groovy/org.apache.groovy-documented.gradle     |  1 +
 .../groovy/gradle/GroovydocAntExtension.groovy     |  2 +
 .../apache/groovy/gradle/GroovydocAntPlugin.groovy |  4 ++
 .../java/org/codehaus/groovy/ant/Groovydoc.java    | 56 ++++++++++++++++++++++
 .../groovy/test/JavadocAssertionTestBuilder.groovy |  7 ++-
 .../test/JavadocAssertionTestBuilderTest.groovy    | 15 ++++++
 6 files changed, 83 insertions(+), 2 deletions(-)

diff --git a/build-logic/src/main/groovy/org.apache.groovy-documented.gradle b/build-logic/src/main/groovy/org.apache.groovy-documented.gradle
index 99a141be35..2bde47b3de 100644
--- a/build-logic/src/main/groovy/org.apache.groovy-documented.gradle
+++ b/build-logic/src/main/groovy/org.apache.groovy-documented.gradle
@@ -23,6 +23,7 @@ pluginManager.apply(GroovydocAntPlugin)
 
 groovydocAnt {
     syntaxHighlighter = 'prism'
+    preLanguage = 'groovy'
 }
 
 configurations {
diff --git a/build-logic/src/main/groovy/org/apache/groovy/gradle/GroovydocAntExtension.groovy b/build-logic/src/main/groovy/org/apache/groovy/gradle/GroovydocAntExtension.groovy
index 9253718984..b93401703e 100644
--- a/build-logic/src/main/groovy/org/apache/groovy/gradle/GroovydocAntExtension.groovy
+++ b/build-logic/src/main/groovy/org/apache/groovy/gradle/GroovydocAntExtension.groovy
@@ -43,6 +43,7 @@ class GroovydocAntExtension {
     final Property<Boolean> noHelp
     final Property<String> syntaxHighlighter
     final Property<String> theme
+    final Property<String> preLanguage
     final ConfigurableFileCollection additionalStylesheets
 
     @Inject
@@ -55,6 +56,7 @@ class GroovydocAntExtension {
         noHelp = objects.property(Boolean).convention(false)
         syntaxHighlighter = objects.property(String).convention('none')
         theme = objects.property(String).convention('auto')
+        preLanguage = objects.property(String).convention('')
         additionalStylesheets = objects.fileCollection()
     }
 }
diff --git a/build-logic/src/main/groovy/org/apache/groovy/gradle/GroovydocAntPlugin.groovy b/build-logic/src/main/groovy/org/apache/groovy/gradle/GroovydocAntPlugin.groovy
index 357456bb8b..dab78236fa 100644
--- a/build-logic/src/main/groovy/org/apache/groovy/gradle/GroovydocAntPlugin.groovy
+++ b/build-logic/src/main/groovy/org/apache/groovy/gradle/GroovydocAntPlugin.groovy
@@ -77,6 +77,7 @@ class GroovydocAntPlugin implements Plugin<Project> {
             gdoc.inputs.property('antNoHelp', extension.noHelp)
             gdoc.inputs.property('antSyntaxHighlighter', extension.syntaxHighlighter)
             gdoc.inputs.property('antTheme', extension.theme)
+            gdoc.inputs.property('antPreLanguage', extension.preLanguage)
             gdoc.inputs.files(extension.additionalStylesheets)
                     .withPropertyName('antAdditionalStylesheets')
                     .optional(true)
@@ -143,6 +144,9 @@ class GroovydocAntPlugin implements Plugin<Project> {
         String jv = extension.javaVersion.getOrNull()
         if (jv) antArgs.put('javaVersion', jv)
 
+        String preLang = extension.preLanguage.getOrNull()
+        if (preLang) antArgs.put('preLanguage', preLang)
+
         def overviewText = gdoc.overviewText
         if (overviewText != null) {
             File overviewTmp = new File(
diff --git a/subprojects/groovy-ant/src/main/java/org/codehaus/groovy/ant/Groovydoc.java b/subprojects/groovy-ant/src/main/java/org/codehaus/groovy/ant/Groovydoc.java
index 050d0b5a82..66ba584226 100644
--- a/subprojects/groovy-ant/src/main/java/org/codehaus/groovy/ant/Groovydoc.java
+++ b/subprojects/groovy-ant/src/main/java/org/codehaus/groovy/ant/Groovydoc.java
@@ -74,6 +74,7 @@ public class Groovydoc extends Task {
     private String syntaxHighlighter;
     private String theme;
     private String javaVersion;
+    private String preLanguage;
     private final List<DirSet> packageSets;
     private final List<String> sourceFilesToDoc;
     private final List<LinkArgument> links = new ArrayList<>();
@@ -171,6 +172,21 @@ public class Groovydoc extends Task {
         this.javaVersion = javaVersion;
     }
 
+    /**
+     * Default language id for {@code <pre>} blocks in rendered doc comments
+     * that carry no {@code class=} attribute. When set (e.g. {@code "groovy"}),
+     * a post-pass rewrites such {@code <pre>} openings to
+     * {@code <pre class="language-xxx">}, which enables Prism syntax
+     * highlighting for legacy doc-comment code blocks without touching
+     * source files. {@code <pre>} blocks that already have any {@code class}
+     * attribute (including {@code class="groovyTestCase"}) are left alone.
+     *
+     * @param preLanguage the language id, or empty/null to disable
+     */
+    public void setPreLanguage(String preLanguage) {
+        this.preLanguage = preLanguage;
+    }
+
     /**
      * If set to true, members annotated with {@code groovy.transform.@Internal}
      * (per GEP-17) are still included in the generated documentation. Defaults
@@ -644,6 +660,46 @@ public class Groovydoc extends Task {
                         f.getAbsolutePath(), e.getMessage());
             }
         }
+        if (preLanguage != null && !preLanguage.isEmpty()) {
+            applyPreLanguageToHtml(destDir);
+        }
+    }
+
+    /**
+     * Walk the generated HTML under {@code destDir} and rewrite each
+     * classless {@code <pre>} opening tag to {@code <pre class="language-xxx">}.
+     * {@code <pre>} tags that already carry a {@code class} attribute — e.g.
+     * {@code <pre class="groovyTestCase">}, or blocks emitted for inline
+     * {@code {@snippet}} tags — are untouched.
+     */
+    /**
+     * Match {@code <pre>} or {@code <pre\s*>} (no attributes at all). Any
+     * existing attribute — class, id, style, data-... — blocks the rewrite so
+     * we only touch the legacy bare form. Package-private for test access.
+     */
+    static String rewritePreTags(String html, String preLanguage) {
+        return html.replaceAll("<pre\\s*>", "<pre class=\"language-" + preLanguage + "\">");
+    }
+
+    private void applyPreLanguageToHtml(File dir) {
+        File[] entries = dir.listFiles();
+        if (entries == null) return;
+        for (File f : entries) {
+            if (f.isDirectory()) {
+                applyPreLanguageToHtml(f);
+            } else if (f.getName().endsWith(".html")) {
+                try {
+                    String html = ResourceGroovyMethods.getText(f);
+                    String rewritten = rewritePreTags(html, preLanguage);
+                    if (!rewritten.equals(html)) {
+                        ResourceGroovyMethods.setText(f, rewritten);
+                    }
+                } catch (IOException e) {
+                    LOGGER.log(WARNING, "Unable to apply preLanguage to ''{0}'': {1}",
+                            f.getAbsolutePath(), e.getMessage());
+                }
+            }
+        }
     }
 
     private void checkScopeProperties(Properties properties) {
diff --git a/subprojects/groovy-test/src/main/groovy/groovy/test/JavadocAssertionTestBuilder.groovy b/subprojects/groovy-test/src/main/groovy/groovy/test/JavadocAssertionTestBuilder.groovy
index e0df783171..3e9bbfbac8 100644
--- a/subprojects/groovy-test/src/main/groovy/groovy/test/JavadocAssertionTestBuilder.groovy
+++ b/subprojects/groovy-test/src/main/groovy/groovy/test/JavadocAssertionTestBuilder.groovy
@@ -51,12 +51,15 @@ class JavadocAssertionTestBuilder {
     private static final Pattern javadocPattern =
         Pattern.compile( /(?ims)\/\*\*.*?\*\/|(?-s:(?:^[ \t]*\/\/\/[^\n]*\n?)+)/ )
     // Two authoring forms for the assertion inside a doc comment:
-    //   - HTML wrapper:   <pre class="groovyTestCase">...</pre>  (or <code>, <samp>, ...)
+    //   - HTML wrapper:   <pre class="groovyTestCase">...</pre>  (or <code>, <samp>, ...).
+    //     Other classes may be present alongside groovyTestCase — e.g.
+    //     <pre class="language-groovy groovyTestCase"> combines Prism highlighting
+    //     with test extraction.
     //   - Fenced Markdown: ```groovy groovyTestCase ... ``` — only recognised within `///` runs
     //     (Javadoc `/** */` is not Markdown, so there's no ambiguity).
     private static final Pattern assertionPattern =
         Pattern.compile(
-            /(?ims)<([a-z]+)\s+class\s*=\s*['"]groovyTestCase['"]\s*>.*?<\s*\/\s*\1>/
+            /(?ims)<([a-z]+)\s+class\s*=\s*['"][^'"]*\bgroovyTestCase\b[^'"]*['"]\s*>.*?<\s*\/\s*\1>/
             + /|(?m:^[ \t]*\/\/\/[ \t]*```groovy[ \t]+groovyTestCase\b[^\n]*\n(?s:.*?)^[ \t]*\/\/\/[ \t]*```)/
         )
 
diff --git a/subprojects/groovy-test/src/test/groovy/groovy/test/JavadocAssertionTestBuilderTest.groovy b/subprojects/groovy-test/src/test/groovy/groovy/test/JavadocAssertionTestBuilderTest.groovy
index 86426a56d0..dec514b8de 100644
--- a/subprojects/groovy-test/src/test/groovy/groovy/test/JavadocAssertionTestBuilderTest.groovy
+++ b/subprojects/groovy-test/src/test/groovy/groovy/test/JavadocAssertionTestBuilderTest.groovy
@@ -49,6 +49,21 @@ class JavadocAssertionTestBuilderTest {
         }
     }
 
+    @Test
+    void testCombinedClassesAreRecognised() {
+        Class test = builder.buildTest("SomeClass.java",
+                '/** <pre class="language-groovy groovyTestCase"> assert 1 + 1 == 2 </pre> */ public class SomeClass { }')
+        assert test != null
+        test.newInstance().testAssertionFromSomeClassLine1()
+    }
+
+    @Test
+    void testSimilarClassNameIsNotMatched() {
+        Class test = builder.buildTest("SomeClass.java",
+                '/** <pre class="notAGroovyTestCaseAtAll"> assert false </pre> */ public class SomeClass { }')
+        assert test == null
+    }
+
     @Test
     void testLineNumbering() {
         Class test = builder.buildTest("SomeClass.java", '''