diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/AddToTagVisitor.java b/rewrite-xml/src/main/java/org/openrewrite/xml/AddToTagVisitor.java index fd3c7e333a..8fc51b09b8 100755 --- a/rewrite-xml/src/main/java/org/openrewrite/xml/AddToTagVisitor.java +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/AddToTagVisitor.java @@ -33,19 +33,37 @@ public class AddToTagVisitor

extends XmlVisitor

{ @Nullable private final Comparator tagComparator; + private final boolean allowDuplicates; + public AddToTagVisitor(Xml.Tag scope, Xml.Tag tagToAdd) { - this(scope, tagToAdd, null); + this(scope, tagToAdd, null, false); + } + + public AddToTagVisitor(Xml.Tag scope, Xml.Tag tagToAdd, boolean allowDuplicates) { + this(scope, tagToAdd, null, allowDuplicates); } public AddToTagVisitor(Xml.Tag scope, Xml.Tag tagToAdd, @Nullable Comparator tagComparator) { + this(scope, tagToAdd, tagComparator, false); + } + + public AddToTagVisitor(Xml.Tag scope, Xml.Tag tagToAdd, @Nullable Comparator tagComparator, boolean allowDuplicates) { this.scope = scope; this.tagToAdd = tagToAdd; this.tagComparator = tagComparator; + this.allowDuplicates = allowDuplicates; } @Override public Xml visitTag(Xml.Tag t, P p) { if (scope.isScope(t)) { + if (!allowDuplicates && t.getContent() != null) { + for (Content existing : t.getContent()) { + if (existing instanceof Xml.Tag && SemanticallyEqual.areEqual(existing, tagToAdd)) { + return super.visitTag(t, p); + } + } + } assert getCursor().getParent() != null; if (t.getClosing() == null) { t = t.withClosing(autoFormat(new Xml.Tag.Closing(Tree.randomId(), "\n", diff --git a/rewrite-xml/src/test/java/org/openrewrite/xml/AddToTagTest.java b/rewrite-xml/src/test/java/org/openrewrite/xml/AddToTagTest.java index bd0fba786e..b3fa960b0c 100755 --- a/rewrite-xml/src/test/java/org/openrewrite/xml/AddToTagTest.java +++ b/rewrite-xml/src/test/java/org/openrewrite/xml/AddToTagTest.java @@ -143,6 +143,102 @@ public Xml visitDocument(Xml.Document x, ExecutionContext ctx) { ); } + @Test + void doesNotAddSemanticallyEqualDuplicate() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new XmlVisitor<>() { + @Override + public Xml visitDocument(Xml.Document x, ExecutionContext ctx) { + doAfterVisit(new AddToTagVisitor<>(x.getRoot(), Xml.Tag.build(""))); + return super.visitDocument(x, ctx); + } + })), + xml( + """ + + + + """ + ) + ); + } + + @Test + void doesNotAddDuplicateIgnoringAttributeOrder() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new XmlVisitor<>() { + @Override + public Xml visitDocument(Xml.Document x, ExecutionContext ctx) { + doAfterVisit(new AddToTagVisitor<>(x.getRoot(), + Xml.Tag.build(""))); + return super.visitDocument(x, ctx); + } + })), + xml( + """ + + + + """ + ) + ); + } + + @Test + void addsSemanticallyEqualDuplicateWhenAllowDuplicatesTrue() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new XmlVisitor<>() { + @Override + public Xml visitDocument(Xml.Document x, ExecutionContext ctx) { + if (x.getRoot().getChildren().size() == 1) { + doAfterVisit(new AddToTagVisitor<>(x.getRoot(), + Xml.Tag.build(""), true)); + } + return super.visitDocument(x, ctx); + } + })), + xml( + """ + + + + """, + """ + + + + + """ + ) + ); + } + + @Test + void addsWhenChildrenShareNameButDifferentAttributes() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new XmlVisitor<>() { + @Override + public Xml visitDocument(Xml.Document x, ExecutionContext ctx) { + doAfterVisit(new AddToTagVisitor<>(x.getRoot(), Xml.Tag.build(""))); + return super.visitDocument(x, ctx); + } + })), + xml( + """ + + + + """, + """ + + + + + """ + ) + ); + } + @Issue("https://github.com/openrewrite/rewrite/issues/1392") @Test void preserveNonTagContent() {