diff --git a/src/main/java/org/openrewrite/java/migrate/lang/JavadocToMarkdownDocComment.java b/src/main/java/org/openrewrite/java/migrate/lang/JavadocToMarkdownDocComment.java index ccb42e684c..aec73d48cb 100644 --- a/src/main/java/org/openrewrite/java/migrate/lang/JavadocToMarkdownDocComment.java +++ b/src/main/java/org/openrewrite/java/migrate/lang/JavadocToMarkdownDocComment.java @@ -237,6 +237,15 @@ private void convertLink(Javadoc.Link link) { private void convertStartElement(Javadoc.StartElement element) { String name = element.getName().toLowerCase(); if (inPre && !"pre".equals(name)) { + if ("code".equals(name)) { + // Place the fence (and optional language) on its own line so the + // code content starts fresh; otherwise the first code line would be + // consumed as the fenced code block's info string. + currentLine.append(extractCodeLanguage(element)); + lines.add(currentLine.toString()); + currentLine = new StringBuilder(); + return; + } renderHtmlStartElement(element); return; } @@ -293,6 +302,31 @@ private void convertStartElement(Javadoc.StartElement element) { } } + private String extractCodeLanguage(Javadoc.StartElement element) { + return element.getAttributes().stream() + .filter(attr -> attr instanceof Javadoc.Attribute) + .map(attr -> (Javadoc.Attribute) attr) + .filter(a -> "class".equalsIgnoreCase(a.getName())) + .filter(a -> a.getValue() != null) + .map(a -> renderInline(a.getValue()).trim()) + .map(JavadocToMarkdownConverter::stripAttributeQuotes) + .findFirst() + .orElse(""); + } + + private static String stripAttributeQuotes(String value) { + if (value.length() < 2) { + return value; + } + + char first = value.charAt(0); + char last = value.charAt(value.length() - 1); + if ((first == '\'' && last == '\'') || (first == '"' && last == '"')) { + return value.substring(1, value.length() - 1); + } + return value; + } + private void renderHtmlStartElement(Javadoc.StartElement element) { currentLine.append('<').append(element.getName()); for (Javadoc attr : element.getAttributes()) { @@ -314,12 +348,21 @@ private void renderHtmlStartElement(Javadoc.StartElement element) { private void convertEndElement(Javadoc.EndElement element) { String name = element.getName().toLowerCase(); if (inPre && !"pre".equals(name)) { - currentLine.append("'); + if (!"code".equals(name)) { + currentLine.append("'); + } return; } switch (name) { case "pre": inPre = false; + // Flush any trailing inline code (e.g. `...);`) so the + // closing fence sits on its own line, as Markdown requires. Only flush + // real content; a whitespace-only line means the fence is already alone. + if (!currentLine.toString().trim().isEmpty()) { + lines.add(currentLine.toString()); + currentLine = new StringBuilder(); + } currentLine.append("```"); break; case "code": diff --git a/src/test/java/org/openrewrite/java/migrate/lang/JavadocToMarkdownDocCommentTest.java b/src/test/java/org/openrewrite/java/migrate/lang/JavadocToMarkdownDocCommentTest.java index 601aa856c7..ed9216f8e2 100644 --- a/src/test/java/org/openrewrite/java/migrate/lang/JavadocToMarkdownDocCommentTest.java +++ b/src/test/java/org/openrewrite/java/migrate/lang/JavadocToMarkdownDocCommentTest.java @@ -496,6 +496,134 @@ public void m() {} ); } + @Test + public void codeTagConverted() { + rewriteRun( + spec -> spec.recipe(new JavadocToMarkdownDocComment()), + java( + """ + class Test { + /** + *
 public class TolkienCharacter {
+                   *   String name;
+                   *   double height;
+                   * }
+                   * 
+ */ + public void withErrorMessageForFields() { + } + } + """, + """ + class Test { + /// ``` + /// public class TolkienCharacter { + /// String name; + /// double height; + /// } + /// ``` + public void withErrorMessageForFields() { + } + } + """) + ); + } + + @Test + public void codeTagWithEmptyLanguageConverted() { + rewriteRun( + spec -> spec.recipe(new JavadocToMarkdownDocComment()), + java( + """ + class Test { + /** + *
 public class TolkienCharacter {
+                   *   String name;
+                   *   double height;
+                   * }
+                   * 
+ */ + public void withErrorMessageForFields() { + } + } + """, + """ + class Test { + /// ``` + /// public class TolkienCharacter { + /// String name; + /// double height; + /// } + /// ``` + public void withErrorMessageForFields() { + } + } + """) + ); + } + + @Test + public void codeTagLanguageNotConverted() { + rewriteRun( + spec -> spec.recipe(new JavadocToMarkdownDocComment()), + java( + """ + class Test { + /** + *
 public class TolkienCharacter {
+                   *   String name;
+                   *   double height;
+                   * }
+                   * 
+ */ + public void withErrorMessageForFields() { + } + } + """, + """ + class Test { + /// ```java + /// public class TolkienCharacter { + /// String name; + /// double height; + /// } + /// ``` + public void withErrorMessageForFields() { + } + } + """) + ); + } + + @Test + public void codeTagClosedInline() { + rewriteRun( + spec -> spec.recipe(new JavadocToMarkdownDocComment()), + java( + """ + class Test { + /** + *
 // assertion will pass
+                   * assertThat("abc").contains("ab");
+ */ + public void withErrorMessageForFields() { + } + } + """, + """ + class Test { + /// ```java + /// // assertion will pass + /// assertThat("abc").contains("ab"); + /// ``` + public void withErrorMessageForFields() { + } + } + """) + ); + } + + @Nested class Jep467FlagshipExamples {