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 06:13:33 UTC

(groovy) 22/34: GROOVY-11938: {@snippet} per JEP 413 (syntax highlighting assets)

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 6e9db0a6808b579743a52d07531d608440d7b8db
Author: Paul King <pa...@asert.com.au>
AuthorDate: Tue Apr 21 00:08:50 2026 +1000

    GROOVY-11938: {@snippet} per JEP 413 (syntax highlighting assets)
---
 .../java/org/codehaus/groovy/ant/Groovydoc.java    | 13 +++++++
 .../codehaus/groovy/tools/groovydoc/Main.groovy    |  5 +++
 .../groovy/tools/groovydoc/GroovyDocWriter.java    |  9 ++++-
 .../gstringTemplates/GroovyDocTemplateInfo.java    | 21 +++++++++++
 .../groovy/tools/groovydoc/Main.properties         |  2 ++
 .../gstringTemplates/classLevel/classDocName.html  | 14 ++++++++
 .../packageLevel/package-summary.html              | 14 ++++++++
 .../packageLevel/package-tree.html                 | 14 ++++++++
 .../topLevel/overview-summary.html                 | 14 ++++++++
 .../gstringTemplates/topLevel/overview-tree.html   | 14 ++++++++
 .../groovy/tools/groovydoc/GroovyDocToolTest.java  | 42 ++++++++++++++++++++++
 11 files changed, 161 insertions(+), 1 deletion(-)

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 a05f2dcc9d..58d5b84841 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
@@ -71,6 +71,7 @@ public class Groovydoc extends Task {
     private Boolean noIndex;
     private Boolean noDeprecatedList;
     private Boolean noHelp;
+    private String syntaxHighlighter;
     private String javaVersion;
     private final List<DirSet> packageSets;
     private final List<String> sourceFilesToDoc;
@@ -105,6 +106,7 @@ public class Groovydoc extends Task {
         noIndex = false;
         noDeprecatedList = false;
         noHelp = false;
+        syntaxHighlighter = "none";
     }
 
     /**
@@ -202,6 +204,16 @@ public class Groovydoc extends Task {
         this.noHelp = noHelp;
     }
 
+    /**
+     * GROOVY-11938 stage 4: selects a client-side syntax highlighter for
+     * {@code {@snippet}} and fenced Markdown code blocks. Valid values are
+     * {@code "prism"} (bundled) or {@code "none"} (default). Any other value
+     * is treated as {@code "none"}.
+     */
+    public void setSyntaxHighlighter(String syntaxHighlighter) {
+        this.syntaxHighlighter = syntaxHighlighter;
+    }
+
     /**
      * If set to false, Scripts will not be processed.
      * Defaults to true.
@@ -536,6 +548,7 @@ public class Groovydoc extends Task {
         properties.setProperty("noIndex", noIndex.toString());
         properties.setProperty("noDeprecatedList", noDeprecatedList.toString());
         properties.setProperty("noHelp", noHelp.toString());
+        properties.setProperty("syntaxHighlighter", syntaxHighlighter != null ? syntaxHighlighter : "none");
         // GROOVY-11941: expose additional stylesheet basenames to templates.
         StringBuilder extras = new StringBuilder();
         for (int k = 0; k < addStylesheetFiles.size(); k++) {
diff --git a/subprojects/groovy-groovydoc/src/main/groovy/org/codehaus/groovy/tools/groovydoc/Main.groovy b/subprojects/groovy-groovydoc/src/main/groovy/org/codehaus/groovy/tools/groovydoc/Main.groovy
index 54b2be7b61..7070ad1937 100644
--- a/subprojects/groovy-groovydoc/src/main/groovy/org/codehaus/groovy/tools/groovydoc/Main.groovy
+++ b/subprojects/groovy-groovydoc/src/main/groovy/org/codehaus/groovy/tools/groovydoc/Main.groovy
@@ -49,6 +49,7 @@ class Main {
     private static Boolean noIndex
     private static Boolean noDeprecatedList
     private static Boolean noHelp
+    private static String syntaxHighlighter
     private static Boolean privateScope
     private static Boolean packageScope
     private static Boolean publicScope
@@ -85,6 +86,7 @@ class Main {
         cli.noindex(messages['cli.option.noindex.description'])
         cli.nodeprecatedlist(messages['cli.option.nodeprecatedlist.description'])
         cli.nohelp(messages['cli.option.nohelp.description'])
+        cli.syntaxHighlighter(args: 1, argName: 'name', messages['cli.option.syntaxHighlighter.description'])
         cli.overview(args:1, argName: 'file', messages['cli.option.overview.description'])
         cli.public(messages['cli.option.public.description'])
         cli.protected(messages['cli.option.protected.description'])
@@ -154,6 +156,7 @@ class Main {
         noIndex = Boolean.valueOf(options.noindex) ?: false
         noDeprecatedList = Boolean.valueOf(options.nodeprecatedlist) ?: false
         noHelp = Boolean.valueOf(options.nohelp) ?: false
+        syntaxHighlighter = options.syntaxHighlighter ?: 'none'
         showInternal = Boolean.valueOf(options.showInternal) ?: false
         packageScope = Boolean.valueOf(options.package) ?: false
         privateScope = Boolean.valueOf(options.private) ?: false
@@ -243,6 +246,8 @@ class Main {
         properties.put("noIndex", noIndex.toString())
         properties.put("noDeprecatedList", noDeprecatedList.toString())
         properties.put("noHelp", noHelp.toString())
+        // GROOVY-11938 stage 4: client-side syntax highlighter ("prism"|"none").
+        properties.put("syntaxHighlighter", syntaxHighlighter)
         properties.put("overviewFile", overviewFile?.absolutePath ?: "")
         String phaseOverride = SystemUtil.getSystemPropertySafe("groovydoc.phase.override")
         if (phaseOverride) properties.put("phaseOverride", phaseOverride)
diff --git a/subprojects/groovy-groovydoc/src/main/java/org/codehaus/groovy/tools/groovydoc/GroovyDocWriter.java b/subprojects/groovy-groovydoc/src/main/java/org/codehaus/groovy/tools/groovydoc/GroovyDocWriter.java
index f3fb75b518..cebd9c60cf 100644
--- a/subprojects/groovy-groovydoc/src/main/java/org/codehaus/groovy/tools/groovydoc/GroovyDocWriter.java
+++ b/subprojects/groovy-groovydoc/src/main/java/org/codehaus/groovy/tools/groovydoc/GroovyDocWriter.java
@@ -183,7 +183,14 @@ public class GroovyDocWriter {
     }
 
     private static boolean hasBinaryExtension(String template) {
-        return template.endsWith(".gif") || template.endsWith(".ico");
+        if (template.endsWith(".gif") || template.endsWith(".ico")) return true;
+        // GROOVY-11938 stage 4: Prism.js bundle. The minified JS/CSS files
+        // contain `$` characters which the GString template engine would
+        // otherwise try to interpret as expressions, so copy verbatim.
+        int lastSlash = template.lastIndexOf('/');
+        String name = lastSlash >= 0 ? template.substring(lastSlash + 1) : template;
+        if (name.startsWith("prism") && (name.endsWith(".js") || name.endsWith(".css"))) return true;
+        return false;
     }
 
 }
diff --git a/subprojects/groovy-groovydoc/src/main/java/org/codehaus/groovy/tools/groovydoc/gstringTemplates/GroovyDocTemplateInfo.java b/subprojects/groovy-groovydoc/src/main/java/org/codehaus/groovy/tools/groovydoc/gstringTemplates/GroovyDocTemplateInfo.java
index 66bd46cdaf..a706ab997e 100644
--- a/subprojects/groovy-groovydoc/src/main/java/org/codehaus/groovy/tools/groovydoc/gstringTemplates/GroovyDocTemplateInfo.java
+++ b/subprojects/groovy-groovydoc/src/main/java/org/codehaus/groovy/tools/groovydoc/gstringTemplates/GroovyDocTemplateInfo.java
@@ -33,6 +33,27 @@ public class GroovyDocTemplateInfo {
             TEMPLATE_BASEDIR + "topLevel/stylesheet.css", // copy default one, may override later
             TEMPLATE_BASEDIR + "topLevel/inherit.gif",
             DOCGEN_BASEDIR   + "groovy.ico",
+            // GROOVY-11938 stage 4: Prism.js for client-side syntax highlighting of
+            // {@snippet} bodies and fenced Markdown code blocks. These files are
+            // always copied so an opt-in flag ({@code -syntaxHighlighter=prism})
+            // can enable highlighting at any time without re-running with a
+            // different template set.
+            TEMPLATE_BASEDIR + "topLevel/prism.js",
+            TEMPLATE_BASEDIR + "topLevel/prism.min.css",
+            TEMPLATE_BASEDIR + "topLevel/prism-dark.min.css",
+            TEMPLATE_BASEDIR + "topLevel/prism-csv.min.js",
+            TEMPLATE_BASEDIR + "topLevel/prism-groovy.min.js",
+            TEMPLATE_BASEDIR + "topLevel/prism-java.min.js",
+            TEMPLATE_BASEDIR + "topLevel/prism-javadoclike.min.js",
+            TEMPLATE_BASEDIR + "topLevel/prism-javascript.min.js",
+            TEMPLATE_BASEDIR + "topLevel/prism-json.min.js",
+            TEMPLATE_BASEDIR + "topLevel/prism-markdown.min.js",
+            TEMPLATE_BASEDIR + "topLevel/prism-properties.min.js",
+            TEMPLATE_BASEDIR + "topLevel/prism-regex.min.js",
+            TEMPLATE_BASEDIR + "topLevel/prism-sql.min.js",
+            TEMPLATE_BASEDIR + "topLevel/prism-toml.min.js",
+            TEMPLATE_BASEDIR + "topLevel/prism-xml-doc.min.js",
+            TEMPLATE_BASEDIR + "topLevel/prism-yaml.min.js",
     };
     public static final String[] DEFAULT_PACKAGE_TEMPLATES = new String[]{ // package level templates
             TEMPLATE_BASEDIR + "packageLevel/package-frame.html",
diff --git a/subprojects/groovy-groovydoc/src/main/resources/org/codehaus/groovy/tools/groovydoc/Main.properties b/subprojects/groovy-groovydoc/src/main/resources/org/codehaus/groovy/tools/groovydoc/Main.properties
index b650bed86e..69dce4b90a 100644
--- a/subprojects/groovy-groovydoc/src/main/resources/org/codehaus/groovy/tools/groovydoc/Main.properties
+++ b/subprojects/groovy-groovydoc/src/main/resources/org/codehaus/groovy/tools/groovydoc/Main.properties
@@ -66,6 +66,8 @@ cli.option.nodeprecatedlist.description=Don't generate the deprecated-list page
 
 cli.option.nohelp.description=Don't generate the help page
 
+cli.option.syntaxHighlighter.description=Client-side syntax highlighter for snippets and Markdown fenced code blocks: "prism" (bundled) or "none" (default).
+
 cli.option.public.description=Show only public classes and members
 
 cli.option.protected.description=Show protected/public classes and members (default)
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 d609933b20..954f2dc2c4 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
@@ -96,6 +96,20 @@
 <% if (props.additionalStylesheets) props.additionalStylesheets.tokenize(',').each { __ss -> %>
     <link rel="stylesheet" type="text/css" href="${classDoc.relativeRootPath}${__ss.trim()}">
 <% } %>
+<% if (props.syntaxHighlighter == 'prism') { def __p = "${classDoc.relativeRootPath}" %>
+    <link rel="stylesheet" type="text/css" href="${__p}prism.min.css">
+    <script src="${__p}prism.js"></script>
+    <script src="${__p}prism-groovy.min.js"></script>
+    <script src="${__p}prism-java.min.js"></script>
+    <script src="${__p}prism-json.min.js"></script>
+    <script src="${__p}prism-yaml.min.js"></script>
+    <script src="${__p}prism-toml.min.js"></script>
+    <script src="${__p}prism-sql.min.js"></script>
+    <script src="${__p}prism-csv.min.js"></script>
+    <script src="${__p}prism-markdown.min.js"></script>
+    <script src="${__p}prism-xml-doc.min.js"></script>
+    <script src="${__p}prism-javadoclike.min.js"></script>
+<% } %>
 
 <body class="center">
 <script type="text/javascript"><!--
diff --git a/subprojects/groovy-groovydoc/src/main/resources/org/codehaus/groovy/tools/groovydoc/gstringTemplates/packageLevel/package-summary.html b/subprojects/groovy-groovydoc/src/main/resources/org/codehaus/groovy/tools/groovydoc/gstringTemplates/packageLevel/package-summary.html
index 06d1bc0691..79996ecf60 100644
--- a/subprojects/groovy-groovydoc/src/main/resources/org/codehaus/groovy/tools/groovydoc/gstringTemplates/packageLevel/package-summary.html
+++ b/subprojects/groovy-groovydoc/src/main/resources/org/codehaus/groovy/tools/groovydoc/gstringTemplates/packageLevel/package-summary.html
@@ -54,6 +54,20 @@ function windowTitle()
 <noscript>
 </noscript>
 
+<% if (props.syntaxHighlighter == 'prism') { def __p = '${packageDoc.relativeRootPath}' %>
+<link rel="stylesheet" type="text/css" href="${__p}prism.min.css">
+<script src="${__p}prism.js"></script>
+<script src="${__p}prism-groovy.min.js"></script>
+<script src="${__p}prism-java.min.js"></script>
+<script src="${__p}prism-json.min.js"></script>
+<script src="${__p}prism-yaml.min.js"></script>
+<script src="${__p}prism-toml.min.js"></script>
+<script src="${__p}prism-sql.min.js"></script>
+<script src="${__p}prism-csv.min.js"></script>
+<script src="${__p}prism-markdown.min.js"></script>
+<script src="${__p}prism-xml-doc.min.js"></script>
+<script src="${__p}prism-javadoclike.min.js"></script>
+<% } %>
 </head>
 
 <body class="center" onload="windowTitle();">
diff --git a/subprojects/groovy-groovydoc/src/main/resources/org/codehaus/groovy/tools/groovydoc/gstringTemplates/packageLevel/package-tree.html b/subprojects/groovy-groovydoc/src/main/resources/org/codehaus/groovy/tools/groovydoc/gstringTemplates/packageLevel/package-tree.html
index 4dee916e02..cc82eb7e45 100644
--- a/subprojects/groovy-groovydoc/src/main/resources/org/codehaus/groovy/tools/groovydoc/gstringTemplates/packageLevel/package-tree.html
+++ b/subprojects/groovy-groovydoc/src/main/resources/org/codehaus/groovy/tools/groovydoc/gstringTemplates/packageLevel/package-tree.html
@@ -31,6 +31,20 @@
 <% } %>
 <link href="${packageDoc.relativeRootPath}groovy.ico" type="image/x-icon" rel="shortcut icon">
 <link href="${packageDoc.relativeRootPath}groovy.ico" type="image/x-icon" rel="icon">
+<% if (props.syntaxHighlighter == 'prism') { def __p = '${packageDoc.relativeRootPath}' %>
+<link rel="stylesheet" type="text/css" href="${__p}prism.min.css">
+<script src="${__p}prism.js"></script>
+<script src="${__p}prism-groovy.min.js"></script>
+<script src="${__p}prism-java.min.js"></script>
+<script src="${__p}prism-json.min.js"></script>
+<script src="${__p}prism-yaml.min.js"></script>
+<script src="${__p}prism-toml.min.js"></script>
+<script src="${__p}prism-sql.min.js"></script>
+<script src="${__p}prism-csv.min.js"></script>
+<script src="${__p}prism-markdown.min.js"></script>
+<script src="${__p}prism-xml-doc.min.js"></script>
+<script src="${__p}prism-javadoclike.min.js"></script>
+<% } %>
 </head>
 <body>
 <!-- ========= START OF TOP NAVBAR ======= -->
diff --git a/subprojects/groovy-groovydoc/src/main/resources/org/codehaus/groovy/tools/groovydoc/gstringTemplates/topLevel/overview-summary.html b/subprojects/groovy-groovydoc/src/main/resources/org/codehaus/groovy/tools/groovydoc/gstringTemplates/topLevel/overview-summary.html
index 66e7d44eee..253cd12feb 100644
--- a/subprojects/groovy-groovydoc/src/main/resources/org/codehaus/groovy/tools/groovydoc/gstringTemplates/topLevel/overview-summary.html
+++ b/subprojects/groovy-groovydoc/src/main/resources/org/codehaus/groovy/tools/groovydoc/gstringTemplates/topLevel/overview-summary.html
@@ -31,6 +31,20 @@
 <% } %>
 <link href="groovy.ico" type="image/x-icon" rel="shortcut icon">
 <link href="groovy.ico" type="image/x-icon" rel="icon">
+<% if (props.syntaxHighlighter == 'prism') { def __p = '' %>
+<link rel="stylesheet" type="text/css" href="${__p}prism.min.css">
+<script src="${__p}prism.js"></script>
+<script src="${__p}prism-groovy.min.js"></script>
+<script src="${__p}prism-java.min.js"></script>
+<script src="${__p}prism-json.min.js"></script>
+<script src="${__p}prism-yaml.min.js"></script>
+<script src="${__p}prism-toml.min.js"></script>
+<script src="${__p}prism-sql.min.js"></script>
+<script src="${__p}prism-csv.min.js"></script>
+<script src="${__p}prism-markdown.min.js"></script>
+<script src="${__p}prism-xml-doc.min.js"></script>
+<script src="${__p}prism-javadoclike.min.js"></script>
+<% } %>
 </head>
 <body class="center">
 <script type="text/javascript"><!--
diff --git a/subprojects/groovy-groovydoc/src/main/resources/org/codehaus/groovy/tools/groovydoc/gstringTemplates/topLevel/overview-tree.html b/subprojects/groovy-groovydoc/src/main/resources/org/codehaus/groovy/tools/groovydoc/gstringTemplates/topLevel/overview-tree.html
index 07acbdef56..212f8ba893 100644
--- a/subprojects/groovy-groovydoc/src/main/resources/org/codehaus/groovy/tools/groovydoc/gstringTemplates/topLevel/overview-tree.html
+++ b/subprojects/groovy-groovydoc/src/main/resources/org/codehaus/groovy/tools/groovydoc/gstringTemplates/topLevel/overview-tree.html
@@ -31,6 +31,20 @@
 <% } %>
 <link href="groovy.ico" type="image/x-icon" rel="shortcut icon">
 <link href="groovy.ico" type="image/x-icon" rel="icon">
+<% if (props.syntaxHighlighter == 'prism') { def __p = '' %>
+<link rel="stylesheet" type="text/css" href="${__p}prism.min.css">
+<script src="${__p}prism.js"></script>
+<script src="${__p}prism-groovy.min.js"></script>
+<script src="${__p}prism-java.min.js"></script>
+<script src="${__p}prism-json.min.js"></script>
+<script src="${__p}prism-yaml.min.js"></script>
+<script src="${__p}prism-toml.min.js"></script>
+<script src="${__p}prism-sql.min.js"></script>
+<script src="${__p}prism-csv.min.js"></script>
+<script src="${__p}prism-markdown.min.js"></script>
+<script src="${__p}prism-xml-doc.min.js"></script>
+<script src="${__p}prism-javadoclike.min.js"></script>
+<% } %>
 </head>
 <body>
 <!-- ========= START OF TOP NAVBAR ======= -->
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 5c9c0b27ff..90b0e71761 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
@@ -495,6 +495,48 @@ public class GroovyDocToolTest extends GroovyTestCase {
                 page.contains("the doubled value"));
     }
 
+    // GROOVY-11938 stage 4: opt-in client-side syntax highlighting via Prism.
+    // When -syntaxHighlighter=prism is passed, the head of each content page
+    // should include prism.min.css and the bundled language scripts; when
+    // it's unset (default), none of the prism refs should appear.
+    public void testSyntaxHighlighterPrismInjectsHead() throws Exception {
+        String base = "org/codehaus/groovy/tools/groovydoc/testfiles";
+        Properties props = new Properties();
+        props.put("syntaxHighlighter", "prism");
+        GroovyDocTool tool = makeHtmltool(new ArrayList<>(), null, props);
+        tool.add(List.of(base + "/DocumentedClass.groovy"));
+
+        MockOutputTool output = new MockOutputTool();
+        tool.renderToOutput(output, MOCK_DIR);
+
+        String classPage = output.getText(MOCK_DIR + "/" + base + "/DocumentedClass.html");
+        assertNotNull(classPage);
+        assertTrue("Expected prism.min.css <link> in class page head:\n" + classPage,
+                classPage.contains("prism.min.css"));
+        assertTrue("Expected prism.js <script> in class page head:\n" + classPage,
+                classPage.contains("prism.js"));
+        assertTrue("Expected prism-groovy language component in class page head:\n" + classPage,
+                classPage.contains("prism-groovy.min.js"));
+
+        String overview = output.getText(MOCK_DIR + "/overview-summary.html");
+        assertNotNull(overview);
+        assertTrue("Expected prism.min.css in overview:\n" + overview,
+                overview.contains("prism.min.css"));
+    }
+
+    public void testSyntaxHighlighterOffByDefault() throws Exception {
+        String base = "org/codehaus/groovy/tools/groovydoc/testfiles";
+        htmlTool.add(List.of(base + "/DocumentedClass.groovy"));
+        MockOutputTool output = new MockOutputTool();
+        htmlTool.renderToOutput(output, MOCK_DIR);
+        String classPage = output.getText(MOCK_DIR + "/" + base + "/DocumentedClass.html");
+        assertNotNull(classPage);
+        assertFalse("Prism should NOT be referenced when default (syntaxHighlighter=none):\n" + classPage,
+                classPage.contains("prism.js"));
+        assertFalse("Prism CSS should NOT be referenced when default:\n" + classPage,
+                classPage.contains("prism.min.css"));
+    }
+
     // GROOVY-11542 stage 3a: inline tags inside a Markdown code fence or
     // code span stay literal (TagRenderer must not expand them).
     public void testMarkdownCodeFencePreservesInlineTagSyntax() throws Exception {