Skip to content

Keep the comment prefix when wrapping a markdown docstring (palantir #1776) - #102

Merged
abashev merged 2 commits into
mainfrom
port-pjf-1776-markdown-docstring-prefix
Oct 3, 2026
Merged

abashev merged 2 commits into
mainfrom
port-pjf-1776-markdown-docstring-prefix

Conversation

@abashev

@abashev abashev commented Oct 3, 2026

Copy link
Copy Markdown
Collaborator

Port of palantir#1776 by asm0dey, cherry-picked with the author kept. It fixes palantir#1803: from JDK 23 on, javac hands a run of /// markdown doc lines over as one comment token, every line after the first still carries its source indentation, and JavaCommentsHelper.wrapLineComments read the slash prefix, and the width budget, from that untrimmed line. The prefix came back empty, so the wrapped remainder was written out as bare code.

Reproduced on main (2fbf175)

The class from palantir#1803, formatted on JDK 25 and 27, comes out exactly as reported:

    /// @param b Aaaaaaa aaaaaaaaaa aaaaaaaaaaaaa aaaaaa aaaaaaaaaaaaaa aaaaaaaaa, aaaaaa aaaaaaaaa (aaaaaaaa
    XXXXXXXX aaaaaaa)
    public record Yyy(String a, String b) {}

javac then fails with ';' expected. The default CLI run reports the same errors and exits with 2, because the import pass re-parses the broken result; that is the crash the issue shows in Spotless. On JDK 21 each /// line is its own token and the output is right.

It is not only a contrived case: in the JDK 27 sources, jdk.compiler/com/sun/source/tree/VariableTree.java has a 141-column /// line. On JDK 27, main turns its overflow into bare text and the CLI refuses the file with = expected; with this change the overflow wraps as a /// line.

The change

wrapLineComments trims the line before reading the prefix and measuring it: six lines in JavaCommentsHelper. From the upstream description: a continuation line keeps /// when it wraps; a line at the column limit is no longer wrapped (the indentation was counted twice); an unbreakable token is left long instead of becoming an empty /// followed by bare text; ///foo becomes /// foo on every line of the run, so the same source no longer formats differently depending on whether the running JDK hands the run over as one token or one per line.

Tests: JavaCommentsHelperTest builds the multi-line token directly, so the JDK 21 test task covers the bug; four of its six tests fail on main. The end-to-end test in FormatterTest is gated at JDK 23 and runs in the JDK 25, 26 and 27 jobs.

Adapted to the fork

  • Style.OJF for Style.PALANTIR.
  • The fork's JavaCommentsHelper takes the JavaInput rather than a line separator, so the test builds one from class T {}.
  • The test class and its methods are package-private and final, as the Picnic rules JUnitClassModifiers and JUnitMethodDeclaration demand.
  • No changelog entry: the fork keeps none.

Checks

  • :open-java-format:test on JDK 21 (gated test skipped), 25 and 27 (gated test passes): 1604 tests, 0 failures.
  • The issue's class on JDK 25 and 27: compiles, a second run changes nothing, and the default run agrees with the run that skips the import passes.
  • Corpora formatted with main's jar and this branch's jar on the matching runtime: JDK 21 (15,747 files) and JDK 25 (15,368) identical; JDK 27 (15,310): one file differs, the VariableTree.java above, which main could not format at all.

From JDK 23 on, javac returns a run of `///` lines (JEP 467) as a single comment token,
so every line after the first still carries its source indentation when
`JavaCommentsHelper.wrapLineComments` sees it. The prefix was read from index 0 of that
untrimmed line, came back empty, and the wrapped remainder was emitted with no `///` at
all -- as bare code. The same indentation was then counted twice in the width test, so
lines under the column limit were wrapped as well.

In Palantir style the result compiles, which is the dangerous part:

    class P1 {
        /// Summary line.
        /// This second markdown line is long enough to overflow the palantir one hundred twenty column limit @deprecated
        void f() {}
    }

formats to the comment, then `@Deprecated` on a line of its own, then the method.
`javap -v` goes from zero Deprecated markers to four, `javac` exits 0, and nothing
downstream notices that the formatter deprecated a method. In Google style the same
input produces output that does not compile (`illegal start of type`), and formatting it
again fails to parse. Both need the `///` block to be indented, which is why a
`column0 == 0` case does not reproduce it.

The prefix and the width budget now come from the trimmed line, which
`indentLineComments` trims and re-indents anyway. An unbreakable token is left long
instead of producing an empty `///` followed by bare text, and the missing-space rule
(`///foo` -> `/// foo`) now applies to every line of the run, so the same source no
longer formats differently depending on whether the running JDK hands the run over as
one token or as one token per line.

JavaCommentsHelperTest builds the multi-line comment token directly, so the JDK 21 test
task covers the bug that only a JDK 23 or later parser can produce; four of its six
tests fail without this change. The end-to-end test in FormatterTest is gated at 23.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 0f3a437)
@abashev
abashev enabled auto-merge October 3, 2026 18:31
@abashev
abashev disabled auto-merge October 3, 2026 18:54
@abashev
abashev merged commit 825828a into main Oct 3, 2026
16 checks passed
@abashev
abashev deleted the port-pjf-1776-markdown-docstring-prefix branch October 3, 2026 18:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Code won't compile after Markdown Javadoc is formatted

2 participants