From c04446e408a157a798b2b0d91a18a2f15e3455c5 Mon Sep 17 00:00:00 2001 From: Doug Bunting <6431421+dougbu@users.noreply.github.com> Date: Sat, 6 Jan 2024 21:19:28 -0800 Subject: [PATCH] Handle blank lines in `Yaml.Text()` - see https://github.com/aaubry/YamlDotNet/issues/886 - transform blank lines to `string.Empty` - remove all trailing whitespace from lines - if trailing whitespace is significant in a future test, YAML files can be added - `throw new ArgumentException(...)` if a line is insufficiently indented - should be easier to fix test than with previous `ArgumentOutOfRangeException` for this case - add tests --- .../Serialization/SerializationTests.cs | 28 ++--- YamlDotNet.Test/Yaml.cs | 15 ++- YamlDotNet.Test/YamlTests.cs | 116 ++++++++++++++++++ 3 files changed, 141 insertions(+), 18 deletions(-) create mode 100644 YamlDotNet.Test/YamlTests.cs diff --git a/YamlDotNet.Test/Serialization/SerializationTests.cs b/YamlDotNet.Test/Serialization/SerializationTests.cs index 28a502f9..0e6d0885 100644 --- a/YamlDotNet.Test/Serialization/SerializationTests.cs +++ b/YamlDotNet.Test/Serialization/SerializationTests.cs @@ -252,7 +252,7 @@ public void DeserializeWithGapsBetweenKeys() { var yamlReader = new StringReader(@"Text: > Some Text. - + Value: foo"); var result = Deserializer.Deserialize(yamlReader); @@ -1202,10 +1202,10 @@ public void BackreferencesAreMergedWithMappings() public void MergingDoesNotProduceDuplicateAnchors() { var parser = new MergingParser(Yaml.ParserForText(@" - anchor: &default + anchor: &default key1: &myValue value1 key2: value2 - alias: + alias: <<: *default key2: Overriding key2 key3: value3 @@ -1233,7 +1233,7 @@ public void ExampleFromSpecificationIsHandledCorrectly() - &LEFT { x: 0, y: 2 } - &BIG { r: 10 } - &SMALL { r: 1 } - + # All the following maps are equal: results: - # Explicit keys @@ -1241,16 +1241,16 @@ public void ExampleFromSpecificationIsHandledCorrectly() y: 2 r: 10 label: center/big - + - # Merge one map << : *CENTER r: 10 label: center/big - + - # Merge multiple maps << : [ *CENTER, *BIG ] label: center/big - + - # Override << : [ *BIG, *LEFT, *SMALL ] x: 1 @@ -1286,10 +1286,10 @@ public void MergeNestedReferenceCorrectly() derived1: <<: *level1 key: D1 - derived2: + derived2: <<: *level2 key: D2 - derived3: + derived3: <<: [ *level1, *level2 ] key: D3 ")); @@ -2168,7 +2168,7 @@ public void ShouldIndentSequences() [Fact] public void ExampleFromSpecificationIsHandledCorrectlyWithLateDefine() { - var parser = new MergingParser(Yaml.ParserForText(@" + var parser = new MergingParser(Yaml.ParserForText(@" # All the following maps are equal: results: - # Explicit keys @@ -2176,21 +2176,21 @@ public void ExampleFromSpecificationIsHandledCorrectlyWithLateDefine() y: 2 r: 10 label: center/big - + - # Merge one map << : *CENTER r: 10 label: center/big - + - # Merge multiple maps << : [ *CENTER, *BIG ] label: center/big - + - # Override << : [ *BIG, *LEFT, *SMALL ] x: 1 label: center/big - + obj: - &CENTER { x: 1, y: 2 } - &LEFT { x: 0, y: 2 } diff --git a/YamlDotNet.Test/Yaml.cs b/YamlDotNet.Test/Yaml.cs index 63b99c05..1f9e06f8 100644 --- a/YamlDotNet.Test/Yaml.cs +++ b/YamlDotNet.Test/Yaml.cs @@ -90,11 +90,11 @@ public static string Text(string yamlText) { var lines = yamlText .Split('\n') - .Select(l => l.TrimEnd('\r', '\n')) - .SkipWhile(l => l.Trim(' ', '\t').Length == 0) + .Select(l => l.TrimEnd()) + .SkipWhile(l => l.Length == 0) .ToList(); - while (lines.Count > 0 && lines[lines.Count - 1].Trim(' ', '\t').Length == 0) + while (lines.Count > 0 && lines[lines.Count - 1].Length == 0) { lines.RemoveAt(lines.Count - 1); } @@ -107,8 +107,15 @@ public static string Text(string yamlText) throw new ArgumentException("Invalid indentation"); } + var indentation = indent.Groups[1].Length; lines = lines - .Select(l => l.Substring(indent.Groups[1].Length)) + .Select((l, num) => l.Length == 0 ? + // Blank lines don't need to be indented. + string.Empty : + l.TakeWhile(c => c == ' ' || c == '\t').Count() < indentation ? + // However, other lines must be indented at least as much as the first line. + throw new ArgumentException($"Incorrectly indented line '{l}', #{num}.", nameof(yamlText)) : + l.Substring(indentation)) .ToList(); } diff --git a/YamlDotNet.Test/YamlTests.cs b/YamlDotNet.Test/YamlTests.cs new file mode 100644 index 00000000..14eebb4a --- /dev/null +++ b/YamlDotNet.Test/YamlTests.cs @@ -0,0 +1,116 @@ +// This file is part of YamlDotNet - A .NET library for YAML. +// Copyright (c) Antoine Aubry and contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using FluentAssertions; +using Xunit; + +namespace YamlDotNet.Test +{ + public class YamlTests + { + private const string SingleLine = "object:"; + private const string LeadingBlankLines = @" + + +object:"; + private const string LeadingBlankLinesWithWhitespace = @" + + +object:"; + private const string TrailingBlankLines = @"object: + + +"; + private const string TrailingBlankLinesWithWhitespace = @"object: + + +"; + + private const string Lines = @"this: +that: +theOtherThing:"; + private const string IndentedLines = @" this: + that: + theOtherThing:"; + + private const string NestedLines = @"Map1: + Map2: + - entry 1 + - entry 2 + - entry 3"; + private const string IndentedNestedLines = @" Map1: + Map2: + - entry 1 + - entry 2 + - entry 3"; + + private const string SomeBlankLines = @"this: + +that: + + +theOtherThing:"; + private const string SomeBlankLinesWithWhitespace = @"this: + +that: + + +theOtherThing:"; + + [Theory] + [InlineData(SingleLine, SingleLine)] + [InlineData(LeadingBlankLines, SingleLine)] + [InlineData(LeadingBlankLinesWithWhitespace, SingleLine)] + [InlineData(TrailingBlankLines, SingleLine)] + [InlineData(TrailingBlankLinesWithWhitespace, SingleLine)] + [InlineData(Lines, Lines)] + [InlineData(IndentedLines, Lines)] + [InlineData(NestedLines, NestedLines)] + [InlineData(IndentedNestedLines, NestedLines)] + [InlineData(SomeBlankLines, SomeBlankLines)] + [InlineData(SomeBlankLinesWithWhitespace, SomeBlankLines)] + public void TextProducesExpectedOutput(string text, string expectedText) + { + expectedText = expectedText.NormalizeNewLines(); + var result = Yaml.Text(text); + + result.NormalizeNewLines().Should().Be(expectedText); + } + + [Fact] + public void TextThrowsArgumentOutOfRangeExceptionForInsuffientIndentation() + { + const string BadlyIndentedLines = @" this: + that: + theOtherThing:"; + var expectedMessage = +#if NETFRAMEWORK + "Incorrectly indented line ' that:', #1." + Environment.NewLine + "Parameter name: yamlText"; +#else + "Incorrectly indented line ' that:', #1. (Parameter 'yamlText')"; +#endif + Action act = () => Yaml.Text(BadlyIndentedLines); + + act.ShouldThrowExactly().WithMessage(expectedMessage); + } + } +}