diff --git a/src/Util/YamlSourceManipulator.php b/src/Util/YamlSourceManipulator.php index 267111a51..882ba504d 100644 --- a/src/Util/YamlSourceManipulator.php +++ b/src/Util/YamlSourceManipulator.php @@ -449,8 +449,14 @@ private function changeValueInYaml($value) $endValuePosition = $this->findEndPositionOfValue($originalVal); - $newYamlValue = $this->convertToYaml($value); - if (!\is_array($originalVal) && \is_array($value)) { + $isMultilineValue = null !== $this->findPositionOfMultilineCharInLine($this->currentPosition); + + // In case of multiline, $value is converted as plain string like "Foo\nBar" + // We need to keep it "as is" + $newYamlValue = $isMultilineValue ? rtrim($value, "\n") : $this->convertToYaml($value); + if ((!\is_array($originalVal) && \is_array($value)) || + ($this->isMultilineString($originalVal) && $this->isMultilineString($value)) + ) { // we're converting from a scalar to a (multiline) array // this means we need to break onto the next line @@ -468,7 +474,13 @@ private function changeValueInYaml($value) ++$newPosition; } + if ($isMultilineValue) { + // strlen(" |") + $newPosition -= 2; + } + $newContents = substr($this->contents, 0, $this->currentPosition) + .($isMultilineValue ? ' |' : '') .$newYamlValue /* * If the next line is a comment, this means we probably had @@ -771,16 +783,26 @@ private function findEndPositionOfValue($value, $offset = null) } if (is_scalar($value) || null === $value) { + $offset = null === $offset ? $this->currentPosition : $offset; + if (\is_bool($value)) { // (?i) & (?-i) opens/closes case insensitive match $pattern = sprintf('(?i)%s(?-i)', $value ? 'true' : 'false'); } elseif (null === $value) { $pattern = '(~|NULL|null|\n)'; } else { - $pattern = sprintf('\'?"?%s\'?"?', preg_quote($value, '#')); - } + // Multiline value ends with \n. + // If we remove this character, the next property will ne merged with this value + $quotedValue = preg_quote(rtrim($value, "\n"), '#'); + $patternValue = $quotedValue; + + // Iterates until we find a new line char or we reach end of file + if (null !== $this->findPositionOfMultilineCharInLine($offset)) { + $patternValue = str_replace(["\r\n", "\n"], '\r?\n\s*', $quotedValue); + } - $offset = null === $offset ? $this->currentPosition : $offset; + $pattern = sprintf('\'?"?%s\'?"?', $patternValue); + } // a value like "foo:" can simply end a file // this means the value is null @@ -1164,7 +1186,31 @@ private function indentMultilineYamlArray(string $yaml): string // also need to be indented artificially by the same amount $yaml = str_replace("\n", "\n".$this->getCurrentIndentation(), $yaml); + if ($this->isMultilineString($yaml)) { + // Remove extra indentation in case of blank line in multiline string + $yaml = str_replace("\n".$this->getCurrentIndentation()."\n", "\n\n", $yaml); + } + // now indent this level return $this->getCurrentIndentation().$yaml; } + + private function findPositionOfMultilineCharInLine(int $position): ?int + { + $cursor = $position; + while (!$this->isCharLineBreak($currentChar = substr($this->contents, $cursor + 1, 1)) && !$this->isEOF($cursor)) { + if ('|' === $currentChar) { + return $cursor; + } + + ++$cursor; + } + + return null; + } + + private function isMultilineString($value): bool + { + return \is_string($value) && false !== strpos($value, "\n"); + } } diff --git a/tests/Util/YamlSourceManipulatorTest.php b/tests/Util/YamlSourceManipulatorTest.php index 5e01d6ce6..45c6065cf 100644 --- a/tests/Util/YamlSourceManipulatorTest.php +++ b/tests/Util/YamlSourceManipulatorTest.php @@ -58,13 +58,17 @@ private function getYamlDataTests() foreach ($finder as $file) { list($source, $changeCode, $expected) = explode('===', $file->getContents()); + // Multiline string ends with an \n + $source = rtrim($source, "\n"); + $expected = ltrim($expected, "\n"); + $data = Yaml::parse($source); eval($changeCode); yield $file->getFilename() => [ - 'source' => rtrim($source, "\n"), + 'source' => $source, 'newData' => $data, - 'expectedSource' => ltrim($expected, "\n"), + 'expectedSource' => $expected, ]; } diff --git a/tests/Util/yaml_fixtures/multiline_string.test b/tests/Util/yaml_fixtures/multiline_string.test new file mode 100644 index 000000000..b070fe0c2 --- /dev/null +++ b/tests/Util/yaml_fixtures/multiline_string.test @@ -0,0 +1,16 @@ +nelmio_api_doc: + documentation: + info: + description: | + Multiline string + Second line +=== +$data['nelmio_api_doc']['documentation']['info']['description'] = "Multiline string\nSecond line\nThird line"; +=== +nelmio_api_doc: + documentation: + info: + description: | + Multiline string + Second line + Third line \ No newline at end of file diff --git a/tests/Util/yaml_fixtures/multiline_string_before_property.test b/tests/Util/yaml_fixtures/multiline_string_before_property.test new file mode 100644 index 000000000..d68ec6208 --- /dev/null +++ b/tests/Util/yaml_fixtures/multiline_string_before_property.test @@ -0,0 +1,18 @@ +nelmio_api_doc: + documentation: + info: + description: | + Multiline string + Second line + foo: bar +=== +$data['nelmio_api_doc']['documentation']['info']['description'] = "Multiline string\nSecond line\nThird line\n"; +=== +nelmio_api_doc: + documentation: + info: + description: | + Multiline string + Second line + Third line + foo: bar \ No newline at end of file diff --git a/tests/Util/yaml_fixtures/multiline_string_remove_line.test b/tests/Util/yaml_fixtures/multiline_string_remove_line.test new file mode 100644 index 000000000..72956ea7b --- /dev/null +++ b/tests/Util/yaml_fixtures/multiline_string_remove_line.test @@ -0,0 +1,18 @@ +nelmio_api_doc: + documentation: + info: + description: | + Multiline string + Second line + Third line + foo: bar +=== +$data['nelmio_api_doc']['documentation']['info']['description'] = "Multiline string\nSecond line\n"; +=== +nelmio_api_doc: + documentation: + info: + description: | + Multiline string + Second line + foo: bar \ No newline at end of file diff --git a/tests/Util/yaml_fixtures/multiline_string_with_blank_line.test b/tests/Util/yaml_fixtures/multiline_string_with_blank_line.test new file mode 100644 index 000000000..c92296a31 --- /dev/null +++ b/tests/Util/yaml_fixtures/multiline_string_with_blank_line.test @@ -0,0 +1,20 @@ +nelmio_api_doc: + documentation: + info: + description: | + Multiline string + + Second line + foo: bar +=== +$data['nelmio_api_doc']['documentation']['info']['description'] = "Multiline string\n\nSecond line\nThird line\n"; +=== +nelmio_api_doc: + documentation: + info: + description: | + Multiline string + + Second line + Third line + foo: bar \ No newline at end of file diff --git a/tests/Util/yaml_fixtures/multiline_string_without_update.test b/tests/Util/yaml_fixtures/multiline_string_without_update.test new file mode 100644 index 000000000..c004d12c2 --- /dev/null +++ b/tests/Util/yaml_fixtures/multiline_string_without_update.test @@ -0,0 +1,15 @@ +nelmio_api_doc: + documentation: + info: + description: | + Multiline string + Second line +=== + +=== +nelmio_api_doc: + documentation: + info: + description: | + Multiline string + Second line \ No newline at end of file