From a1b84fbc7559778a5f8fba4b0817b51602494f45 Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Thu, 24 Sep 2026 22:59:29 +0300 Subject: [PATCH 1/2] Format a class body that holds nothing but semicolons "class A { ; }" failed with "expected token: ';'; generated } instead". javac drops a stray semicolon from the member list, so the body looked empty and the formatter wrote "{}" without ever emitting the ";" token that was still in the input. A semicolon between real members was already handled; only a body with no member at all lost it. This ports google/google-java-format#1104 by Liam Miller-Cushon: an empty body whose next token is ";" writes those semicolons one per line at the member indent before the closing brace, as the member list does. The golden SemicolonInClass comes from upstream, with the expected output in this project's style. The 15,747 files of the JDK 21 sources format exactly as before. --- .../com/palantir/javaformat/java/JavaInputAstVisitor.java | 8 ++++++++ .../javaformat/java/testdata/SemicolonInClass.input | 3 +++ .../javaformat/java/testdata/SemicolonInClass.output | 3 +++ 3 files changed, 14 insertions(+) create mode 100644 open-java-format/src/test/resources/com/palantir/javaformat/java/testdata/SemicolonInClass.input create mode 100644 open-java-format/src/test/resources/com/palantir/javaformat/java/testdata/SemicolonInClass.output diff --git a/open-java-format/src/main/java/com/palantir/javaformat/java/JavaInputAstVisitor.java b/open-java-format/src/main/java/com/palantir/javaformat/java/JavaInputAstVisitor.java index 0fd3569d8..dcecd1392 100644 --- a/open-java-format/src/main/java/com/palantir/javaformat/java/JavaInputAstVisitor.java +++ b/open-java-format/src/main/java/com/palantir/javaformat/java/JavaInputAstVisitor.java @@ -3790,6 +3790,14 @@ private void addBodyDeclarations( tokenBreakTrailingComment("{", plusTwo); builder.blankLineWanted(BlankLineWanted.NO); builder.open(ZERO); + if (builder.peekToken().equals(Optional.of(";"))) { + // A body with nothing but stray semicolons: javac drops them, so the member list is empty, but + // the tokens are still there and have to be written out, one per line at the member indent. + builder.open(memberIndent); + dropEmptyDeclarations(); + builder.close(); + builder.forcedBreak(); + } token("}", plusTwo); builder.close(); } diff --git a/open-java-format/src/test/resources/com/palantir/javaformat/java/testdata/SemicolonInClass.input b/open-java-format/src/test/resources/com/palantir/javaformat/java/testdata/SemicolonInClass.input new file mode 100644 index 000000000..52d3c1226 --- /dev/null +++ b/open-java-format/src/test/resources/com/palantir/javaformat/java/testdata/SemicolonInClass.input @@ -0,0 +1,3 @@ +class SemicolonInClass { + ; +} diff --git a/open-java-format/src/test/resources/com/palantir/javaformat/java/testdata/SemicolonInClass.output b/open-java-format/src/test/resources/com/palantir/javaformat/java/testdata/SemicolonInClass.output new file mode 100644 index 000000000..e5a5ed860 --- /dev/null +++ b/open-java-format/src/test/resources/com/palantir/javaformat/java/testdata/SemicolonInClass.output @@ -0,0 +1,3 @@ +class SemicolonInClass { + ; +} From 956f1d804858a54858471766191146d00e9e46eb Mon Sep 17 00:00:00 2001 From: Alex Abashev Date: Thu, 24 Sep 2026 23:00:34 +0300 Subject: [PATCH 2/2] Cover the other bodies that can hold only semicolons Interfaces, enums, records, annotation types and anonymous classes go through the same member-list code as classes, so the fix covers them too; the golden pins that, together with two semicolons in a row, a comment after the opening brace, and a semicolon in front of a real member, which took the existing path all along. --- .../testdata/ojf-semicolon-only-body.input | 13 +++++++ .../testdata/ojf-semicolon-only-body.output | 36 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 open-java-format/src/test/resources/com/palantir/javaformat/java/testdata/ojf-semicolon-only-body.input create mode 100644 open-java-format/src/test/resources/com/palantir/javaformat/java/testdata/ojf-semicolon-only-body.output diff --git a/open-java-format/src/test/resources/com/palantir/javaformat/java/testdata/ojf-semicolon-only-body.input b/open-java-format/src/test/resources/com/palantir/javaformat/java/testdata/ojf-semicolon-only-body.input new file mode 100644 index 000000000..5be48bd82 --- /dev/null +++ b/open-java-format/src/test/resources/com/palantir/javaformat/java/testdata/ojf-semicolon-only-body.input @@ -0,0 +1,13 @@ +interface B { ; } +enum C { X; ; } +record R(int a) { ; } +class E { ;; } +class F { Object o = new Object() { ; }; } +class G { // comment + ; +} +@interface H { ; } +class I { + ; + int x; +} diff --git a/open-java-format/src/test/resources/com/palantir/javaformat/java/testdata/ojf-semicolon-only-body.output b/open-java-format/src/test/resources/com/palantir/javaformat/java/testdata/ojf-semicolon-only-body.output new file mode 100644 index 000000000..38962018b --- /dev/null +++ b/open-java-format/src/test/resources/com/palantir/javaformat/java/testdata/ojf-semicolon-only-body.output @@ -0,0 +1,36 @@ +interface B { + ; +} + +enum C { + X; + ; +} + +record R(int a) { + ; +} + +class E { + ; + ; +} + +class F { + Object o = new Object() { + ; + }; +} + +class G { // comment + ; +} + +@interface H { + ; +} + +class I { + ; + int x; +}