From 1f9d2acbab0b03cd37f3060d3b2d3ca8fcfff45d Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Tue, 6 Oct 2026 04:23:54 -0700 Subject: [PATCH] Preserve relative indentation in `
{@code ...}` blocks
 lacking leading `*`.

In classic Javadoc, lines inside `
` blocks that omit the `*` margin prefix
previously had all leading whitespace stripped via `line.stripLeading()`,
destroying relative indentation in code samples.

Instead, strip only up to the comment's base indentation column so that
`JavadocLexer.deindentPreCodeBlocks` can preserve the relative indentation of
the sample.

PiperOrigin-RevId: 994305827
---
 .../java/javadoc/JavadocFormatter.java        | 24 +++++++++---
 .../java/JavadocFormattingTest.java           | 37 +++++++++++++++++++
 2 files changed, 56 insertions(+), 5 deletions(-)

diff --git a/core/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocFormatter.java b/core/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocFormatter.java
index 0f49edf43..83d997476 100644
--- a/core/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocFormatter.java
+++ b/core/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocFormatter.java
@@ -236,15 +236,29 @@ private static String classicCommentText(String input) {
     // The end result should not strip the `*` from `* foo`.
     List processedLines = new ArrayList<>();
     processedLines.add(lines.get(0));
-    for (String line : lines.subList(1, lines.size())) {
+    List continuations = lines.subList(1, lines.size());
+    int starPrefixLength =
+        continuations.stream()
+            .map(CLASSIC_PREFIX_PATTERN::matcher)
+            .filter(Matcher::find)
+            .mapToInt(Matcher::end)
+            .min()
+            .orElse(0);
+    int nonStarMinLeading =
+        continuations.stream()
+            .filter(line -> !CLASSIC_PREFIX_PATTERN.matcher(line).find())
+            .filter(line -> NOT_SPACE_OR_TAB.matchesAnyOf(line))
+            .mapToInt(NOT_SPACE_OR_TAB::indexIn)
+            .min()
+            .orElse(0);
+    int nonStarStripCount = Math.min(starPrefixLength, nonStarMinLeading);
+    for (String line : continuations) {
       Matcher m = CLASSIC_PREFIX_PATTERN.matcher(line);
       if (m.find()) {
         processedLines.add(m.replaceFirst(""));
       } else {
-        // Input line did not have leading `*`. In that case, it's hard to know what is supposed to
-        // be indentation of the comment as a whole and what is supposed to be indentation of the
-        // content. We just strip all leading whitespace.
-        processedLines.add(line.stripLeading());
+        processedLines.add(
+            line.length() >= nonStarStripCount ? line.substring(nonStarStripCount) : "");
       }
     }
     // Unlike Markdown comments, stripping common leading whitespace is not mandated by any
diff --git a/core/src/test/java/com/google/googlejavaformat/java/JavadocFormattingTest.java b/core/src/test/java/com/google/googlejavaformat/java/JavadocFormattingTest.java
index c95358f6c..1fc5d0b74 100644
--- a/core/src/test/java/com/google/googlejavaformat/java/JavadocFormattingTest.java
+++ b/core/src/test/java/com/google/googlejavaformat/java/JavadocFormattingTest.java
@@ -2534,6 +2534,43 @@ class Test {}
     doFormatTest(input, expected);
   }
 
+  @Test
+  public void preCodeWithoutLeadingStarPreservesIndent() {
+    String input =
+        """
+        /**
+         * 
{@code
+        class Demo {
+          // Comment
+          static final int X = 1;
+
+          public static void example() {
+            int y = 2;
+          }
+        }
+         * }
+ */ + class Test {} + """; + String expected = + """ + /** + *
{@code
+         * class Demo {
+         *   // Comment
+         *   static final int X = 1;
+         *
+         *   public static void example() {
+         *     int y = 2;
+         *   }
+         * }
+         * }
+ */ + class Test {} + """; + doFormatTest(input, expected); + } + // TODO: b/346668798 - Test the following Markdown constructs, and make the tests work as needed. // We can assume that the CommonMark parser correctly handles Markdown, so the question is whether // they are subsequently mishandled by our formatting logic. So for example the CommonMark parser