diff --git a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11ParserVisitor.java b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11ParserVisitor.java index b85e3fc19ef..b947261f76c 100644 --- a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11ParserVisitor.java +++ b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11ParserVisitor.java @@ -478,6 +478,7 @@ public J visitClass(ClassTree node, Space fmt) { members.add(enumSet); } members.addAll(convertStatements(membersMultiVariablesSeparated)); + addPossibleEmptyStatementsBeforeClosingBrace(members); J.Block body = new J.Block(randomId(), bodyPrefix, Markers.EMPTY, new JRightPadded<>(false, EMPTY, Markers.EMPTY), members, sourceBefore("}")); @@ -1081,8 +1082,11 @@ public J visitNewClass(NewClassTree node, Space fmt) { } } + List> converted = convertStatements(members); + addPossibleEmptyStatementsBeforeClosingBrace(converted); + body = new J.Block(randomId(), bodyPrefix, Markers.EMPTY, new JRightPadded<>(false, EMPTY, Markers.EMPTY), - convertStatements(members), sourceBefore("}")); + converted, sourceBefore("}")); } JCNewClass jcNewClass = (JCNewClass) node; @@ -1857,7 +1861,7 @@ private List> convertStatements(@Nullable List> convertStatements(@Nullable List trees, Function suffix) { if (trees == null || trees.isEmpty()) { - return emptyList(); + return new ArrayList<>(); } Map> treesGroupedByStartPosition = new LinkedHashMap<>(); @@ -1875,6 +1879,19 @@ private List> convertStatements(@Nullable List startPosition) continue; + if (!(t instanceof JCSkip)) { + while (cursor < startPosition) { + int nonWhitespaceIndex = indexOfNextNonWhitespace(cursor, source); + int semicolonIndex = source.charAt(nonWhitespaceIndex) == ';' ? nonWhitespaceIndex : -1; + if (semicolonIndex > -1) { + Space prefix = Space.format(source, cursor, semicolonIndex); + converted.add(new JRightPadded<>(new J.Empty(randomId(), prefix, Markers.EMPTY), Space.EMPTY, Markers.EMPTY)); + cursor = semicolonIndex + 1; + } else { + break; + } + } + } converted.add(convert(treeGroup.get(0), suffix)); } else { // multi-variable declarations are split into independent overlapping JCVariableDecl's by the OpenJDK AST @@ -2288,4 +2305,18 @@ Space formatWithCommentTree(String prefix, JCTree tree, DCTree.@Nullable DCDocCo return fmt; } + + private void addPossibleEmptyStatementsBeforeClosingBrace(List> converted) { + while (true) { + int closingBracePosition = positionOfNext("}", null); + int semicolonPosition = positionOfNext(";", null); + + if (semicolonPosition > -1 && semicolonPosition < closingBracePosition) { + converted.add(new JRightPadded<>(new J.Empty(randomId(), sourceBefore(";"), Markers.EMPTY), Space.EMPTY, Markers.EMPTY)); + cursor = semicolonPosition + 1; + } else { + break; + } + } + } } diff --git a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17ParserVisitor.java b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17ParserVisitor.java index 39512ecb49f..549181ac893 100644 --- a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17ParserVisitor.java +++ b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17ParserVisitor.java @@ -46,8 +46,6 @@ import javax.lang.model.element.Modifier; import javax.lang.model.element.Name; import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.nio.charset.Charset; import java.nio.file.Path; import java.util.*; @@ -64,6 +62,7 @@ import static java.util.stream.Collectors.toList; import static java.util.stream.StreamSupport.stream; import static org.openrewrite.Tree.randomId; +import static org.openrewrite.internal.StringUtils.indexOf; import static org.openrewrite.internal.StringUtils.indexOfNextNonWhitespace; import static org.openrewrite.java.tree.Space.EMPTY; import static org.openrewrite.java.tree.Space.format; @@ -539,6 +538,7 @@ public J visitClass(ClassTree node, Space fmt) { members.add(enumSet); } members.addAll(convertStatements(membersMultiVariablesSeparated)); + addPossibleEmptyStatementsBeforeClosingBrace(members); J.Block body = new J.Block(randomId(), bodyPrefix, Markers.EMPTY, new JRightPadded<>(false, EMPTY, Markers.EMPTY), members, sourceBefore("}")); @@ -1154,8 +1154,11 @@ public J visitNewClass(NewClassTree node, Space fmt) { } } + List> converted = convertStatements(members); + addPossibleEmptyStatementsBeforeClosingBrace(converted); + body = new J.Block(randomId(), bodyPrefix, Markers.EMPTY, new JRightPadded<>(false, EMPTY, Markers.EMPTY), - convertStatements(members), sourceBefore("}")); + converted, sourceBefore("}")); } JCNewClass jcNewClass = (JCNewClass) node; @@ -1937,7 +1940,7 @@ private List> convertStatements(@Nullable List> convertStatements(@Nullable List trees, Function suffix) { if (trees == null || trees.isEmpty()) { - return emptyList(); + return new ArrayList<>(); } Map> treesGroupedByStartPosition = new LinkedHashMap<>(); @@ -1955,6 +1958,19 @@ private List> convertStatements(@Nullable List startPosition) continue; + if (!(t instanceof JCSkip)) { + while (cursor < startPosition) { + int nonWhitespaceIndex = indexOfNextNonWhitespace(cursor, source); + int semicolonIndex = source.charAt(nonWhitespaceIndex) == ';' ? nonWhitespaceIndex : -1; + if (semicolonIndex > -1) { + Space prefix = Space.format(source, cursor, semicolonIndex); + converted.add(new JRightPadded<>(new J.Empty(randomId(), prefix, Markers.EMPTY), Space.EMPTY, Markers.EMPTY)); + cursor = semicolonIndex + 1; + } else { + break; + } + } + } converted.add(convert(treeGroup.get(0), suffix)); } else { // multi-variable declarations are split into independent overlapping JCVariableDecl's by the OpenJDK AST @@ -2369,4 +2385,18 @@ Space formatWithCommentTree(String prefix, JCTree tree, @Nullable DocCommentTree return fmt; } + + private void addPossibleEmptyStatementsBeforeClosingBrace(List> converted) { + while (true) { + int closingBracePosition = positionOfNext("}", null); + int semicolonPosition = positionOfNext(";", null); + + if (semicolonPosition > -1 && semicolonPosition < closingBracePosition) { + converted.add(new JRightPadded<>(new J.Empty(randomId(), sourceBefore(";"), Markers.EMPTY), Space.EMPTY, Markers.EMPTY)); + cursor = semicolonPosition + 1; + } else { + break; + } + } + } } diff --git a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserVisitor.java b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserVisitor.java index 01fa9e51774..9ae2e24d54a 100644 --- a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserVisitor.java +++ b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserVisitor.java @@ -540,6 +540,7 @@ public J visitClass(ClassTree node, Space fmt) { members.add(enumSet); } members.addAll(convertStatements(membersMultiVariablesSeparated)); + addPossibleEmptyStatementsBeforeClosingBrace(members); J.Block body = new J.Block(randomId(), bodyPrefix, Markers.EMPTY, new JRightPadded<>(false, EMPTY, Markers.EMPTY), members, sourceBefore("}")); @@ -1181,9 +1182,11 @@ public J visitNewClass(NewClassTree node, Space fmt) { members.add(m); } } + List> converted = convertStatements(members); + addPossibleEmptyStatementsBeforeClosingBrace(converted); body = new J.Block(randomId(), bodyPrefix, Markers.EMPTY, new JRightPadded<>(false, EMPTY, Markers.EMPTY), - convertStatements(members), sourceBefore("}")); + converted, sourceBefore("}")); } JCNewClass jcNewClass = (JCNewClass) node; @@ -1970,7 +1973,7 @@ private List> convertStatements(@Nullable List> convertStatements(@Nullable List trees, Function suffix) { if (trees == null || trees.isEmpty()) { - return emptyList(); + return new ArrayList<>(); } Map> treesGroupedByStartPosition = new LinkedHashMap<>(); @@ -1988,6 +1991,19 @@ private List> convertStatements(@Nullable List startPosition) continue; + if (!(t instanceof JCSkip)) { + while (cursor < startPosition) { + int nonWhitespaceIndex = indexOfNextNonWhitespace(cursor, source); + int semicolonIndex = source.charAt(nonWhitespaceIndex) == ';' ? nonWhitespaceIndex : -1; + if (semicolonIndex > -1) { + Space prefix = Space.format(source, cursor, semicolonIndex); + converted.add(new JRightPadded<>(new J.Empty(randomId(), prefix, Markers.EMPTY), Space.EMPTY, Markers.EMPTY)); + cursor = semicolonIndex + 1; + } else { + break; + } + } + } converted.add(convert(treeGroup.get(0), suffix)); } else { // multi-variable declarations are split into independent overlapping JCVariableDecl's by the OpenJDK AST @@ -2402,4 +2418,18 @@ Space formatWithCommentTree(String prefix, JCTree tree, @Nullable DocCommentTree return fmt; } + + private void addPossibleEmptyStatementsBeforeClosingBrace(List> converted) { + while (true) { + int closingBracePosition = positionOfNext("}", null); + int semicolonPosition = positionOfNext(";", null); + + if (semicolonPosition > -1 && semicolonPosition < closingBracePosition) { + converted.add(new JRightPadded<>(new J.Empty(randomId(), sourceBefore(";"), Markers.EMPTY), Space.EMPTY, Markers.EMPTY)); + cursor = semicolonPosition + 1; + } else { + break; + } + } + } } diff --git a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8ParserVisitor.java b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8ParserVisitor.java index a81d83221a4..8bb60f59aab 100644 --- a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8ParserVisitor.java +++ b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8ParserVisitor.java @@ -476,6 +476,7 @@ public J visitClass(ClassTree node, Space fmt) { members.add(enumSet); } members.addAll(convertStatements(membersMultiVariablesSeparated)); + addPossibleEmptyStatementsBeforeClosingBrace(members); J.Block body = new J.Block(randomId(), bodyPrefix, Markers.EMPTY, new JRightPadded<>(false, EMPTY, Markers.EMPTY), members, sourceBefore("}")); @@ -1079,8 +1080,11 @@ public J visitNewClass(NewClassTree node, Space fmt) { } } + List> converted = convertStatements(members); + addPossibleEmptyStatementsBeforeClosingBrace(converted); + body = new J.Block(randomId(), bodyPrefix, Markers.EMPTY, new JRightPadded<>(false, EMPTY, Markers.EMPTY), - convertStatements(members), sourceBefore("}")); + converted, sourceBefore("}")); } JCNewClass jcNewClass = (JCNewClass) node; @@ -1850,7 +1854,7 @@ private List> convertStatements(@Nullable List> convertStatements(@Nullable List trees, Function suffix) { if (trees == null || trees.isEmpty()) { - return emptyList(); + return new ArrayList<>(); } Map> treesGroupedByStartPosition = new LinkedHashMap<>(); @@ -1868,6 +1872,19 @@ private List> convertStatements(@Nullable List startPosition) continue; + if (!(t instanceof JCSkip)) { + while (cursor < startPosition) { + int nonWhitespaceIndex = indexOfNextNonWhitespace(cursor, source); + int semicolonIndex = source.charAt(nonWhitespaceIndex) == ';' ? nonWhitespaceIndex : -1; + if (semicolonIndex > -1) { + Space prefix = Space.format(source, cursor, semicolonIndex); + converted.add(new JRightPadded<>(new J.Empty(randomId(), prefix, Markers.EMPTY), Space.EMPTY, Markers.EMPTY)); + cursor = semicolonIndex + 1; + } else { + break; + } + } + } converted.add(convert(treeGroup.get(0), suffix)); } else { // multi-variable declarations are split into independent overlapping JCVariableDecl's by the OpenJDK AST @@ -2284,4 +2301,18 @@ Space formatWithCommentTree(String prefix, JCTree tree, DCTree.@Nullable DCDocCo return fmt; } + + private void addPossibleEmptyStatementsBeforeClosingBrace(List> converted) { + while (true) { + int closingBracePosition = positionOfNext("}", null); + int semicolonPosition = positionOfNext(";", null); + + if (semicolonPosition > -1 && semicolonPosition < closingBracePosition) { + converted.add(new JRightPadded<>(new J.Empty(randomId(), sourceBefore(";"), Markers.EMPTY), Space.EMPTY, Markers.EMPTY)); + cursor = semicolonPosition + 1; + } else { + break; + } + } + } } diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/ClassDeclarationTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/ClassDeclarationTest.java index 99b63e7f402..65a13f6d26f 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/ClassDeclarationTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/ClassDeclarationTest.java @@ -17,6 +17,8 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.openrewrite.Issue; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.MinimumJava17; @@ -234,4 +236,44 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ob ) ); } + + @Test + @SuppressWarnings("UnnecessarySemicolon") + void unnecessarySemicolonInBody() { + rewriteRun( + java( + """ + class A { + int i = 0;; + int j = 0; + } + """ + ) + ); + } + + @ParameterizedTest + @ValueSource(strings = { + ";", + ";;;", + "; // comment", + "; // comment;with;semicolons", + "; /* comment;with;semicolons */", + "; /* comment\n*/", + "; // comment1\n// comment2\n;", + "static String method() { return null; };" + }) + void unnecessaryLeadingOrEndingSemicolons(String suffix) { + rewriteRun( + java( + """ + class A { + /*@@*/ + int i = 0; + /*@@*/ + } + """.replaceAll("/[*]@@[*]/", suffix) + ) + ); + } } diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/NewClassTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/NewClassTest.java index 8428f91e34f..4d027461e25 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/NewClassTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/NewClassTest.java @@ -187,7 +187,7 @@ class Test { void method() { Arrays.sort(new ArrayList[]{new ArrayList()}, new Comparator() { long time1, time2; - + @Override public int compare(Object o1, Object o2) { time1 = ((File) o1).lastModified(); @@ -201,4 +201,80 @@ public int compare(Object o1, Object o2) { ) ); } + + @Test + @SuppressWarnings("UnnecessarySemicolon") + void unnecessarySemicolonInBody1() { + rewriteRun( + java( + """ + class A { + Object o = new Object() {;;;;; + @Override + public String toString() { + return super.toString(); + } + }; + } + """ + ) + ); + } + + @Test + @SuppressWarnings("UnnecessarySemicolon") + void unnecessarySemicolonInBody1WithComment() { + rewriteRun( + java( + """ + class A { + Object o = new Object() { /*~~>*/ ; /*<~~*/ + @Override + public String toString() { + return super.toString(); + } + }; + } + """ + ) + ); + } + + @Test + @SuppressWarnings("UnnecessarySemicolon") + void unnecessarySemicolonInBody2() { + rewriteRun( + java( + """ + class B { + Object o = new Object() { + @Override + public String toString() { + return super.toString(); + };;;; + }; + } + """ + ) + ); + } + + @Test + @SuppressWarnings("UnnecessarySemicolon") + void unnecessarySemicolonInBody2WithComment() { + rewriteRun( + java( + """ + class B { + Object o = new Object() { + @Override + public String toString() { + return super.toString(); + } /*~~>*/ ; /*<~~*/ + }; + } + """ + ) + ); + } }