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 b8dd7a5d14f..a9972f1e672 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 @@ -38,6 +38,7 @@ import org.openrewrite.java.JavaPrinter; import org.openrewrite.java.internal.JavaTypeCache; import org.openrewrite.java.marker.OmitParentheses; +import org.openrewrite.java.marker.TrailingComma; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markers; import org.openrewrite.style.NamedStyles; @@ -327,7 +328,7 @@ public J visitBreak(BreakTree node, Space fmt) { J.Identifier label = node.getLabel() == null ? null : new J.Identifier(randomId(), sourceBefore(node.getLabel().toString()), Markers.EMPTY, - emptyList(), skip(node.getLabel().toString()), null, null); + emptyList(), node.getLabel().toString(), null, null); return new J.Break(randomId(), fmt, Markers.EMPTY, label); } @@ -420,13 +421,31 @@ public J visitClass(ClassTree node, Space fmt) { JRightPadded enumSet = null; if (!jcEnums.isEmpty()) { - AtomicBoolean semicolonPresent = new AtomicBoolean(false); - + Tree lastConstant = jcEnums.get(jcEnums.size() - 1); List> enumValues = convertAll(jcEnums, commaDelim, t -> { - // this semicolon is required when there are non-value members, but can still - // be present when there are not - semicolonPresent.set(positionOfNext(";", '}') > 0); - return semicolonPresent.get() ? sourceBefore(";", '}') : EMPTY; + if (t != lastConstant) { + return whitespace(); + } + int savedCursor = cursor; + Space suffix = whitespace(); + if (source.charAt(cursor) == ',' || source.charAt(cursor) == ';') { + return suffix; + } + // Whitespace should be assigned to prefix of next statement or `J.Block#end` + cursor = savedCursor; + return EMPTY; + }, t -> { + if (t == lastConstant && skip(",") != null) { + int savedCursor = cursor; + Space suffix = whitespace(); + if (source.charAt(cursor) == ';') { + return Markers.build(singletonList(new TrailingComma(randomId(), suffix))); + } + // Whitespace should be assigned to prefix of next statement or `J.Block#end` + cursor = savedCursor; + return Markers.build(singletonList(new TrailingComma(randomId(), EMPTY))); + } + return Markers.EMPTY; }); enumSet = padRight( @@ -435,7 +454,7 @@ public J visitClass(ClassTree node, Space fmt) { EMPTY, Markers.EMPTY, enumValues, - semicolonPresent.get() + skip(";") != null ), EMPTY ); @@ -1010,7 +1029,14 @@ public J visitNewArray(NewArrayTree node, Space fmt) { JContainer initializer = node.getInitializers() == null ? null : JContainer.build(sourceBefore("{"), node.getInitializers().isEmpty() ? singletonList(padRight(new J.Empty(randomId(), sourceBefore("}"), Markers.EMPTY), EMPTY)) : - convertAll(node.getInitializers(), commaDelim, t -> sourceBefore("}")), Markers.EMPTY); + convertAll(node.getInitializers(), commaDelim, t -> whitespace(), t -> { + if (t == node.getInitializers().get(node.getInitializers().size() - 1) && source.charAt(cursor) == ',') { + cursor++; + return Markers.build(singletonList(new TrailingComma(randomId(), whitespace()))); + } + return Markers.EMPTY; + }), Markers.EMPTY); + skip("}"); return new J.NewArray(randomId(), fmt, Markers.EMPTY, typeExpr, dimensions, initializer, typeMapping.type(node)); @@ -1622,7 +1648,6 @@ public J visitWildcard(WildcardTree node, Space fmt) { * Conversion utilities * -------------- */ - private @Nullable J2 convert(@Nullable Tree t) { if (t == null) { return null; @@ -1667,20 +1692,24 @@ private static int getActualStartPosition(JCTree t) { } private @Nullable JRightPadded convert(@Nullable Tree t, Function suffix) { + return convert(t, suffix, j -> Markers.EMPTY); + } + + private @Nullable JRightPadded convert(@Nullable Tree t, Function suffix, Function markers) { if (t == null) { return null; } J2 j = convert(t); @SuppressWarnings("ConstantConditions") JRightPadded rightPadded = j == null ? null : - new JRightPadded<>(j, suffix.apply(t), Markers.EMPTY); + new JRightPadded<>(j, suffix.apply(t), markers.apply(t)); int idx = findFirstNonWhitespaceChar(rightPadded.getAfter().getWhitespace()); if (idx >= 0) { rightPadded = (JRightPadded) JRightPadded.build(getErroneous(List.of(rightPadded))); } // Cursor hasn't been updated but points at the end of erroneous node already // This means that error node start position == end position - // Therefore ensure that cursor has moved to the end of erroneous node bu adding its length to the cursor - // Example `/pet` results in 2 erroeneous nodes: `/` and `pet`. The `/` node would have start and end position the + // Therefore ensure that cursor has moved to the end of erroneous node but adding its length to the cursor + // Example `/pet` results in 2 erroneous nodes: `/` and `pet`. The `/` node would have start and end position the // same from the JC compiler. if (endPos(t) == cursor && rightPadded.getElement() instanceof J.Erroneous) { cursor += ((J.Erroneous) rightPadded.getElement()).getText().length(); @@ -1691,8 +1720,8 @@ private static int getActualStartPosition(JCTree t) { } private J.Erroneous getErroneous(List> converted) { - PrintOutputCapture p = new PrintOutputCapture<>(0); - new JavaPrinter<>().visitContainer(JContainer.build(EMPTY, converted, Markers.EMPTY), JContainer.Location.METHOD_INVOCATION_ARGUMENTS, p); + PrintOutputCapture p = new PrintOutputCapture<>(0); + new JavaPrinter().visitContainer(JContainer.build(EMPTY, converted, Markers.EMPTY), JContainer.Location.METHOD_INVOCATION_ARGUMENTS, p); return new J.Erroneous( org.openrewrite.Tree.randomId(), EMPTY, @@ -1725,13 +1754,20 @@ private List convertAll(List trees) { private List> convertAll(List trees, Function innerSuffix, Function suffix) { + return convertAll(trees, innerSuffix, suffix, t -> Markers.EMPTY); + } + + private List> convertAll(List trees, + Function innerSuffix, + Function suffix, + Function markers) { int size = trees.size(); if (size == 0) { return emptyList(); } List> converted = new ArrayList<>(size); for (int i = 0; i < size; i++) { - converted.add(convert(trees.get(i), i == size - 1 ? suffix : innerSuffix)); + converted.add(convert(trees.get(i), i == size - 1 ? suffix : innerSuffix, markers)); } return converted; } @@ -1756,17 +1792,17 @@ private List> convertAll(List tr private Space statementDelim(@Nullable Tree t) { if (t instanceof JCAssert || - t instanceof JCAssign || - t instanceof JCAssignOp || - t instanceof JCBreak || - t instanceof JCContinue || - t instanceof JCDoWhileLoop || - t instanceof JCImport || - t instanceof JCMethodInvocation || - t instanceof JCNewClass || - t instanceof JCReturn || - t instanceof JCThrow || - t instanceof JCUnary) { + t instanceof JCAssign || + t instanceof JCAssignOp || + t instanceof JCBreak || + t instanceof JCContinue || + t instanceof JCDoWhileLoop || + t instanceof JCImport || + t instanceof JCMethodInvocation || + t instanceof JCNewClass || + t instanceof JCReturn || + t instanceof JCThrow || + t instanceof JCUnary) { return sourceBefore(";"); } @@ -1933,6 +1969,7 @@ private Space sourceBefore(String untilDelim, @Nullable Character stop) { cursor += untilDelim.length(); return EMPTY; } + Space space = format(source, cursor, delimIndex); cursor = delimIndex + untilDelim.length(); // advance past the delimiter return space; @@ -2011,15 +2048,16 @@ private Space whitespace() { return space; } - private String skip(@Nullable String token) { + private @Nullable String skip(@Nullable String token) { if (token == null) { //noinspection ConstantConditions return null; } if (source.startsWith(token, cursor)) { cursor += token.length(); + return token; } - return token; + return null; } // Only exists as a function to make it easier to debug unexpected cursor shifts @@ -2040,7 +2078,7 @@ private List listFlags(long flags) { try { // FIXME instanceof probably not right here... return field.get(null) instanceof Long && - field.getName().matches("[A-Z_]+"); + field.getName().matches("[A-Z_]+"); } catch (IllegalAccessException e) { throw new RuntimeException(e); } 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 9f56bfb0b3c..738a3fe7c23 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 @@ -39,6 +39,7 @@ import org.openrewrite.java.internal.JavaTypeCache; import org.openrewrite.java.marker.CompactConstructor; import org.openrewrite.java.marker.OmitParentheses; +import org.openrewrite.java.marker.TrailingComma; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markers; import org.openrewrite.style.NamedStyles; @@ -51,7 +52,6 @@ import java.nio.charset.Charset; import java.nio.file.Path; import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.regex.Matcher; @@ -334,7 +334,7 @@ public J visitBreak(BreakTree node, Space fmt) { J.Identifier label = node.getLabel() == null ? null : new J.Identifier(randomId(), sourceBefore(node.getLabel().toString()), Markers.EMPTY, - emptyList(), skip(node.getLabel().toString()), null, null); + emptyList(), node.getLabel().toString(), null, null); return new J.Break(randomId(), fmt, Markers.EMPTY, label); } @@ -476,13 +476,31 @@ public J visitClass(ClassTree node, Space fmt) { JRightPadded enumSet = null; if (!jcEnums.isEmpty()) { - AtomicBoolean semicolonPresent = new AtomicBoolean(false); - + Tree lastConstant = jcEnums.get(jcEnums.size() - 1); List> enumValues = convertAll(jcEnums, commaDelim, t -> { - // this semicolon is required when there are non-value members, but can still - // be present when there are not - semicolonPresent.set(positionOfNext(";", '}') > 0); - return semicolonPresent.get() ? sourceBefore(";", '}') : EMPTY; + if (t != lastConstant) { + return whitespace(); + } + int savedCursor = cursor; + Space suffix = whitespace(); + if (source.charAt(cursor) == ',' || source.charAt(cursor) == ';') { + return suffix; + } + // Whitespace should be assigned to prefix of next statement or `J.Block#end` + cursor = savedCursor; + return EMPTY; + }, t -> { + if (t == lastConstant && skip(",") != null) { + int savedCursor = cursor; + Space suffix = whitespace(); + if (source.charAt(cursor) == ';') { + return Markers.build(singletonList(new TrailingComma(randomId(), suffix))); + } + // Whitespace should be assigned to prefix of next statement or `J.Block#end` + cursor = savedCursor; + return Markers.build(singletonList(new TrailingComma(randomId(), EMPTY))); + } + return Markers.EMPTY; }); enumSet = padRight( @@ -491,7 +509,7 @@ public J visitClass(ClassTree node, Space fmt) { EMPTY, Markers.EMPTY, enumValues, - semicolonPresent.get() + skip(";") != null ), EMPTY ); @@ -1079,7 +1097,14 @@ public J visitNewArray(NewArrayTree node, Space fmt) { JContainer initializer = node.getInitializers() == null ? null : JContainer.build(sourceBefore("{"), node.getInitializers().isEmpty() ? singletonList(padRight(new J.Empty(randomId(), sourceBefore("}"), Markers.EMPTY), EMPTY)) : - convertAll(node.getInitializers(), commaDelim, t -> sourceBefore("}")), Markers.EMPTY); + convertAll(node.getInitializers(), commaDelim, t -> whitespace(), t -> { + if (t == node.getInitializers().get(node.getInitializers().size() - 1) && source.charAt(cursor) == ',') { + cursor++; + return Markers.build(singletonList(new TrailingComma(randomId(), whitespace()))); + } + return Markers.EMPTY; + }), Markers.EMPTY); + skip("}"); return new J.NewArray(randomId(), fmt, Markers.EMPTY, typeExpr, dimensions, initializer, typeMapping.type(node)); @@ -1722,14 +1747,6 @@ public J visitWildcard(WildcardTree node, Space fmt) { } } - private static int getActualStartPosition(JCTree t) { - // not sure if this is a bug in Lombok, but the variable's start position is after the `val` annotation - if (t instanceof JCVariableDecl && isLombokVal((JCVariableDecl) t)) { - return ((JCVariableDecl) t).mods.annotations.get(0).getStartPosition(); - } - return t.getStartPosition(); - } - private void reportJavaParsingException(Throwable ex) { // this SHOULD never happen, but is here simply as a diagnostic measure in the event of unexpected exceptions StringBuilder message = new StringBuilder("Failed to convert for the following cursor stack:"); @@ -1754,21 +1771,33 @@ private void reportJavaParsingException(Throwable ex) { ctx.getOnError().accept(new JavaParsingException(message.toString(), ex)); } + private static int getActualStartPosition(JCTree t) { + // not sure if this is a bug in Lombok, but the variable's start position is after the `val` annotation + if (t instanceof JCVariableDecl && isLombokVal((JCVariableDecl) t)) { + return ((JCVariableDecl) t).mods.annotations.get(0).getStartPosition(); + } + return t.getStartPosition(); + } + private @Nullable JRightPadded convert(@Nullable Tree t, Function suffix) { + return convert(t, suffix, j -> Markers.EMPTY); + } + + private @Nullable JRightPadded convert(@Nullable Tree t, Function suffix, Function markers) { if (t == null) { return null; } J2 j = convert(t); @SuppressWarnings("ConstantConditions") JRightPadded rightPadded = j == null ? null : - new JRightPadded<>(j, suffix.apply(t), Markers.EMPTY); + new JRightPadded<>(j, suffix.apply(t), markers.apply(t)); int idx = findFirstNonWhitespaceChar(rightPadded.getAfter().getWhitespace()); if (idx >= 0) { rightPadded = (JRightPadded) JRightPadded.build(getErroneous(List.of(rightPadded))); } // Cursor hasn't been updated but points at the end of erroneous node already // This means that error node start position == end position - // Therefore ensure that cursor has moved to the end of erroneous node bu adding its length to the cursor - // Example `/pet` results in 2 erroeneous nodes: `/` and `pet`. The `/` node would have start and end position the + // Therefore ensure that cursor has moved to the end of erroneous node but adding its length to the cursor + // Example `/pet` results in 2 erroneous nodes: `/` and `pet`. The `/` node would have start and end position the // same from the JC compiler. if (endPos(t) == cursor && rightPadded.getElement() instanceof J.Erroneous) { cursor += ((J.Erroneous) rightPadded.getElement()).getText().length(); @@ -1779,8 +1808,8 @@ private void reportJavaParsingException(Throwable ex) { } private J.Erroneous getErroneous(List> converted) { - PrintOutputCapture p = new PrintOutputCapture<>(0); - new JavaPrinter<>().visitContainer(JContainer.build(EMPTY, converted, Markers.EMPTY), JContainer.Location.METHOD_INVOCATION_ARGUMENTS, p); + PrintOutputCapture p = new PrintOutputCapture<>(0); + new JavaPrinter().visitContainer(JContainer.build(EMPTY, converted, Markers.EMPTY), JContainer.Location.METHOD_INVOCATION_ARGUMENTS, p); return new J.Erroneous( org.openrewrite.Tree.randomId(), EMPTY, @@ -1813,13 +1842,20 @@ private List convertAll(List trees) { private List> convertAll(List trees, Function innerSuffix, Function suffix) { + return convertAll(trees, innerSuffix, suffix, t -> Markers.EMPTY); + } + + private List> convertAll(List trees, + Function innerSuffix, + Function suffix, + Function markers) { int size = trees.size(); if (size == 0) { return emptyList(); } List> converted = new ArrayList<>(size); for (int i = 0; i < size; i++) { - converted.add(convert(trees.get(i), i == size - 1 ? suffix : innerSuffix)); + converted.add(convert(trees.get(i), i == size - 1 ? suffix : innerSuffix, markers)); } return converted; } @@ -2093,15 +2129,16 @@ private Space whitespace() { return space; } - private String skip(@Nullable String token) { + private @Nullable String skip(@Nullable String token) { if (token == null) { //noinspection ConstantConditions return null; } if (source.startsWith(token, cursor)) { cursor += token.length(); + return token; } - return token; + return null; } // Only exists as a function to make it easier to debug unexpected cursor shifts 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 c1c20fd3ff4..b49c1f1563a 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 @@ -39,6 +39,7 @@ import org.openrewrite.java.internal.JavaTypeCache; import org.openrewrite.java.marker.CompactConstructor; import org.openrewrite.java.marker.OmitParentheses; +import org.openrewrite.java.marker.TrailingComma; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markers; import org.openrewrite.style.NamedStyles; @@ -51,7 +52,6 @@ import java.nio.charset.Charset; import java.nio.file.Path; import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.regex.Matcher; @@ -334,7 +334,7 @@ public J visitBreak(BreakTree node, Space fmt) { J.Identifier label = node.getLabel() == null ? null : new J.Identifier(randomId(), sourceBefore(node.getLabel().toString()), Markers.EMPTY, - emptyList(), skip(node.getLabel().toString()), null, null); + emptyList(), node.getLabel().toString(), null, null); return new J.Break(randomId(), fmt, Markers.EMPTY, label); } @@ -476,13 +476,31 @@ public J visitClass(ClassTree node, Space fmt) { JRightPadded enumSet = null; if (!jcEnums.isEmpty()) { - AtomicBoolean semicolonPresent = new AtomicBoolean(false); - + Tree lastConstant = jcEnums.get(jcEnums.size() - 1); List> enumValues = convertAll(jcEnums, commaDelim, t -> { - // this semicolon is required when there are non-value members, but can still - // be present when there are not - semicolonPresent.set(positionOfNext(";", '}') > 0); - return semicolonPresent.get() ? sourceBefore(";", '}') : EMPTY; + if (t != lastConstant) { + return whitespace(); + } + int savedCursor = cursor; + Space suffix = whitespace(); + if (source.charAt(cursor) == ',' || source.charAt(cursor) == ';') { + return suffix; + } + // Whitespace should be assigned to prefix of next statement or `J.Block#end` + cursor = savedCursor; + return EMPTY; + }, t -> { + if (t == lastConstant && skip(",") != null) { + int savedCursor = cursor; + Space suffix = whitespace(); + if (source.charAt(cursor) == ';') { + return Markers.build(singletonList(new TrailingComma(randomId(), suffix))); + } + // Whitespace should be assigned to prefix of next statement or `J.Block#end` + cursor = savedCursor; + return Markers.build(singletonList(new TrailingComma(randomId(), EMPTY))); + } + return Markers.EMPTY; }); enumSet = padRight( @@ -491,7 +509,7 @@ public J visitClass(ClassTree node, Space fmt) { EMPTY, Markers.EMPTY, enumValues, - semicolonPresent.get() + skip(";") != null ), EMPTY ); @@ -1079,7 +1097,14 @@ public J visitNewArray(NewArrayTree node, Space fmt) { JContainer initializer = node.getInitializers() == null ? null : JContainer.build(sourceBefore("{"), node.getInitializers().isEmpty() ? singletonList(padRight(new J.Empty(randomId(), sourceBefore("}"), Markers.EMPTY), EMPTY)) : - convertAll(node.getInitializers(), commaDelim, t -> sourceBefore("}")), Markers.EMPTY); + convertAll(node.getInitializers(), commaDelim, t -> whitespace(), t -> { + if (t == node.getInitializers().get(node.getInitializers().size() - 1) && source.charAt(cursor) == ',') { + cursor++; + return Markers.build(singletonList(new TrailingComma(randomId(), whitespace()))); + } + return Markers.EMPTY; + }), Markers.EMPTY); + skip("}"); return new J.NewArray(randomId(), fmt, Markers.EMPTY, typeExpr, dimensions, initializer, typeMapping.type(node)); @@ -1722,14 +1747,6 @@ public J visitWildcard(WildcardTree node, Space fmt) { } } - private static int getActualStartPosition(JCTree t) { - // not sure if this is a bug in Lombok, but the variable's start position is after the `val` annotation - if (t instanceof JCVariableDecl && isLombokVal((JCVariableDecl) t)) { - return ((JCVariableDecl) t).mods.annotations.get(0).getStartPosition(); - } - return t.getStartPosition(); - } - private void reportJavaParsingException(Throwable ex) { // this SHOULD never happen, but is here simply as a diagnostic measure in the event of unexpected exceptions StringBuilder message = new StringBuilder("Failed to convert for the following cursor stack:"); @@ -1754,21 +1771,33 @@ private void reportJavaParsingException(Throwable ex) { ctx.getOnError().accept(new JavaParsingException(message.toString(), ex)); } + private static int getActualStartPosition(JCTree t) { + // not sure if this is a bug in Lombok, but the variable's start position is after the `val` annotation + if (t instanceof JCVariableDecl && isLombokVal((JCVariableDecl) t)) { + return ((JCVariableDecl) t).mods.annotations.get(0).getStartPosition(); + } + return t.getStartPosition(); + } + private @Nullable JRightPadded convert(@Nullable Tree t, Function suffix) { + return convert(t, suffix, j -> Markers.EMPTY); + } + + private @Nullable JRightPadded convert(@Nullable Tree t, Function suffix, Function markers) { if (t == null) { return null; } J2 j = convert(t); @SuppressWarnings("ConstantConditions") JRightPadded rightPadded = j == null ? null : - new JRightPadded<>(j, suffix.apply(t), Markers.EMPTY); + new JRightPadded<>(j, suffix.apply(t), markers.apply(t)); int idx = findFirstNonWhitespaceChar(rightPadded.getAfter().getWhitespace()); if (idx >= 0) { rightPadded = (JRightPadded) JRightPadded.build(getErroneous(List.of(rightPadded))); } // Cursor hasn't been updated but points at the end of erroneous node already // This means that error node start position == end position - // Therefore ensure that cursor has moved to the end of erroneous node bu adding its length to the cursor - // Example `/pet` results in 2 erroeneous nodes: `/` and `pet`. The `/` node would have start and end position the + // Therefore ensure that cursor has moved to the end of erroneous node but adding its length to the cursor + // Example `/pet` results in 2 erroneous nodes: `/` and `pet`. The `/` node would have start and end position the // same from the JC compiler. if (endPos(t) == cursor && rightPadded.getElement() instanceof J.Erroneous) { cursor += ((J.Erroneous) rightPadded.getElement()).getText().length(); @@ -1779,8 +1808,8 @@ private void reportJavaParsingException(Throwable ex) { } private J.Erroneous getErroneous(List> converted) { - PrintOutputCapture p = new PrintOutputCapture<>(0); - new JavaPrinter<>().visitContainer(JContainer.build(EMPTY, converted, Markers.EMPTY), JContainer.Location.METHOD_INVOCATION_ARGUMENTS, p); + PrintOutputCapture p = new PrintOutputCapture<>(0); + new JavaPrinter().visitContainer(JContainer.build(EMPTY, converted, Markers.EMPTY), JContainer.Location.METHOD_INVOCATION_ARGUMENTS, p); return new J.Erroneous( org.openrewrite.Tree.randomId(), EMPTY, @@ -1813,13 +1842,20 @@ private List convertAll(List trees) { private List> convertAll(List trees, Function innerSuffix, Function suffix) { + return convertAll(trees, innerSuffix, suffix, t -> Markers.EMPTY); + } + + private List> convertAll(List trees, + Function innerSuffix, + Function suffix, + Function markers) { int size = trees.size(); if (size == 0) { return emptyList(); } List> converted = new ArrayList<>(size); for (int i = 0; i < size; i++) { - converted.add(convert(trees.get(i), i == size - 1 ? suffix : innerSuffix)); + converted.add(convert(trees.get(i), i == size - 1 ? suffix : innerSuffix, markers)); } return converted; } @@ -1858,7 +1894,7 @@ private Space statementDelim(@Nullable Tree t) { case EXPRESSION_STATEMENT: ExpressionTree expTree = ((ExpressionStatementTree) t).getExpression(); if (expTree instanceof ErroneousTree) { - return Space.build(source.substring(((JCTree) expTree).getEndPosition(endPosTable),((JCTree) t).getEndPosition(endPosTable)), Collections.emptyList()); + return Space.build(source.substring(((JCTree) expTree).getEndPosition(endPosTable), ((JCTree) t).getEndPosition(endPosTable)), Collections.emptyList()); } else { return sourceBefore(";"); } @@ -2093,15 +2129,16 @@ private Space whitespace() { return space; } - private String skip(@Nullable String token) { + private @Nullable String skip(@Nullable String token) { if (token == null) { //noinspection ConstantConditions return null; } if (source.startsWith(token, cursor)) { cursor += token.length(); + return token; } - return token; + return null; } // Only exists as a function to make it easier to debug unexpected cursor shifts 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 8a502fd31b8..6b944cff74a 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 @@ -35,6 +35,7 @@ import org.openrewrite.java.internal.JavaTypeCache; import org.openrewrite.java.JavaPrinter; import org.openrewrite.java.marker.OmitParentheses; +import org.openrewrite.java.marker.TrailingComma; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markers; import org.openrewrite.style.NamedStyles; @@ -324,7 +325,7 @@ public J visitBreak(BreakTree node, Space fmt) { J.Identifier label = node.getLabel() == null ? null : new J.Identifier(randomId(), sourceBefore(node.getLabel().toString()), Markers.EMPTY, - emptyList(), skip(node.getLabel().toString()), null, null); + emptyList(), node.getLabel().toString(), null, null); return new J.Break(randomId(), fmt, Markers.EMPTY, label); } @@ -418,13 +419,31 @@ public J visitClass(ClassTree node, Space fmt) { JRightPadded enumSet = null; if (!jcEnums.isEmpty()) { - AtomicBoolean semicolonPresent = new AtomicBoolean(false); - + Tree lastConstant = jcEnums.get(jcEnums.size() - 1); List> enumValues = convertAll(jcEnums, commaDelim, t -> { - // this semicolon is required when there are non-value members, but can still - // be present when there are not - semicolonPresent.set(positionOfNext(";", '}') > 0); - return semicolonPresent.get() ? sourceBefore(";", '}') : EMPTY; + if (t != lastConstant) { + return whitespace(); + } + int savedCursor = cursor; + Space suffix = whitespace(); + if (source.charAt(cursor) == ',' || source.charAt(cursor) == ';') { + return suffix; + } + // Whitespace should be assigned to prefix of next statement or `J.Block#end` + cursor = savedCursor; + return EMPTY; + }, t -> { + if (t == lastConstant && skip(",") != null) { + int savedCursor = cursor; + Space suffix = whitespace(); + if (source.charAt(cursor) == ';') { + return Markers.build(singletonList(new TrailingComma(randomId(), suffix))); + } + // Whitespace should be assigned to prefix of next statement or `J.Block#end` + cursor = savedCursor; + return Markers.build(singletonList(new TrailingComma(randomId(), EMPTY))); + } + return Markers.EMPTY; }); enumSet = padRight( @@ -433,7 +452,7 @@ public J visitClass(ClassTree node, Space fmt) { EMPTY, Markers.EMPTY, enumValues, - semicolonPresent.get() + skip(";") != null ), EMPTY ); @@ -990,7 +1009,7 @@ public J visitNewArray(NewArrayTree node, Space fmt) { convert(dim, t -> sourceBefore("]")))); } - while(true) { + while (true) { int beginBracket = indexOfNextNonWhitespace(cursor, source); if (source.charAt(beginBracket) == '[') { int endBracket = indexOfNextNonWhitespace(beginBracket + 1, source); @@ -1008,7 +1027,14 @@ public J visitNewArray(NewArrayTree node, Space fmt) { JContainer initializer = node.getInitializers() == null ? null : JContainer.build(sourceBefore("{"), node.getInitializers().isEmpty() ? singletonList(padRight(new J.Empty(randomId(), sourceBefore("}"), Markers.EMPTY), EMPTY)) : - convertAll(node.getInitializers(), commaDelim, t -> sourceBefore("}")), Markers.EMPTY); + convertAll(node.getInitializers(), commaDelim, t -> whitespace(), t -> { + if (t == node.getInitializers().get(node.getInitializers().size() - 1) && source.charAt(cursor) == ',') { + cursor++; + return Markers.build(singletonList(new TrailingComma(randomId(), whitespace()))); + } + return Markers.EMPTY; + }), Markers.EMPTY); + skip("}"); return new J.NewArray(randomId(), fmt, Markers.EMPTY, typeExpr, dimensions, initializer, typeMapping.type(node)); @@ -1597,7 +1623,6 @@ public J visitWildcard(WildcardTree node, Space fmt) { * Conversion utilities * -------------- */ - private J2 convert(Tree t) { try { String prefix = source.substring(cursor, max(((JCTree) t).getStartPosition(), cursor)); @@ -1631,17 +1656,21 @@ private J2 convert(Tree t) { } private JRightPadded convert(Tree t, Function suffix) { + return convert(t, suffix, j -> Markers.EMPTY); + } + + private JRightPadded convert(Tree t, Function suffix, Function markers) { J2 j = convert(t); @SuppressWarnings("ConstantConditions") JRightPadded rightPadded = j == null ? null : - new JRightPadded<>(j, suffix.apply(t), Markers.EMPTY); + new JRightPadded<>(j, suffix.apply(t), markers.apply(t)); int idx = findFirstNonWhitespaceChar(rightPadded.getAfter().getWhitespace()); if (idx >= 0) { rightPadded = (JRightPadded) JRightPadded.build(getErroneous(Collections.singletonList(rightPadded))); } // Cursor hasn't been updated but points at the end of erroneous node already // This means that error node start position == end position - // Therefore ensure that cursor has moved to the end of erroneous node bu adding its length to the cursor - // Example `/pet` results in 2 erroeneous nodes: `/` and `pet`. The `/` node would have start and end position the + // Therefore ensure that cursor has moved to the end of erroneous node but adding its length to the cursor + // Example `/pet` results in 2 erroneous nodes: `/` and `pet`. The `/` node would have start and end position the // same from the JC compiler. if (endPos(t) == cursor && rightPadded.getElement() instanceof J.Erroneous) { cursor += ((J.Erroneous) rightPadded.getElement()).getText().length(); @@ -1652,8 +1681,8 @@ private JRightPadded convert(Tree t, Function su } private J.Erroneous getErroneous(List> converted) { - PrintOutputCapture p = new PrintOutputCapture<>(0); - new JavaPrinter<>().visitContainer(JContainer.build(EMPTY, converted, Markers.EMPTY), JContainer.Location.METHOD_INVOCATION_ARGUMENTS, p); + PrintOutputCapture p = new PrintOutputCapture<>(0); + new JavaPrinter().visitContainer(JContainer.build(EMPTY, converted, Markers.EMPTY), JContainer.Location.METHOD_INVOCATION_ARGUMENTS, p); return new J.Erroneous( org.openrewrite.Tree.randomId(), EMPTY, @@ -1700,13 +1729,20 @@ private List convertAll(List trees) { private List> convertAll(List trees, Function innerSuffix, Function suffix) { + return convertAll(trees, innerSuffix, suffix, t -> Markers.EMPTY); + } + + private List> convertAll(List trees, + Function innerSuffix, + Function suffix, + Function markers) { int size = trees.size(); if (size == 0) { return emptyList(); } List> converted = new ArrayList<>(size); for (int i = 0; i < size; i++) { - converted.add(convert(trees.get(i), i == size - 1 ? suffix : innerSuffix)); + converted.add(convert(trees.get(i), i == size - 1 ? suffix : innerSuffix, markers)); } return converted; } @@ -1936,15 +1972,16 @@ private Space whitespace() { return format(prefix); } - private String skip(@Nullable String token) { + private @Nullable String skip(@Nullable String token) { if (token == null) { //noinspection ConstantConditions return null; } if (source.startsWith(token, cursor)) { cursor += token.length(); + return token; } - return token; + return null; } // Only exists as a function to make it easier to debug unexpected cursor shifts diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/AnnotationTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/AnnotationTest.java index 9a89ce50b27..51e56507716 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/AnnotationTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/AnnotationTest.java @@ -82,6 +82,21 @@ void newArrayArgument() { ); } + @Test + void newArrayArgumentTrailingComma() { + rewriteRun( + java( + """ + import java.lang.annotation.Target; + import static java.lang.annotation.ElementType.*; + + @Target({ FIELD, PARAMETER , }) + public @interface Annotation {} + """ + ) + ); + } + @SuppressWarnings("FinalMethodInFinalClass") @Test void annotationsInManyLocations() { diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/EnumTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/EnumTest.java index 03787bb3816..90dc6a8d5c1 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/EnumTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/EnumTest.java @@ -16,7 +16,6 @@ package org.openrewrite.java.tree; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.openrewrite.Issue; import org.openrewrite.test.RewriteTest; @@ -35,7 +34,7 @@ void enumWithAnnotations() { public enum Test { @Deprecated(since = "now") One, - + @Deprecated(since = "now") Two; } @@ -56,7 +55,7 @@ void foo() {} }; A(int n) {} - + abstract void foo(); } """ @@ -74,7 +73,7 @@ public enum A { @Override void foo() {} }; - + abstract void foo(); } """ @@ -90,10 +89,10 @@ void enumConstructor() { public class Outer { public enum A { A1(1); - + A(int n) {} } - + private static final class ContextFailedToStart { private static Object[] combineArguments(String context, Throwable ex, Object[] arguments) { return new Object[arguments.length + 2]; @@ -126,7 +125,7 @@ void enumWithParameters() { public enum A { ONE(1), TWO(2); - + A(int n) {} } """ @@ -150,7 +149,6 @@ void enumUnnecessarilyTerminatedWithSemicolon() { } @Test - @Disabled("enum A { ONE~~(non-whitespace)~~>, <~~}") void enumValuesTerminatedWithComma() { rewriteRun( java("enum A { ONE, }") @@ -163,4 +161,21 @@ void enumWithEmptyParameters() { java("public enum A { ONE ( ), TWO ( ) }") ); } + + @Test + void enumWithParametersAndTrailingComma() { + rewriteRun( + java( + """ + public enum A { + ONE(1), + TWO(2), + ; + + A(int n) {} + } + """ + ) + ); + } } diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/NewArrayTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/NewArrayTest.java index 48c0972ce7e..e504de17cb2 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/NewArrayTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/NewArrayTest.java @@ -15,7 +15,6 @@ */ package org.openrewrite.java.tree; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.openrewrite.test.RewriteTest; @@ -50,7 +49,6 @@ class Test { } @Test - @Disabled("int[] n = new int[] { 0, 1, 2~~(non-whitespace)~~>, <~~};") void initializersWithTrailingComma() { rewriteRun( java( diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/JavaParserTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/JavaParserTest.java index 4314094918f..25a75622a02 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/JavaParserTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/JavaParserTest.java @@ -40,6 +40,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.test.TypeValidation.all; /** * @author Alex Boyko @@ -49,6 +50,7 @@ class JavaParserTest implements RewriteTest { @Test void incompleteAssignment() { rewriteRun( + spec -> spec.typeValidationOptions(all().erroneous(false)), java( """ @Deprecated(since=) @@ -238,6 +240,7 @@ public void test(int param ) { }) void erroneousExpressionStatements(@Language("java") String source) { rewriteRun( + spec -> spec.typeValidationOptions(all().erroneous(false)), java(source) ); } @@ -245,7 +248,8 @@ void erroneousExpressionStatements(@Language("java") String source) { @Test void erroneousVariableDeclarations() { rewriteRun( - spec -> spec.recipe(new FindCompileErrors()), + spec -> spec.recipe(new FindCompileErrors()) + .typeValidationOptions(all().erroneous(false)), java( """ package com.example.demo; diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/JavaVisitorTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/JavaVisitorTest.java index de0f9daa151..a906fb84249 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/JavaVisitorTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/JavaVisitorTest.java @@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.fail; import static org.openrewrite.java.Assertions.java; import static org.openrewrite.test.RewriteTest.toRecipe; +import static org.openrewrite.test.TypeValidation.all; class JavaVisitorTest implements RewriteTest { @@ -116,20 +117,21 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex }) ), java( - """ - class A { - public void method1() { - } - - @Deprecated - public String myMethod() { - return "hello"; - } - - public void method2() { - } - } - """) + """ + class A { + public void method1() { + } + + @Deprecated + public String myMethod() { + return "hello"; + } + + public void method2() { + } + } + """ + ) ); } @@ -149,7 +151,8 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex return method; } }) - ), + ) + .typeValidationOptions(all().erroneous(false)), java( """ class A { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/Assertions.java b/rewrite-java/src/main/java/org/openrewrite/java/Assertions.java index 27ae9b4ec08..1524c72188e 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/Assertions.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/Assertions.java @@ -38,6 +38,7 @@ import java.util.function.Consumer; import java.util.stream.Collectors; +import static java.util.stream.Collectors.joining; import static org.openrewrite.test.SourceSpecs.dir; @SuppressWarnings("unused") @@ -58,13 +59,28 @@ static void customizeExecutionContext(ExecutionContext ctx) { public static SourceFile validateTypes(SourceFile source, TypeValidation typeValidation) { if (source instanceof JavaSourceFile) { assertValidTypes(typeValidation, (JavaSourceFile) source); + if (typeValidation.erroneous()) { + List allErroneous = new JavaIsoVisitor>() { + @Override + public J.Erroneous visitErroneous(J.Erroneous erroneous, List list) { + J.Erroneous err = super.visitErroneous(erroneous, list); + list.add(err); + return err; + } + }.reduce(source, new ArrayList<>()); + if (!allErroneous.isEmpty()) { + throw new IllegalStateException("LST contains erroneous nodes\n" + allErroneous.stream() + .map(J.Erroneous::getText) + .collect(joining("\n\n"))); + } + } } return source; } private static void assertValidTypes(TypeValidation typeValidation, J sf) { if (typeValidation.identifiers() || typeValidation.methodInvocations() || typeValidation.methodDeclarations() || typeValidation.classDeclarations() || - typeValidation.constructorInvocations()) { + typeValidation.constructorInvocations()) { List missingTypeResults = FindMissingTypes.findMissingTypes(sf); missingTypeResults = missingTypeResults.stream() .filter(missingType -> { @@ -89,10 +105,10 @@ private static void assertValidTypes(TypeValidation typeValidation, J sf) { if (!missingTypeResults.isEmpty()) { String missingTypes = missingTypeResults.stream() .map(v -> v.getPath() + "\n" + v.getPrintedTree()) - .collect(Collectors.joining("\n\n")); + .collect(joining("\n\n")); throw new IllegalStateException( "LST contains missing or invalid type information\n" + missingTypes + - "\nhttps://docs.openrewrite.org/reference/faq#im-seeing-lst-contains-missing-or-invalid-type-information-in-my-recipe-unit-tests-how-to-resolve"); + "\nhttps://docs.openrewrite.org/reference/faq#im-seeing-lst-contains-missing-or-invalid-type-information-in-my-recipe-unit-tests-how-to-resolve"); } } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java index 71a1d56ccac..9743bb1abcd 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java @@ -21,6 +21,7 @@ import org.openrewrite.Tree; import org.openrewrite.java.marker.CompactConstructor; import org.openrewrite.java.marker.OmitParentheses; +import org.openrewrite.java.marker.TrailingComma; import org.openrewrite.java.tree.*; import org.openrewrite.java.tree.J.*; import org.openrewrite.marker.Marker; @@ -94,6 +95,16 @@ protected void visitRightPadded(@Nullable JRightPadded rightPadded, } } + @Override + public M visitMarker(Marker marker, PrintOutputCapture

p) { + if (marker instanceof TrailingComma) { + p.append(','); + visitSpace(((TrailingComma) marker).getSuffix(), Space.Location.TRAILING_COMMA_SUFFIX, p); + } + //noinspection unchecked + return (M) marker; + } + @Override public J visitModifier(Modifier mod, PrintOutputCapture

p) { visit(mod.getAnnotations(), p); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java index fc40698f5d9..a334eafa601 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java @@ -300,21 +300,21 @@ public String toString() { } public enum Location { - ANY, ANNOTATED_TYPE_PREFIX, + ANNOTATIONS, ANNOTATION_ARGUMENTS, ANNOTATION_ARGUMENT_SUFFIX, - ANNOTATIONS, ANNOTATION_PREFIX, + ANY, ARRAY_ACCESS_PREFIX, ARRAY_INDEX_SUFFIX, ARRAY_TYPE_PREFIX, - ASSERT_PREFIX, ASSERT_DETAIL, ASSERT_DETAIL_PREFIX, + ASSERT_PREFIX, ASSIGNMENT, - ASSIGNMENT_OPERATION_PREFIX, ASSIGNMENT_OPERATION_OPERATOR, + ASSIGNMENT_OPERATION_PREFIX, ASSIGNMENT_PREFIX, BINARY_OPERATOR, BINARY_PREFIX, @@ -323,9 +323,9 @@ public enum Location { BLOCK_STATEMENT_SUFFIX, BREAK_PREFIX, CASE, - CASE_PREFIX, CASE_BODY, CASE_EXPRESSION, + CASE_PREFIX, CASE_SUFFIX, CATCH_ALTERNATIVE_SUFFIX, CATCH_PREFIX, @@ -335,8 +335,8 @@ public enum Location { COMPILATION_UNIT_PREFIX, CONTINUE_PREFIX, CONTROL_PARENTHESES_PREFIX, - DIMENSION_PREFIX, DIMENSION, + DIMENSION_PREFIX, DIMENSION_SUFFIX, DO_WHILE_PREFIX, ELSE_PREFIX, @@ -364,9 +364,8 @@ public enum Location { IF_PREFIX, IF_THEN_SUFFIX, IMPLEMENTS, - IMPORT_ALIAS_PREFIX, - PERMITS, IMPLEMENTS_SUFFIX, + IMPORT_ALIAS_PREFIX, IMPORT_PREFIX, IMPORT_SUFFIX, INSTANCEOF_PREFIX, @@ -383,9 +382,9 @@ public enum Location { MEMBER_REFERENCE_CONTAINING, MEMBER_REFERENCE_NAME, MEMBER_REFERENCE_PREFIX, + METHOD_DECLARATION_DEFAULT_VALUE, METHOD_DECLARATION_PARAMETERS, METHOD_DECLARATION_PARAMETER_SUFFIX, - METHOD_DECLARATION_DEFAULT_VALUE, METHOD_DECLARATION_PREFIX, METHOD_INVOCATION_ARGUMENTS, METHOD_INVOCATION_ARGUMENT_SUFFIX, @@ -410,6 +409,7 @@ public enum Location { PARAMETERIZED_TYPE_PREFIX, PARENTHESES_PREFIX, PARENTHESES_SUFFIX, + PERMITS, PERMITS_SUFFIX, PRIMITIVE_PREFIX, RECORD_STATE_VECTOR, @@ -418,8 +418,8 @@ public enum Location { STATEMENT_PREFIX, STATIC_IMPORT, STATIC_INIT_SUFFIX, - SWITCH_PREFIX, SWITCH_EXPRESSION_PREFIX, + SWITCH_PREFIX, SYNCHRONIZED_PREFIX, TERNARY_FALSE, TERNARY_PREFIX, @@ -427,6 +427,7 @@ public enum Location { THROWS, THROWS_SUFFIX, THROW_PREFIX, + TRAILING_COMMA_SUFFIX, TRY_FINALLY, TRY_PREFIX, TRY_RESOURCE, diff --git a/rewrite-java/src/test/java/org/openrewrite/java/search/FindCompileErrorsTest.java b/rewrite-java/src/test/java/org/openrewrite/java/search/FindCompileErrorsTest.java index 168e88ee0ec..36fc57b9637 100644 --- a/rewrite-java/src/test/java/org/openrewrite/java/search/FindCompileErrorsTest.java +++ b/rewrite-java/src/test/java/org/openrewrite/java/search/FindCompileErrorsTest.java @@ -23,6 +23,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.test.TypeValidation.all; class FindCompileErrorsTest implements RewriteTest { @@ -39,7 +40,7 @@ void javaVisitorHandlesErroneousNodes() { rows -> assertThat(rows).singleElement().satisfies(row -> { assertThat(row.getSourceFile()).isEqualTo("A.java"); assertThat(row.getCode()).isEqualTo("\n owner"); - })), + })).typeValidationOptions(all().erroneous(false)), java( """ class A { diff --git a/rewrite-test/src/main/java/org/openrewrite/test/TypeValidation.java b/rewrite-test/src/main/java/org/openrewrite/test/TypeValidation.java index e4df172f017..9d78cd0a33a 100644 --- a/rewrite-test/src/main/java/org/openrewrite/test/TypeValidation.java +++ b/rewrite-test/src/main/java/org/openrewrite/test/TypeValidation.java @@ -96,6 +96,13 @@ public class TypeValidation { @Builder.Default private Function allowMissingType = o -> false; + + /** + * Controls whether the LST is validated not to contain any `J.Erroneous` elements. + */ + @Builder.Default + private boolean erroneous = true; + /** * Enable all invariant validation checks. */ @@ -107,7 +114,7 @@ public static TypeValidation all() { * Skip all invariant validation checks. */ public static TypeValidation none() { - return new TypeValidation(false, false, false, false, false, false, false, false, o -> false); + return new TypeValidation(false, false, false, false, false, false, false, false, o -> false, false); } static TypeValidation before(RecipeSpec testMethodSpec, RecipeSpec testClassSpec) {