diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/ChangePropertyValue.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/ChangePropertyValue.java index c3c1d6ca1f6..131250dad05 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/ChangePropertyValue.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/ChangePropertyValue.java @@ -22,6 +22,7 @@ import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.NameCaseConvention; import org.openrewrite.internal.StringUtils; +import org.openrewrite.yaml.trait.BlockScalar; import org.openrewrite.yaml.tree.Yaml; import java.util.Iterator; @@ -96,7 +97,7 @@ public Yaml.Mapping.Entry visitMappingEntry(Yaml.Mapping.Entry entry, ExecutionC Yaml.Mapping.Entry e = super.visitMappingEntry(entry, ctx); String prop = getProperty(getCursor()); if (keyMatcher.matchesGlob(prop) && matchesOldValue(e.getValue())) { - Yaml.Block updatedValue = updateValue(e.getValue()); + Yaml.Block updatedValue = updateValue(e.getValue(), getCursor()); if (updatedValue != null) { e = e.withValue(updatedValue); } @@ -107,19 +108,24 @@ public Yaml.Mapping.Entry visitMappingEntry(Yaml.Mapping.Entry entry, ExecutionC } // returns null if value should not change - private Yaml.@Nullable Block updateValue(Yaml.Block value) { + private Yaml.@Nullable Block updateValue(Yaml.Block value, Cursor parent) { if (value instanceof Yaml.Scalar) { Yaml.Scalar scalar = (Yaml.Scalar) value; - Yaml.Scalar newScalar = scalar.withValue(Boolean.TRUE.equals(regex) ? - scalar.getValue().replaceAll(Objects.requireNonNull(oldValue), newValue) : - newValue); - return scalar.getValue().equals(newScalar.getValue()) ? null : newScalar; + BlockScalar block = new BlockScalar.Matcher().get(scalar, parent).orElse(null); + String body = block != null ? block.getBody() : scalar.getValue(); + String updatedBody = Boolean.TRUE.equals(regex) ? + body.replaceAll(Objects.requireNonNull(oldValue), newValue) : + newValue; + if (body.equals(updatedBody)) { + return null; + } + return block != null ? block.withBody(updatedBody) : scalar.withValue(updatedBody); } if (value instanceof Yaml.Sequence) { Yaml.Sequence sequence = (Yaml.Sequence) value; return sequence.withEntries(ListUtils.map(sequence.getEntries(), entry -> { if (matchesOldValue(entry.getBlock())) { - Yaml.Block updatedValue = updateValue(entry.getBlock()); + Yaml.Block updatedValue = updateValue(entry.getBlock(), parent); if (updatedValue != null) { return entry.withBlock(updatedValue); } diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/ChangeValue.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/ChangeValue.java index 18aab31be58..68a38d9c396 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/ChangeValue.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/ChangeValue.java @@ -20,6 +20,7 @@ import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.marker.Markers; +import org.openrewrite.yaml.trait.BlockScalar; import org.openrewrite.yaml.tree.Yaml; import static org.openrewrite.Tree.randomId; @@ -66,7 +67,15 @@ public TreeVisitor getVisitor() { @Override public Yaml.Mapping.Entry visitMappingEntry(Yaml.Mapping.Entry entry, ExecutionContext ctx) { Yaml.Mapping.Entry e = super.visitMappingEntry(entry, ctx); - if (matcher.matches(getCursor()) && (!(e.getValue() instanceof Yaml.Scalar) || !((Yaml.Scalar) e.getValue()).getValue().equals(value))) { + if (!matcher.matches(getCursor())) { + return e; + } + BlockScalar block = new BlockScalar.Matcher().get(new Cursor(getCursor(), e.getValue())).orElse(null); + if (block != null) { + if (!block.getBody().equals(value)) { + e = e.withValue(block.withBody(value)); + } + } else if (!(e.getValue() instanceof Yaml.Scalar) || !((Yaml.Scalar) e.getValue()).getValue().equals(value)) { Yaml.Anchor anchor = (e.getValue() instanceof Yaml.Scalar) ? ((Yaml.Scalar) e.getValue()).getAnchor() : null; Yaml.Tag tag = (e.getValue() instanceof Yaml.Scalar) ? ((Yaml.Scalar) e.getValue()).getTag() : null; String prefix = e.getValue() instanceof Yaml.Sequence ? ((Yaml.Sequence) e.getValue()).getOpeningBracketPrefix() : e.getValue().getPrefix(); @@ -82,7 +91,14 @@ public Yaml.Mapping.Entry visitMappingEntry(Yaml.Mapping.Entry entry, ExecutionC public Yaml.Scalar visitScalar(Yaml.Scalar scalar, ExecutionContext ctx) { Yaml.Scalar s = super.visitScalar(scalar, ctx); if (matcher.matches(getCursor())) { - s = s.withValue(value); + BlockScalar block = new BlockScalar.Matcher().get(getCursor()).orElse(null); + if (block != null) { + if (!block.getBody().equals(value)) { + s = block.withBody(value); + } + } else if (!s.getValue().equals(value)) { + s = s.withValue(value); + } } return s; } diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/DeleteProperty.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/DeleteProperty.java index 58664fcaa76..3c21273af79 100755 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/DeleteProperty.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/DeleteProperty.java @@ -184,6 +184,11 @@ public Yaml.Mapping visitMapping(Yaml.Mapping mapping, ExecutionContext ctx) { entry = entry.withPrefix(firstDeletedPrefix); } else if (previousWasDeleted && !entries.isEmpty() && !containsNewline(entry.getPrefix())) { entry = entry.withPrefix("\n" + entry.getPrefix()); + } else if (previousWasDeleted && !entries.isEmpty() + && endsWithBlockScalar(entries.get(entries.size() - 1)) + && containsNewline(entry.getPrefix())) { + // Block-scalar predecessor already owns the boundary newline; strip the duplicate. + entry = entry.withPrefix(stripLeadingLineBreak(entry.getPrefix())); } entries.add(entry); previousWasDeleted = false; @@ -257,6 +262,32 @@ private static boolean containsOnlyWhitespace(@Nullable String str) { return true; } + private static String stripLeadingLineBreak(String s) { + if (s.startsWith("\r\n")) { + return s.substring(2); + } + if (s.startsWith("\n") || s.startsWith("\r")) { + return s.substring(1); + } + // Line break not at the start — strip the first one found. + int idx = -1; + int idxN = s.indexOf('\n'); + int idxR = s.indexOf('\r'); + if (idxN >= 0 && (idxR < 0 || idxN < idxR)) { + idx = idxN; + } else if (idxR >= 0) { + idx = idxR; + } + if (idx < 0) { + return s; + } + int after = idx + 1; + if (s.charAt(idx) == '\r' && after < s.length() && s.charAt(after) == '\n') { + after++; + } + return s.substring(0, idx) + s.substring(after); + } + private static boolean containsNewline(@Nullable String str) { return str != null && str.indexOf('\n') >= 0; } diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/BlockScalar.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/BlockScalar.java new file mode 100644 index 00000000000..12f60da1719 --- /dev/null +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/BlockScalar.java @@ -0,0 +1,123 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.yaml.trait; + +import lombok.Value; +import org.jspecify.annotations.Nullable; +import org.openrewrite.Cursor; +import org.openrewrite.internal.StringUtils; +import org.openrewrite.trait.SimpleTraitMatcher; +import org.openrewrite.trait.Trait; +import org.openrewrite.yaml.tree.Yaml; + +/** + * Body-aware access to a FOLDED / LITERAL {@link Yaml.Scalar}: the raw {@link Yaml.Scalar#value} + * carries the block envelope (chomp indicator, header newline, indented body, trailing whitespace + * bounding the next sibling), so rewriting it directly via the Lombok-generated {@code withValue} + * clobbers the envelope. + */ +@Value +public class BlockScalar implements Trait { + Cursor cursor; + + public String getBody() { + String value = getTree().getValue(); + int headerEnd = value.indexOf('\n'); + if (headerEnd < 0) { + return ""; + } + int bodyEnd = value.length(); + while (bodyEnd > headerEnd + 1 && Character.isWhitespace(value.charAt(bodyEnd - 1))) { + bodyEnd--; + } + if (bodyEnd <= headerEnd + 1) { + return ""; + } + String bodyRegion = value.substring(headerEnd + 1, bodyEnd); + int indent = 0; + while (indent < bodyRegion.length() && bodyRegion.charAt(indent) == ' ') { + indent++; + } + String indentStr = bodyRegion.substring(0, indent); + String[] lines = bodyRegion.split("\r\n|\r|\n", -1); + StringBuilder out = new StringBuilder(bodyRegion.length()); + for (int i = 0; i < lines.length; i++) { + String line = lines[i]; + if (indent > 0 && line.startsWith(indentStr)) { + line = line.substring(indent); + } + if (i > 0) { + out.append('\n'); + } + out.append(line); + } + return out.toString(); + } + + public Yaml.Scalar withBody(String newBody) { + return withBody(newBody, 2); + } + + public Yaml.Scalar withBody(String newBody, int defaultIndentSpaces) { + Yaml.Scalar scalar = getTree(); + String value = scalar.getValue(); + int headerEnd = value.indexOf('\n'); + String header = headerEnd < 0 ? value : value.substring(0, headerEnd + 1); + String newLine = (headerEnd > 0 && value.charAt(headerEnd - 1) == '\r') ? "\r\n" : "\n"; + int bodyEnd = value.length(); + while (bodyEnd > 0 && Character.isWhitespace(value.charAt(bodyEnd - 1))) { + bodyEnd--; + } + String indent; + if (headerEnd >= 0 && headerEnd + 1 < bodyEnd) { + int indentEnd = headerEnd + 1; + while (indentEnd < bodyEnd && value.charAt(indentEnd) == ' ') { + indentEnd++; + } + indent = value.substring(headerEnd + 1, indentEnd); + if (indent.isEmpty()) { + indent = StringUtils.repeat(" ", defaultIndentSpaces); + } + } else { + indent = StringUtils.repeat(" ", defaultIndentSpaces); + } + String trailing = value.substring(bodyEnd); + String[] lines = newBody.split("\r\n|\r|\n", -1); + StringBuilder body = new StringBuilder(); + for (int i = 0; i < lines.length; i++) { + if (i > 0) { + body.append(newLine); + } + if (!lines[i].isEmpty()) { + body.append(indent).append(lines[i]); + } + } + return scalar.withValue(header + body + trailing); + } + + public static class Matcher extends SimpleTraitMatcher { + @Override + protected @Nullable BlockScalar test(Cursor cursor) { + if (cursor.getValue() instanceof Yaml.Scalar) { + Yaml.Scalar.Style style = ((Yaml.Scalar) cursor.getValue()).getStyle(); + if (style == Yaml.Scalar.Style.FOLDED || style == Yaml.Scalar.Style.LITERAL) { + return new BlockScalar(cursor); + } + } + return null; + } + } +} diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/YamlReference.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/YamlReference.java index 18ced348a74..afcd36e7dd9 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/YamlReference.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/YamlReference.java @@ -40,7 +40,12 @@ public boolean supportsRename() { public Tree rename(Renamer renamer, Cursor cursor, ExecutionContext ctx) { Tree tree = cursor.getValue(); if (tree instanceof Yaml.Scalar) { - return ((Yaml.Scalar) tree).withValue(renamer.rename(this)); + Yaml.Scalar scalar = (Yaml.Scalar) tree; + String newName = renamer.rename(this); + return new BlockScalar.Matcher() + .get(cursor) + .map(b -> (Yaml.Scalar) b.withBody(newName)) + .orElseGet(() -> scalar.withValue(newName)); } throw new IllegalArgumentException("cursor.getValue() must be an Yaml.Scalar but is: " + tree.getClass()); } diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/YamlValue.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/YamlValue.java index 98fabf683d0..a4c07260c68 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/YamlValue.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/YamlValue.java @@ -52,8 +52,12 @@ public Yaml.Scalar getValueAsScalar() { } public YamlValue withValue(String newValue) { - Yaml.Scalar value = getValueAsScalar().withValue(newValue); - cursor = new Cursor(cursor.getParent(), getTree().withValue(value)); + Yaml.Scalar scalar = getValueAsScalar(); + Yaml.Scalar updated = new BlockScalar.Matcher() + .get(scalar, cursor) + .map(b -> b.withBody(newValue)) + .orElseGet(() -> scalar.withValue(newValue)); + cursor = new Cursor(cursor.getParent(), getTree().withValue(updated)); return this; } diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/tree/Yaml.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/tree/Yaml.java index ec40f247451..f0e7a2b800d 100755 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/tree/Yaml.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/tree/Yaml.java @@ -307,6 +307,13 @@ class Scalar implements Block, YamlKey { @Nullable Tag tag; + /** + * For FOLDED/LITERAL scalars this includes the chomp indicator, header newline, + * indented body, and trailing whitespace bounding the next sibling; the Lombok-generated + * {@code withValue} cannot safely rewrite a block scalar's body. Use the + * {@code org.openrewrite.yaml.trait.BlockScalar} trait to mutate the body without + * clobbering the block envelope. + */ String value; public enum Style { diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/ChangePropertyValueTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/ChangePropertyValueTest.java index 35bbad20208..cd5805b3899 100644 --- a/rewrite-yaml/src/test/java/org/openrewrite/yaml/ChangePropertyValueTest.java +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/ChangePropertyValueTest.java @@ -233,6 +233,207 @@ void supportYamlListValuesWithRegex() { ); } + @Test + void preservesFoldedClipBlockEnvelope() { + rewriteRun( + spec -> spec.recipe(new ChangePropertyValue("key", "replaced", null, null, null, null)), + yaml( + """ + key: > + line one + line two + after: tail + """, + """ + key: > + replaced + after: tail + """ + ) + ); + } + + @Test + void preservesFoldedStripBlockEnvelope() { + rewriteRun( + spec -> spec.recipe(new ChangePropertyValue("key", "replaced", null, null, null, null)), + yaml( + """ + key: >- + line one + line two + after: tail + """, + """ + key: >- + replaced + after: tail + """ + ) + ); + } + + @Test + void preservesFoldedKeepBlockEnvelope() { + rewriteRun( + spec -> spec.recipe(new ChangePropertyValue("key", "replaced", null, null, null, null)), + yaml( + """ + key: >+ + line one + line two + + after: tail + """, + """ + key: >+ + replaced + + after: tail + """ + ) + ); + } + + @Test + void preservesLiteralClipBlockEnvelope() { + rewriteRun( + spec -> spec.recipe(new ChangePropertyValue("key", "replaced", null, null, null, null)), + yaml( + """ + key: | + line one + line two + after: tail + """, + """ + key: | + replaced + after: tail + """ + ) + ); + } + + @Test + void preservesLiteralStripBlockEnvelope() { + rewriteRun( + spec -> spec.recipe(new ChangePropertyValue("key", "replaced", null, null, null, null)), + yaml( + """ + key: |- + line one + line two + after: tail + """, + """ + key: |- + replaced + after: tail + """ + ) + ); + } + + @Test + void preservesLiteralKeepBlockEnvelope() { + rewriteRun( + spec -> spec.recipe(new ChangePropertyValue("key", "replaced", null, null, null, null)), + yaml( + """ + key: |+ + line one + line two + + after: tail + """, + """ + key: |+ + replaced + + after: tail + """ + ) + ); + } + + @Test + void multiLineNewValueReindentsAcrossBlockBody() { + rewriteRun( + spec -> spec.recipe(new ChangePropertyValue("key", "first\nsecond", null, null, null, null)), + yaml( + """ + key: | + old line + after: tail + """, + """ + key: | + first + second + after: tail + """ + ) + ); + } + + @Test + void regexReplacementOnBlockScalarOperatesOnBodyOnly() { + rewriteRun( + spec -> spec.recipe(new ChangePropertyValue("key", "B", "A", true, null, null)), + yaml( + """ + key: |- + line A one + line A two + after: tail + """, + """ + key: |- + line B one + line B two + after: tail + """ + ) + ); + } + + @Test + void preservesCrlfLiteralBlockEnvelope() { + rewriteRun( + spec -> spec.recipe(new ChangePropertyValue("key", "replaced", null, null, null, null)), + yaml( + "key: |\r\n" + + " line one\r\n" + + " line two\r\n" + + "after: tail\r\n", + "key: |\r\n" + + " replaced\r\n" + + "after: tail\r\n" + ) + ); + } + + @Test + void multilineReplacementOnCrlfBlockScalarKeepsCrlf() { + // The new value introduces its own interior line break (a bare '\n' from the recipe + // argument). On a CRLF file that break must be emitted as CRLF, not glued in as a lone + // LF that would leave the block with mixed line endings. + rewriteRun( + spec -> spec.recipe(new ChangePropertyValue("key", "new one\nnew two", null, null, null, null)), + yaml( + "key: |\r\n" + + " line one\r\n" + + " line two\r\n" + + "after: tail\r\n", + "key: |\r\n" + + " new one\r\n" + + " new two\r\n" + + "after: tail\r\n" + ) + ); + } + @Test void validatesThatOldValueIsRequiredIfRegexEnabled() { assertTrue(new ChangePropertyValue("my.prop", "bar", null, true, null, null).validate().isInvalid()); diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/ChangeValueTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/ChangeValueTest.java index dd8321aad44..3456b86d854 100644 --- a/rewrite-yaml/src/test/java/org/openrewrite/yaml/ChangeValueTest.java +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/ChangeValueTest.java @@ -179,6 +179,84 @@ void changeSequenceKeyByWildcard() { ); } + @Test + void preservesFoldedStripBlockEnvelope() { + rewriteRun( + spec -> spec.recipe(new ChangeValue("$.key", "replaced", null)), + yaml( + """ + key: >- + line one + line two + after: tail + """, + """ + key: >- + replaced + after: tail + """ + ) + ); + } + + @Test + void preservesLiteralKeepBlockEnvelope() { + rewriteRun( + spec -> spec.recipe(new ChangeValue("$.key", "replaced", null)), + yaml( + """ + key: |+ + line one + line two + + after: tail + """, + """ + key: |+ + replaced + + after: tail + """ + ) + ); + } + + @Test + void preservesFoldedClipBlockEnvelope() { + rewriteRun( + spec -> spec.recipe(new ChangeValue("$.key", "replaced", null)), + yaml( + """ + key: > + line one + line two + after: tail + """, + """ + key: > + replaced + after: tail + """ + ) + ); + } + + @Test + void preservesCrlfLiteralBlockEnvelope() { + rewriteRun( + spec -> spec.recipe(new ChangeValue("$.key", "replaced", null)), + yaml( + "key: |\r\n" + + " line one\r\n" + + " line two\r\n" + + "after: tail\r\n", + "key: |\r\n" + + " replaced\r\n" + + "after: tail\r\n" + ) + ); + } + @Test void changeSequenceKeyByExactMatch() { rewriteRun( diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/DeletePropertyKeyTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/DeletePropertyKeyTest.java index aea7551bba2..646e578ff5c 100644 --- a/rewrite-yaml/src/test/java/org/openrewrite/yaml/DeletePropertyKeyTest.java +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/DeletePropertyKeyTest.java @@ -666,4 +666,90 @@ void deleteLastEntryPreservesInlineCommentOnPreviousEntry() { ) ); } + + @Test + void deleteEntryAfterFoldedBlockScalar() { + rewriteRun( + spec -> spec.recipe(new DeleteProperty("doomed", null, null, null)), + yaml( + """ + keep: >- + line one + line two + doomed: value + after: tail + """, + """ + keep: >- + line one + line two + after: tail + """ + ) + ); + } + + @Test + void deleteEntryAfterLiteralKeepBlockScalar() { + rewriteRun( + spec -> spec.recipe(new DeleteProperty("doomed", null, null, null)), + yaml( + """ + keep: |+ + line one + line two + + doomed: value + after: tail + """, + """ + keep: |+ + line one + line two + + after: tail + """ + ) + ); + } + + @Test + void deleteEntryBeforeBlockScalarIsUnchanged() { + rewriteRun( + spec -> spec.recipe(new DeleteProperty("doomed", null, null, null)), + yaml( + """ + doomed: value + keep: >- + line one + line two + after: tail + """, + """ + keep: >- + line one + line two + after: tail + """ + ) + ); + } + + @Test + void deleteEntryAfterCrlfBlockScalar() { + rewriteRun( + spec -> spec.recipe(new DeleteProperty("doomed", null, null, null)), + yaml( + "keep: >-\r\n" + + " line one\r\n" + + " line two\r\n" + + "doomed: value\r\n" + + "after: tail\r\n", + "keep: >-\r\n" + + " line one\r\n" + + " line two\r\n" + + "after: tail\r\n" + ) + ); + } } diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/trait/BlockScalarTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/trait/BlockScalarTest.java new file mode 100644 index 00000000000..196d9438b71 --- /dev/null +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/trait/BlockScalarTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.yaml.trait; + +import org.junit.jupiter.api.Test; +import org.openrewrite.Cursor; +import org.openrewrite.marker.Markers; +import org.openrewrite.yaml.tree.Yaml; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.Tree.randomId; + +class BlockScalarTest { + + private static BlockScalar of(String value) { + Yaml.Scalar scalar = new Yaml.Scalar(randomId(), "", Markers.EMPTY, Yaml.Scalar.Style.LITERAL, null, null, value); + return new BlockScalar.Matcher().get(new Cursor(null, scalar)).orElseThrow(); + } + + @Test + void matcherRejectsPlainScalar() { + Yaml.Scalar plain = new Yaml.Scalar(randomId(), "", Markers.EMPTY, Yaml.Scalar.Style.PLAIN, null, null, "hello"); + assertThat(new BlockScalar.Matcher().get(new Cursor(null, plain))).isEmpty(); + } + + @Test + void getBodyStripsCrFromLfBody() { + assertThat(of("\n line one\n line two\n").getBody()).isEqualTo("line one\nline two"); + } + + @Test + void getBodyStripsCrFromCrlfBody() { + assertThat(of("\r\n line one\r\n line two\r\n").getBody()).isEqualTo("line one\nline two"); + } + + @Test + void withBodyKeepsLfForLfScalar() { + assertThat(of("\n line one\n line two\n").withBody("new one\nnew two").getValue()) + .isEqualTo("\n new one\n new two\n"); + } + + @Test + void withBodyEmitsCrlfForCrlfScalar() { + assertThat(of("\r\n line one\r\n line two\r\n").withBody("new one\nnew two").getValue()) + .isEqualTo("\r\n new one\r\n new two\r\n"); + } +}