From 044d4fd15bcb799454dab6432fcad180313d9055 Mon Sep 17 00:00:00 2001 From: Fred Bricon Date: Tue, 19 Dec 2023 15:45:48 +0100 Subject: [PATCH] build: replace remark with flexmark Signed-off-by: Fred Bricon --- build.gradle.kts | 8 +- .../psi/internal/core/ls/PsiUtilsLSImpl.java | 2 +- .../javadoc/JavaDoc2MarkdownConverter.java | 113 ++++++++++++------ .../psi/template/TemplateGetJavadocTest.java | 10 +- .../psi/quarkus/QuarkusKubernetesTest.java | 83 ++++++------- 5 files changed, 131 insertions(+), 85 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index a250c46cc..4640b28d2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -73,9 +73,11 @@ sourceSets { dependencies { implementation("org.zeroturnaround:zt-zip:1.14") - implementation("com.kotcrab.remark:remark:1.2.0") //FIXME use lsp4ij's flexmark instead - implementation("org.jsoup:jsoup:1.14.2") //FIXME use lsp4ij's jsoup instead - implementation("com.google.code.gson:gson:2.10.1") + implementation("org.jsoup:jsoup:1.17.1") + implementation("com.vladsch.flexmark:flexmark-html2md-converter:0.64.8") { + exclude(group="com.vladsch.flexmark", module= "flexmark-jira-converter") + } + implementation("com.google.code.gson:gson:2.10.1") //Need to ensure we don't get telemetry's old gson version implementation("io.quarkus:quarkus-core:$quarkusVersion") { isTransitive = false } diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/internal/core/ls/PsiUtilsLSImpl.java b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/internal/core/ls/PsiUtilsLSImpl.java index 1d6f58d5b..aa1868c9b 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/internal/core/ls/PsiUtilsLSImpl.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/internal/core/ls/PsiUtilsLSImpl.java @@ -118,7 +118,7 @@ public String getJavadoc(PsiMethod method, DocumentFormat documentFormat) { @Override public String getJavadoc(PsiMember method, com.redhat.qute.commons.DocumentFormat documentFormat) { - boolean markdown = DocumentFormat.Markdown.equals(documentFormat); + boolean markdown = DocumentFormat.Markdown.name().toLowerCase().equals(documentFormat.name().toLowerCase()); Reader reader = markdown ? JavadocContentAccess.getMarkdownContentReader(method) : JavadocContentAccess.getPlainTextContentReader(method); return reader != null ? toString(reader) : null; diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/javadoc/JavaDoc2MarkdownConverter.java b/src/main/java/com/redhat/devtools/intellij/quarkus/javadoc/JavaDoc2MarkdownConverter.java index 5566b54ca..722e19d66 100644 --- a/src/main/java/com/redhat/devtools/intellij/quarkus/javadoc/JavaDoc2MarkdownConverter.java +++ b/src/main/java/com/redhat/devtools/intellij/quarkus/javadoc/JavaDoc2MarkdownConverter.java @@ -12,13 +12,13 @@ import java.io.Reader; -import java.lang.reflect.Field; - -import org.jsoup.safety.Cleaner; -import org.jsoup.safety.Safelist; -import com.overzealous.remark.Options; -import com.overzealous.remark.Options.Tables; -import com.overzealous.remark.Remark; +import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter; +import com.vladsch.flexmark.parser.PegdownExtensions; +import com.vladsch.flexmark.util.data.DataKey; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,34 +30,29 @@ public class JavaDoc2MarkdownConverter extends AbstractJavaDocConverter { private static final Logger LOGGER = LoggerFactory.getLogger(JavaDoc2MarkdownConverter.class); - private static Remark remark; - - static { - Options options = new Options(); - options.tables = Tables.MULTI_MARKDOWN; - options.hardwraps = true; - options.inlineLinks = true; - options.autoLinks = true; - options.reverseHtmlSmartPunctuation = true; - remark = new Remark(options); - //Stop remark from stripping file and jdt protocols in an href - try { - Field cleanerField = Remark.class.getDeclaredField("cleaner"); - cleanerField.setAccessible(true); - - Cleaner c = (Cleaner) cleanerField.get(remark); - - Field safelistField = Cleaner.class.getDeclaredField("safelist"); - safelistField.setAccessible(true); + private static final String LINE_SEPARATOR = System.lineSeparator(); - Safelist s = (Safelist) safelistField.get(c); - - s.addProtocols("a", "href", "file", "jdt"); - s.addProtocols("img", "src", "file"); - } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { - LOGGER.error("Unable to modify jsoup to include file and jdt protocols", e); - } - } + final static public DataKey HTML_EXTENSIONS = new DataKey<>("HTML_EXTENSIONS", 0 + //| Extensions.ABBREVIATIONS + //| Extensions.EXTANCHORLINKS /*| Extensions.EXTANCHORLINKS_WRAP*/ + //| Extensions.AUTOLINKS + //| Extensions.DEFINITIONS + | PegdownExtensions.FENCED_CODE_BLOCKS + //| Extensions.FORCELISTITEMPARA + //| Extensions.HARDWRAPS + //| Extensions.ATXHEADERSPACE + //| Extensions.QUOTES + //| Extensions.SMARTS + //| Extensions.RELAXEDHRULES + //| Extensions.STRIKETHROUGH + //| Extensions.SUPPRESS_HTML_BLOCKS + //| Extensions.SUPPRESS_INLINE_HTML + //| Extensions.TABLES + //| Extensions.TASKLISTITEMS + //| Extensions.WIKILINKS + //| Extensions.TRACE_PARSER + ); + private static final FlexmarkHtmlConverter CONVERTER = FlexmarkHtmlConverter.builder().build(); public JavaDoc2MarkdownConverter(Reader reader) { super(reader); @@ -68,7 +63,53 @@ public JavaDoc2MarkdownConverter(String javadoc) { } @Override - String convert(String rawHtml) { - return remark.convert(rawHtml); + public String convert(String html) { + Document document = Jsoup.parse(html); + //Add missing table headers if necessary, else most Markdown renderers will crap out + document.select("table").forEach(JavaDoc2MarkdownConverter::addMissingTableHeaders); + + String markdown = CONVERTER.convert(document); + if (markdown.endsWith(LINE_SEPARATOR)) {// FlexmarkHtmlConverter keeps adding an extra line + markdown = markdown.substring(0, markdown.length() - LINE_SEPARATOR.length()); + } + + return markdown; + } + + /** + * Adds a new row header if the given table doesn't have any. + * + * @param table + * the HTML table to check for a header + */ + private static void addMissingTableHeaders(Element table) { + int numCols = 0; + for (Element child : table.children()) { + if ("thead".equals(child.nodeName())) { + // Table already has a header, nothing else to do + return; + } + if ("tbody".equals(child.nodeName())) { + Elements rows = child.getElementsByTag("tr"); + if (!rows.isEmpty()) { + for (Element row : rows) { + int colSize = row.getElementsByTag("td").size(); + //Keep the biggest column size + if (colSize > numCols) { + numCols = colSize; + } + } + } + } + } + if (numCols > 0) { + //Create a new header row based on the number of columns already found + Element newHeader = new Element("tr"); + for (int i = 0; i < numCols; i++) { + newHeader.appendChild(new Element("th")); + } + //Insert header row in 1st position in the table + table.insertChildren(0, newHeader); + } } } diff --git a/src/test/java/com/redhat/devtools/intellij/qute/psi/template/TemplateGetJavadocTest.java b/src/test/java/com/redhat/devtools/intellij/qute/psi/template/TemplateGetJavadocTest.java index 63a2c903d..998ed022b 100644 --- a/src/test/java/com/redhat/devtools/intellij/qute/psi/template/TemplateGetJavadocTest.java +++ b/src/test/java/com/redhat/devtools/intellij/qute/psi/template/TemplateGetJavadocTest.java @@ -41,7 +41,7 @@ public void testgetFieldJavadoc() throws Exception { DocumentFormat.Markdown); String actual = QuteSupportForTemplate.getInstance().getJavadoc(params, getJDTUtils(), new EmptyProgressIndicator()); - String expected = " The name of the item "; + String expected = "The name of the item"; assertEquals(expected, actual); } @@ -57,9 +57,11 @@ public void testgetMethodJavadoc() throws Exception { DocumentFormat.Markdown); String actual = QuteSupportForTemplate.getInstance().getJavadoc(params, getJDTUtils(), new EmptyProgressIndicator()); - String expected = " Returns the derived items. \n" + - " * Returns:\n" + - " - the derived items"; + String expected = """ + Returns the derived items. + + * **Returns:** + * the derived items"""; assertEquals(expected, actual); } diff --git a/src/test/java/com/redhat/microprofile/psi/quarkus/QuarkusKubernetesTest.java b/src/test/java/com/redhat/microprofile/psi/quarkus/QuarkusKubernetesTest.java index 54334c4e6..62490227b 100644 --- a/src/test/java/com/redhat/microprofile/psi/quarkus/QuarkusKubernetesTest.java +++ b/src/test/java/com/redhat/microprofile/psi/quarkus/QuarkusKubernetesTest.java @@ -37,15 +37,18 @@ public void testKubernetes() throws Exception { // io.dekorate.kubernetes.annotation.KubernetesApplication p(null, "kubernetes.name", "java.lang.String", - "The name of the application. This value will be used for naming Kubernetes resources like: - Deployment - Service and so on ... If no value is specified it will attempt to determine the name using the following rules: If its a maven/gradle project use the artifact id. Else if its a bazel project use the name. Else if the system property app.name is present it will be used. Else find the project root folder and use its name (root folder detection is done by moving to the parent folder until .git is found)." - + System.lineSeparator() + "" + System.lineSeparator() + " * **Returns:**" + System.lineSeparator() - + " " + System.lineSeparator() + " * The specified application name.", + """ + The name of the application. This value will be used for naming Kubernetes resources like: - Deployment - Service and so on ... If no value is specified it will attempt to determine the name using the following rules: If its a maven/gradle project use the artifact id. Else if its a bazel project use the name. Else if the system property app.name is present it will be used. Else find the project root folder and use its name (root folder detection is done by moving to the parent folder until .git is found). + + * **Returns:** + * The specified application name.""", true, "io.dekorate.kubernetes.annotation.KubernetesApplication", null, "name()Ljava/lang/String;", 0, null), - p(null, "kubernetes.readiness-probe.initial-delay-seconds", "int", "The amount of time to wait in seconds before starting to probe." + // - System.lineSeparator() + "" + System.lineSeparator() + // - " * **Returns:**" + System.lineSeparator() + // - " " + System.lineSeparator() + " * The initial delay.", + p(null, "kubernetes.readiness-probe.initial-delay-seconds", "int", """ + The amount of time to wait in seconds before starting to probe. + + * **Returns:** + * The initial delay.""", true, "io.dekorate.kubernetes.annotation.Probe", null, "initialDelaySeconds()I", 0, "0"), p(null, "kubernetes.annotations[*].key", "java.lang.String", null, true, @@ -56,10 +59,10 @@ public void testKubernetes() throws Exception { "protocol()Lio/dekorate/kubernetes/annotation/Protocol;", 0, "TCP"), p(null, "kubernetes.deployment.target", "java.lang.String", - "To enable the generation of OpenShift resources, you need to include OpenShift in the target platforms: `kubernetes.deployment.target=openshift`." - + System.lineSeparator() + "" - + "If you need to generate resources for both platforms (vanilla Kubernetes and OpenShift), then you need to include both (coma separated)." - + System.lineSeparator() + "" + "`kubernetes.deployment.target=kubernetes, openshift`.", + """ + To enable the generation of OpenShift resources, you need to include OpenShift in the target platforms: `kubernetes.deployment.target=openshift`. + If you need to generate resources for both platforms (vanilla Kubernetes and OpenShift), then you need to include both (coma separated). + `kubernetes.deployment.target=kubernetes, openshift`.""", true, null, null, null, 0, "kubernetes"), p(null, "kubernetes.registry", "java.lang.String", "Specify the docker registry.", true, null, null, null, 0, null)); @@ -82,18 +85,20 @@ public void testOpenshift() throws Exception { MicroProfileProjectInfo info = PropertiesManager.getInstance().getMicroProfileProjectInfo(module, MicroProfilePropertiesScope.SOURCES_AND_DEPENDENCIES, ClasspathKind.SRC, PsiUtilsLSImpl.getInstance(myProject), DocumentFormat.Markdown, new EmptyProgressIndicator()); assertProperties(info, - // io.dekorate.openshift.annotation.OpenshiftApplication p(null, "openshift.name", "java.lang.String", - "The name of the application. This value will be used for naming Kubernetes resources like: - Deployment - Service and so on ... If no value is specified it will attempt to determine the name using the following rules: If its a maven/gradle project use the artifact id. Else if its a bazel project use the name. Else if the system property app.name is present it will be used. Else find the project root folder and use its name (root folder detection is done by moving to the parent folder until .git is found)." - + System.lineSeparator() + "" + System.lineSeparator() + " * **Returns:**" + System.lineSeparator() - + " " + System.lineSeparator() + " * The specified application name.", + """ + The name of the application. This value will be used for naming Kubernetes resources like: - Deployment - Service and so on ... If no value is specified it will attempt to determine the name using the following rules: If its a maven/gradle project use the artifact id. Else if its a bazel project use the name. Else if the system property app.name is present it will be used. Else find the project root folder and use its name (root folder detection is done by moving to the parent folder until .git is found). + + * **Returns:** + * The specified application name.""", true, "io.dekorate.openshift.annotation.OpenshiftApplication", null, "name()Ljava/lang/String;", 0, null), - p(null, "openshift.readiness-probe.initial-delay-seconds", "int", "The amount of time to wait in seconds before starting to probe." + // - System.lineSeparator() + "" + System.lineSeparator() + // - " * **Returns:**" + System.lineSeparator() + // - " " + System.lineSeparator() + " * The initial delay.", true, + p(null, "openshift.readiness-probe.initial-delay-seconds", "int", """ + The amount of time to wait in seconds before starting to probe. + + * **Returns:** + * The initial delay.""", true, "io.dekorate.kubernetes.annotation.Probe", null, "initialDelaySeconds()I", 0, "0"), p(null, "openshift.annotations[*].key", "java.lang.String", null, true, @@ -109,7 +114,6 @@ public void testOpenshift() throws Exception { assertPropertiesDuplicate(info); assertHints(info, - h("io.dekorate.kubernetes.annotation.Protocol", null, true, "io.dekorate.kubernetes.annotation.Protocol", vh("TCP", null, null), // vh("UDP", null, null))); @@ -124,28 +128,27 @@ public void testS2i() throws Exception { MicroProfileProjectInfo info = PropertiesManager.getInstance().getMicroProfileProjectInfo(module, MicroProfilePropertiesScope.SOURCES_AND_DEPENDENCIES, ClasspathKind.SRC, PsiUtilsLSImpl.getInstance(myProject), DocumentFormat.Markdown, new EmptyProgressIndicator()); assertProperties(info, - // io.dekorate.s2i.annotation.S2iBuild - p(null, "s2i.docker-file", "java.lang.String", "The relative path of the Dockerfile, from the module root." + // - System.lineSeparator() + "" + System.lineSeparator() + // - " * **Returns:**" + // - System.lineSeparator() + // - " " + // - System.lineSeparator() + // - " * The relative path.", true, "io.dekorate.s2i.annotation.S2iBuild", null, + p(null, "s2i.docker-file", "java.lang.String", """ + The relative path of the Dockerfile, from the module root. + + * **Returns:** + * The relative path.""", true, "io.dekorate.s2i.annotation.S2iBuild", null, "dockerFile()Ljava/lang/String;", 0, "Dockerfile"), p(null, "s2i.group", "java.lang.String", - "The group of the application. This value will be use as image user." - + System.lineSeparator() + "" + System.lineSeparator() + " * **Returns:**" + System.lineSeparator() - + " " + System.lineSeparator() + " * The specified group name.", + """ + The group of the application. This value will be use as image user. + + * **Returns:** + * The specified group name.""", true, "io.dekorate.s2i.annotation.S2iBuild", null, "group()Ljava/lang/String;", 0, null), p("quarkus-container-image-s2i", "quarkus.s2i.jar-directory", "java.lang.String", - "The directory where the jar is added during the assemble phase." + // - "\n" + // - "This is dependent on the S2I image and should be supplied if a non default image is used.", + """ + The directory where the jar is added during the assemble phase. + This is dependent on the S2I image and should be supplied if a non default image is used.""", true, "io.quarkus.container.image.s2i.deployment.S2iConfig", "jarDirectory", null, 1, "/deployments/")); @@ -159,17 +162,15 @@ public void testS2i() throws Exception { public void testDocker() throws Exception { Module module = loadMavenProject(QuarkusMavenProjectName.kubernetes, true); MicroProfileProjectInfo info = PropertiesManager.getInstance().getMicroProfileProjectInfo(module, MicroProfilePropertiesScope.SOURCES_AND_DEPENDENCIES, ClasspathKind.SRC, PsiUtilsLSImpl.getInstance(myProject), DocumentFormat.Markdown, new EmptyProgressIndicator()); + String description = """ + The relative path of the Dockerfile, from the module root. + * **Returns:** + * The relative path."""; assertProperties(info, // io.dekorate.docker.annotation.DockerBuild - p(null, "docker.docker-file", "java.lang.String", "The relative path of the Dockerfile, from the module root." + // - System.lineSeparator() + "" + System.lineSeparator() + // - " * **Returns:**" + // - System.lineSeparator() + // - " " + // - System.lineSeparator() + // - " * The relative path.", true, "io.dekorate.docker.annotation.DockerBuild", null, + p(null, "docker.docker-file", "java.lang.String", description, true, "io.dekorate.docker.annotation.DockerBuild", null, "dockerFile()Ljava/lang/String;", 0, "Dockerfile")); assertPropertiesDuplicate(info);