Skip to content

Keep a wrapped trailing line comment where the first run put it - #169

Merged
abashev merged 1 commit into
21.X.Xfrom
fix/wrapped-trailing-comment
Oct 30, 2026
Merged

abashev merged 1 commit into
21.X.Xfrom
fix/wrapped-trailing-comment

Conversation

@abashev

@abashev abashev commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

The fix for google/google-java-format#211, open upstream since 2017 and reported there again as google/google-java-format#1211 and google/google-java-format#1221; this repository had no issue for it. The input of google/google-java-format#1211 changes on a second run here too and no longer does; the one of google/google-java-format#1221 formats the same before and after, as its comment fits once the code breaks.

A trailing // comment too long for its line was wrapped in its own column, and the next run read the wrapped part as a comment of its own and moved it to the indentation of the code, so formatting the formatter's output changed it. This input

class T {
  public static void main(String[] args) { // one long incredibly unbroken sentence moving from topic to topic so that no-one had a chance to interrupt;
  }
}

came out of the first run as

class T {
    public static void main(
            String[]
                    args) { // one long incredibly unbroken sentence moving from topic to topic so that no-one had a
                            // chance to interrupt;
    }
}

and out of the second as

class T {
    public static void main(
            String[] args) { // one long incredibly unbroken sentence moving from topic to topic so that no-one had a
        // chance to interrupt;
    }
}

The code changed too: the first run broke it as far as it could for room the comment never got, and the second, with a shorter first line, joined String[] args again. A continuation line that the author had aligned under a trailing comment by hand moved to the indentation of the code the same way. Both runs now give

class T {
    public static void main(String[] args) { // one long incredibly unbroken sentence moving from topic to topic so that
                                             // no-one had a chance to interrupt;
    }
}

How

  • A line comment that starts a line in the column of the trailing line comment above continues that comment: JavaInput makes the two one tok, as javac does for a run of /// lines from JDK 23 on. The column, and whether code comes before the comment on its line, are read in the text, because Tok.getColumn() counts an operator such as == once for each of its characters. A block comment between the code and the trailing comment changes neither.
  • A line comment that spans lines counts only its slashes and its first word for the layout, so the code before it is laid out as if the comment were not there. A level that fits on one line and holds such a comment is laid out with none of its breaks taken instead of being written flat, so the comment still gets wrapped.
  • A trailing comment of one line that comes out wrapped did not fit however the code before it was broken. Formatter.format then lays the file out once more with that comment counted by its first word, which is how the next run lays out the wrapped comment it reads.
  • A comment that fits once the code breaks still breaks it, as before.

That is the rule picked from three variants, by examples. The other two counted a comment that spans lines by its joined text, so a hand-wrapped comment broke the code before it, or never broke code for a comment, so short comments wrapped at the right edge.

Effect

Of the 15,747 files of the JDK 21 sources, 129 format differently. In 118 only comment lines move: continuation lines that now stay under their comment, as in java/lang/Enum.java:

 @SuppressWarnings("serial") // No serialVersionUID needed due to
-// special-casing of enum classes.
+                            // special-casing of enum classes.

In 11 the code before a wrapped comment is no longer broken for it, as in com/apple/laf/AquaTreeUI.java:

-        final int height = icon
-                .getIconHeight(); // use the icon height so we don't get drift  we modify the bounds (by changing row
-                                  // height)
+        final int height = icon.getIconHeight(); // use the icon height so we don't get drift  we modify the bounds (by
+                                                 // changing row height)

67 files change on a second run instead of 80: 13 are fixed and none is new. None of the 67 changes a line with a // comment on its second run; they are other bugs.

The CLI and the plugins format such files differently, so this is an output change.

Tests

wrapLineComment moves from FormatterTest into a case, now that a second run keeps its output, with five more cases: a continuation next to a comment of its own, after operators, after a block comment, in an array initializer, and a comment that fits once the code breaks. FormatterCasesTest 880 → 886, FormatterTest 19 → 18. ./gradlew test passes on JDK 21, and the formatter module's 1407 tests on JDK 25 and 27 as well.

Left for later

Tok.getColumn() is off after a multi-character operator. The column also feeds the argument-table detection through actualColumn, so a fix there can move code, and it is a change of its own.

A trailing line comment too long for its line was wrapped in its own
column, and the next run read the wrapped part as a comment of its own
and moved it to the indentation of the code. The code before such a
comment was broken, too, for room the comment never got, and the next
run, seeing a shorter first line, joined it again.

A line comment that starts a line in the column of the trailing line
comment above now continues that comment: JavaInput makes them one
tok, as it is for a run of /// lines from JDK 23 on. A line comment
that spans lines counts only its slashes and first word for the
layout, so the code before it stays as it would be without it; a
level that fits on one line and holds such a comment is laid out
instead of written flat, so that the comment is still wrapped. A
trailing comment of one line that comes out wrapped, because it did
not fit however the code before it was broken, is laid out once more
that way, as the next run lays it out. A comment that fits once the
code breaks still breaks it.

The column of the trailing comment, and whether code comes before it
on its line, are read in the text, since Tok.getColumn() counts an
operator such as == once for each of its characters. A block comment
between the code and the trailing comment changes neither.

wrapLineComment moves from FormatterTest into a case, with five more.
Of the 15,747 files of the JDK 21 sources, 129 format differently: in
118 only comment lines move, continuation lines that now stay under
their comment, and in 11 the code before a wrapped comment is no
longer broken for it. 67 files change on a second run instead of 80,
and none that was stable is not.

Tests on JDK 21: FormatterCasesTest 880 -> 886, FormatterTest 19 ->
18; 1407 tests in the module, which pass on JDK 25 and 27 as well.
@abashev
abashev merged commit d17c6d7 into 21.X.X Oct 30, 2026
17 checks passed
@abashev
abashev deleted the fix/wrapped-trailing-comment branch October 30, 2026 09:11
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.

1 participant