diff --git a/rewrite-core/src/main/java/org/openrewrite/Cursor.java b/rewrite-core/src/main/java/org/openrewrite/Cursor.java index 3be9d7c15a3..7ed54841f73 100644 --- a/rewrite-core/src/main/java/org/openrewrite/Cursor.java +++ b/rewrite-core/src/main/java/org/openrewrite/Cursor.java @@ -57,7 +57,7 @@ public Cursor getRoot() { /** * @return true if this cursor is the root of the tree, false otherwise */ - final boolean isRoot() { + final public boolean isRoot() { return ROOT_VALUE.equals(value); } diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/MergeYaml.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/MergeYaml.java index 5361955065f..3b1bf9c1355 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/MergeYaml.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/MergeYaml.java @@ -84,6 +84,7 @@ public String getDescription() { } final static String FOUND_MATCHING_ELEMENT = "FOUND_MATCHING_ELEMENT"; + final static String REMOVE_PREFIX = "REMOVE_PREFIX"; @Override public TreeVisitor, ExecutionContext> getVisitor() { @@ -96,7 +97,7 @@ public TreeVisitor, ExecutionContext> getVisitor() { .map(docs -> { // Any comments will have been put on the parent Document node, preserve by copying to the mapping Yaml.Document doc = docs.getDocuments().get(0); - if(doc.getBlock() instanceof Yaml.Mapping) { + if (doc.getBlock() instanceof Yaml.Mapping) { Yaml.Mapping m = (Yaml.Mapping) doc.getBlock(); return m.withEntries(ListUtils.mapFirst(m.getEntries(), entry -> entry.withPrefix(doc.getPrefix()))); } else if (doc.getBlock() instanceof Yaml.Sequence) { @@ -110,9 +111,11 @@ public TreeVisitor, ExecutionContext> getVisitor() { @Override public Yaml.Document visitDocument(Yaml.Document document, ExecutionContext ctx) { if ("$".equals(key)) { - return document.withBlock((Yaml.Block) new MergeYamlVisitor<>(document.getBlock(), yaml, - Boolean.TRUE.equals(acceptTheirs), objectIdentifyingProperty).visitNonNull(document.getBlock(), - ctx, getCursor())); + Yaml.Document d = document.withBlock((Yaml.Block) + new MergeYamlVisitor<>(document.getBlock(), yaml, Boolean.TRUE.equals(acceptTheirs), objectIdentifyingProperty) + .visitNonNull(document.getBlock(), ctx, getCursor()) + ); + return getCursor().getMessage(REMOVE_PREFIX, false) ? d.withEnd(d.getEnd().withPrefix("")) : d; } Yaml.Document d = super.visitDocument(document, ctx); if (d == document && !getCursor().getMessage(FOUND_MATCHING_ELEMENT, false)) { diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/MergeYamlVisitor.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/MergeYamlVisitor.java index 66a5f310506..e3d24a32898 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/MergeYamlVisitor.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/MergeYamlVisitor.java @@ -15,23 +15,47 @@ */ package org.openrewrite.yaml; -import lombok.AllArgsConstructor; import lombok.RequiredArgsConstructor; import org.intellij.lang.annotations.Language; import org.jspecify.annotations.Nullable; import org.openrewrite.Cursor; import org.openrewrite.internal.ListUtils; +import org.openrewrite.style.GeneralFormatStyle; import org.openrewrite.yaml.tree.Yaml; +import org.openrewrite.yaml.tree.Yaml.Document; +import org.openrewrite.yaml.tree.Yaml.Mapping; +import org.openrewrite.yaml.tree.Yaml.Mapping.Entry; +import org.openrewrite.yaml.tree.Yaml.Scalar; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.regex.Pattern; import java.util.stream.Collectors; -@AllArgsConstructor +import static java.lang.System.lineSeparator; +import static org.openrewrite.Cursor.ROOT_VALUE; +import static org.openrewrite.internal.ListUtils.*; +import static org.openrewrite.yaml.MergeYaml.REMOVE_PREFIX; +/** + * Visitor class to merge two yaml files. + * + * @implNote Loops recursively through the documents, for every part a new MergeYamlVisitor instance will be created. + * As inline comments are put on the prefix of the next element (which can be an item very much higher in the tree), + * the following solutions are chosen to merge the comments as well: + *
An input object that is passed to every visit method. + */ @RequiredArgsConstructor public class MergeYamlVisitor
extends YamlVisitor
{ - private final Yaml scope; + + private static final Pattern LINE_BREAK = Pattern.compile("\\R"); + + private final Yaml existing; private final Yaml incoming; private final boolean acceptTheirs; @@ -40,6 +64,24 @@ public class MergeYamlVisitor
extends YamlVisitor
{
private boolean shouldAutoFormat = true;
+ public MergeYamlVisitor(Yaml.Block block, Yaml incoming, boolean acceptTheirs, @Nullable String objectIdentifyingProperty, boolean shouldAutoFormat) {
+ this(block, incoming, acceptTheirs, objectIdentifyingProperty);
+ this.shouldAutoFormat = shouldAutoFormat;
+ }
+
+ @Nullable
+ private String linebreak = null;
+
+ private String linebreak() {
+ if (linebreak == null) {
+ linebreak = Optional.ofNullable(getCursor().firstEnclosing(Yaml.Documents.class))
+ .map(docs -> docs.getStyle(GeneralFormatStyle.class))
+ .map(format -> format.isUseCRLFNewLines() ? "\r\n" : "\n")
+ .orElse("\n");
+ }
+ return linebreak;
+ }
+
public MergeYamlVisitor(Yaml scope, @Language("yml") String yamlString, boolean acceptTheirs, @Nullable String objectIdentifyingProperty) {
this(scope,
new YamlParser().parse(yamlString)
@@ -47,8 +89,8 @@ public MergeYamlVisitor(Yaml scope, @Language("yml") String yamlString, boolean
.map(Yaml.Documents.class::cast)
.map(docs -> {
// Any comments will have been put on the parent Document node, preserve by copying to the mapping
- Yaml.Document doc = docs.getDocuments().get(0);
- if(doc.getBlock() instanceof Yaml.Mapping) {
+ Document doc = docs.getDocuments().get(0);
+ if (doc.getBlock() instanceof Yaml.Mapping) {
Yaml.Mapping m = (Yaml.Mapping) doc.getBlock();
return m.withEntries(ListUtils.mapFirst(m.getEntries(), entry -> entry.withPrefix(doc.getPrefix())));
} else if (doc.getBlock() instanceof Yaml.Sequence) {
@@ -64,7 +106,7 @@ public MergeYamlVisitor(Yaml scope, @Language("yml") String yamlString, boolean
@Override
public Yaml visitScalar(Yaml.Scalar existingScalar, P p) {
- if (scope.isScope(existingScalar) && incoming instanceof Yaml.Scalar) {
+ if (existing.isScope(existingScalar) && incoming instanceof Yaml.Scalar) {
return mergeScalar(existingScalar, (Yaml.Scalar) incoming);
}
return super.visitScalar(existingScalar, p);
@@ -72,15 +114,15 @@ public Yaml visitScalar(Yaml.Scalar existingScalar, P p) {
@Override
public Yaml visitSequence(Yaml.Sequence existingSeq, P p) {
- if (scope.isScope(existingSeq)) {
+ if (existing.isScope(existingSeq)) {
if (incoming instanceof Yaml.Mapping) {
// Distribute the incoming mapping to each entry in the sequence
- return existingSeq.withEntries(ListUtils.map(existingSeq.getEntries(), (i, existingSeqEntry) -> {
- Yaml.Block b = (Yaml.Block) new MergeYamlVisitor<>(existingSeqEntry.getBlock(), incoming,
- acceptTheirs, objectIdentifyingProperty, shouldAutoFormat)
- .visitNonNull(existingSeqEntry.getBlock(), p, new Cursor(getCursor(), existingSeqEntry));
- return existingSeqEntry.withBlock(b);
- }));
+ return existingSeq.withEntries(map(existingSeq.getEntries(), (i, existingSeqEntry) ->
+ existingSeqEntry.withBlock((Yaml.Block)
+ new MergeYamlVisitor<>(existingSeqEntry.getBlock(), incoming, acceptTheirs, objectIdentifyingProperty, shouldAutoFormat)
+ .visitNonNull(existingSeqEntry.getBlock(), p, new Cursor(getCursor(), existingSeqEntry))
+ )
+ ));
} else if (incoming instanceof Yaml.Sequence) {
return mergeSequence(existingSeq, (Yaml.Sequence) incoming, p, getCursor());
}
@@ -90,14 +132,21 @@ public Yaml visitSequence(Yaml.Sequence existingSeq, P p) {
@Override
public Yaml visitMapping(Yaml.Mapping existingMapping, P p) {
- if (scope.isScope(existingMapping) && incoming instanceof Yaml.Mapping) {
- return mergeMapping(existingMapping, (Yaml.Mapping) incoming, p, getCursor());
+ if (existing.isScope(existingMapping) && incoming instanceof Yaml.Mapping) {
+ Yaml.Mapping mapping = mergeMapping(existingMapping, (Yaml.Mapping) incoming, p, getCursor());
+
+ if (getCursor().getMessage(REMOVE_PREFIX, false)) {
+ List