diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/RemoveContentVisitor.java b/rewrite-xml/src/main/java/org/openrewrite/xml/RemoveContentVisitor.java index 372279fa2b..c88590eab6 100644 --- a/rewrite-xml/src/main/java/org/openrewrite/xml/RemoveContentVisitor.java +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/RemoveContentVisitor.java @@ -44,7 +44,13 @@ public Xml visitTag(Xml.Tag tag, P p) { int indexOf = contents.indexOf(content); contents.remove(indexOf); - if (removePrecedingComment && 0 < indexOf && contents.get(indexOf - 1) instanceof Xml.Comment) { + if (indexOf < contents.size() && content.getPrefix().contains("\n") && + !contents.get(indexOf).getPrefix().contains("\n")) { + contents.set(indexOf, (Content) contents.get(indexOf).withPrefix(content.getPrefix())); + } + + if (removePrecedingComment && 0 < indexOf && contents.get(indexOf - 1) instanceof Xml.Comment && + !isTrailingComment(contents, indexOf - 1)) { doAfterVisit(new RemoveContentVisitor<>(contents.get(indexOf - 1), true, removePrecedingComment)); } @@ -63,4 +69,11 @@ public Xml visitTag(Xml.Tag tag, P p) { return t; } + + /** + * A comment that shares a line with the sibling before it documents that sibling, not the one after it. + */ + private static boolean isTrailingComment(List contents, int index) { + return 0 < index && !contents.get(index).getPrefix().contains("\n"); + } } diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/format/RemoveTrailingWhitespaceVisitor.java b/rewrite-xml/src/main/java/org/openrewrite/xml/format/RemoveTrailingWhitespaceVisitor.java index 51160e67ff..5d2104ac32 100644 --- a/rewrite-xml/src/main/java/org/openrewrite/xml/format/RemoveTrailingWhitespaceVisitor.java +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/format/RemoveTrailingWhitespaceVisitor.java @@ -20,7 +20,11 @@ import org.openrewrite.xml.XmlIsoVisitor; import org.openrewrite.xml.tree.Xml; +import java.util.regex.Pattern; + public class RemoveTrailingWhitespaceVisitor

extends XmlIsoVisitor

{ + private static final Pattern TRAILING_WHITESPACE = Pattern.compile("[ \\t]+(?=[\\r\\n]|$)"); + @Nullable private final Tree stopAfter; @@ -34,10 +38,8 @@ public RemoveTrailingWhitespaceVisitor(@Nullable Tree stopAfter) { @Override public Xml.Document visitDocument(Xml.Document doc, P p) { - String eof = doc.getEof(); - eof = eof.chars().filter(c -> c == '\n' || c == '\r') - .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) - .toString(); + // `eof` holds everything after the root element, comments and processing instructions included + String eof = TRAILING_WHITESPACE.matcher(doc.getEof()).replaceAll(""); Xml.Document d = super.visitDocument(doc, p); return d.withEof(eof); diff --git a/rewrite-xml/src/test/java/org/openrewrite/xml/RemoveContentTest.java b/rewrite-xml/src/test/java/org/openrewrite/xml/RemoveContentTest.java index 0e23b2a80a..d049b2c07f 100755 --- a/rewrite-xml/src/test/java/org/openrewrite/xml/RemoveContentTest.java +++ b/rewrite-xml/src/test/java/org/openrewrite/xml/RemoveContentTest.java @@ -112,4 +112,84 @@ public Xml visitDocument(Xml.Document x, ExecutionContext ctx) { ) ); } + + @Test + void trailingCommentKeepsItsOwnLine() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new XmlVisitor<>() { + @Override + public Xml visitDocument(Xml.Document x, ExecutionContext ctx) { + doAfterVisit(new RemoveContentVisitor<>(requireNonNull(x.getRoot().getContent()).get(1), false, false)); + return super.visitDocument(x, ctx); + } + }).withMaxCycles(1)), + xml( + """ + + group + + + """, + """ + + group + + + """ + ) + ); + } + + @Test + void precedingCommentOnTheLineOfAnEarlierSiblingIsRetained() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new XmlVisitor<>() { + @Override + public Xml visitDocument(Xml.Document x, ExecutionContext ctx) { + doAfterVisit(new RemoveContentVisitor<>(requireNonNull(x.getRoot().getContent()).get(2), false, true)); + return super.visitDocument(x, ctx); + } + }).withMaxCycles(1)), + xml( + """ + + group + + + """, + """ + + group + + """ + ) + ); + } + + @Test + void precedingCommentOnItsOwnLineIsRemoved() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new XmlVisitor<>() { + @Override + public Xml visitDocument(Xml.Document x, ExecutionContext ctx) { + doAfterVisit(new RemoveContentVisitor<>(requireNonNull(x.getRoot().getContent()).get(2), false, true)); + return super.visitDocument(x, ctx); + } + }).withMaxCycles(1)), + xml( + """ + + group + + + + """, + """ + + group + + """ + ) + ); + } } diff --git a/rewrite-xml/src/test/java/org/openrewrite/xml/format/AutoFormatTest.java b/rewrite-xml/src/test/java/org/openrewrite/xml/format/AutoFormatTest.java index f4e940ed86..ebb669f36f 100644 --- a/rewrite-xml/src/test/java/org/openrewrite/xml/format/AutoFormatTest.java +++ b/rewrite-xml/src/test/java/org/openrewrite/xml/format/AutoFormatTest.java @@ -173,4 +173,53 @@ void misindentedTrailingCommentIsStillIndented() { ) ); } + + @Test + void commentAfterRootElementIsRetained() { + rewriteRun( + xml( + """ + + + + + + """ + ) + ); + } + + @Test + void commentTrailingRootElementIsRetained() { + rewriteRun( + xml( + """ + + + + + """ + ) + ); + } + + @Test + void trailingWhitespaceAfterRootElementIsRemoved() { + rewriteRun( + xml( + """ + + + + \s + """, + """ + + + + + """ + ) + ); + } }