diff --git a/src/Renderer/Block/ListBlockRenderer.php b/src/Renderer/Block/ListBlockRenderer.php index 89ca475..1ede272 100644 --- a/src/Renderer/Block/ListBlockRenderer.php +++ b/src/Renderer/Block/ListBlockRenderer.php @@ -26,6 +26,8 @@ public function render(Node $node, ChildNodeRendererInterface $childRenderer): s $content = $childRenderer->renderNodes($node->children()); $content = explode("\n", $content); + $content = array_map(fn ($item) => $this->replaceInternalLineBreakCharacter($item), $content); + if ($listData->type === ListBlock::TYPE_BULLET) { $content = array_map(fn ($item) => "- {$item}", $content); } @@ -42,4 +44,16 @@ public function render(Node $node, ChildNodeRendererInterface $childRenderer): s return implode("\n", $content) . "\n"; } + + /** + * Replace custom line break character with _native_ line breaks. + * Whitespace is added so that other Markdown clients correctly + * render the list and its line breaks. + * @param string $content + * @return string + */ + private function replaceInternalLineBreakCharacter(string $content): string + { + return str_replace(ListItemRenderer::INLINE_LINE_BREAK, " \n ", $content); + } } diff --git a/src/Renderer/Block/ListItemRenderer.php b/src/Renderer/Block/ListItemRenderer.php index cbceb3d..2bcadd1 100644 --- a/src/Renderer/Block/ListItemRenderer.php +++ b/src/Renderer/Block/ListItemRenderer.php @@ -12,6 +12,8 @@ final class ListItemRenderer implements \League\CommonMark\Renderer\NodeRendererInterface { + public const INLINE_LINE_BREAK = '_COMMONMARK_MARKDOWN_RENDERER_LINE_BREAK_'; + /** * @param ListItem $node * @@ -24,6 +26,15 @@ public function render(Node $node, ChildNodeRendererInterface $childRenderer): s ListItem::assertInstanceOf($node); $contents = $childRenderer->renderNodes($node->children()); + + // If the ListItem contains a line break, replace the line break with a custom string. + // The custom line break string is being replaced with a _native_ line break again, when + // being rendered in a ListBlock. + // This workaround is required to support multi-line list items. + if (str_contains($contents, "\n")) { + $contents = str_replace("\n", self::INLINE_LINE_BREAK, $contents); + } + if (str_starts_with($contents, '<') && ! $this->startsTaskListItem($node)) { $contents = "\n" . $contents; } diff --git a/tests/Renderer/MarkdownRendererTest.php b/tests/Renderer/MarkdownRendererTest.php index 89a252f..d905698 100644 --- a/tests/Renderer/MarkdownRendererTest.php +++ b/tests/Renderer/MarkdownRendererTest.php @@ -41,11 +41,29 @@ public function it_renders_ast_to_markdown(): void public function it_parses_and_renders_kitchen_sink(): void { $contentKitchenSink = file_get_contents(__DIR__ . '/../stubs/kitchen-sink.md'); + $contentKitchenSinkExpected = file_get_contents(__DIR__ . '/../stubs/kitchen-sink-expected.md'); $document = $this->parser->parse($contentKitchenSink); $result = $this->renderer->renderDocument($document)->getContent(); - $this->assertEquals($contentKitchenSink, $result); + $this->assertEquals($contentKitchenSinkExpected, $result); + } + + #[Test] + public function it_parses_kitchen_sink_and_parsing_the_result_again_returns_the_same_result(): void + { + $contentKitchenSink = file_get_contents(__DIR__ . '/../stubs/kitchen-sink.md'); + $contentKitchenSinkExpected = file_get_contents(__DIR__ . '/../stubs/kitchen-sink-expected.md'); + + $document = $this->parser->parse($contentKitchenSink); + + $result = $this->renderer->renderDocument($document)->getContent(); + $this->assertEquals($contentKitchenSinkExpected, $result); + + // Take the result and parse it again + $document = $this->parser->parse($result); + $result = $this->renderer->renderDocument($document)->getContent(); + $this->assertEquals($contentKitchenSinkExpected, $result); } } diff --git a/tests/stubs/kitchen-sink-expected.md b/tests/stubs/kitchen-sink-expected.md new file mode 100644 index 0000000..68ab37b --- /dev/null +++ b/tests/stubs/kitchen-sink-expected.md @@ -0,0 +1,27 @@ +# Changelog + +All notable changes to this project will be documented in this file. +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + +## Curiosities + +- This is a list item which is followed by an indented content on the next line + which is part of the same list item +- This is a list item which contains multiple paragraphs + This is the second paragraph. + This is the third paragraph. +- This is a list item with multiple paragraphs, but without trailing whitespace in the original document at the end + This is the second paragraph. + This is the third paragraph. + +## Task List + +- [ ] Task 1 +- [x] Task 2 + +## [Unreleased](https://github.com/olivierlacan/keep-a-changelog/compare/v1.0.0...HEAD) + +**This Text is Bold** +*This Text is Italic* + +`\Class->getFoo()` diff --git a/tests/stubs/kitchen-sink.md b/tests/stubs/kitchen-sink.md index 2ec39c7..cb6bb8e 100644 --- a/tests/stubs/kitchen-sink.md +++ b/tests/stubs/kitchen-sink.md @@ -1,9 +1,18 @@ # Changelog All notable changes to this project will be documented in this file. +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Curiosities + +- This is a list item which is followed by an indented content on the next line + which is part of the same list item +- This is a list item which contains multiple paragraphs + This is the second paragraph. + This is the third paragraph. +- This is a list item with multiple paragraphs, but without trailing whitespace in the original document at the end + This is the second paragraph. + This is the third paragraph. ## Task List @@ -12,165 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/olivierlacan/keep-a-changelog/compare/v1.0.0...HEAD) -## [1.0.0](https://github.com/olivierlacan/keep-a-changelog/compare/v0.3.0...v1.0.0) - 2017-06-20 - -### Added - -- New visual identity by [@tylerfortune8](https://github.com/tylerfortune8). -- Version navigation. -- Links to latest released version in previous versions. -- "Why keep a changelog?" section. -- "Who needs a changelog?" section. -- "How do I make a changelog?" section. -- "Frequently Asked Questions" section. -- New "Guiding Principles" sub-section to "How do I make a changelog?". -- Simplified and Traditional Chinese translations from [@tianshuo](https://github.com/tianshuo). -- German translation from [@mpbzh](https://github.com/mpbzh) & [@Art4](https://github.com/Art4). -- Italian translation from [@azkidenz](https://github.com/azkidenz). -- Swedish translation from [@magol](https://github.com/magol). -- Turkish translation from [@karalamalar](https://github.com/karalamalar). -- French translation from [@zapashcanon](https://github.com/zapashcanon). -- Brazilian Portugese translation from [@Webysther](https://github.com/Webysther). -- Polish translation from [@amielucha](https://github.com/amielucha) & [@m-aciek](https://github.com/m-aciek). -- Russian translation from [@aishek](https://github.com/aishek). -- Czech translation from [@h4vry](https://github.com/h4vry). -- Slovak translation from [@jkostolansky](https://github.com/jkostolansky). -- Korean translation from [@pierceh89](https://github.com/pierceh89). -- Croatian translation from [@porx](https://github.com/porx). -- Persian translation from [@Hameds](https://github.com/Hameds). -- Ukrainian translation from [@osadchyi-s](https://github.com/osadchyi-s). - -### Changed - -- Start using "changelog" over "change log" since it's the common usage. -- Start versioning based on the current English version at 0.3.0 to help -- translation authors keep things up-to-date. -- Rewrite "What makes unicorns cry?" section. -- Rewrite "Ignoring Deprecations" sub-section to clarify the ideal -- scenario. -- Improve "Commit log diffs" sub-section to further argument against -- them. -- Merge "Why can’t people just use a git log diff?" with "Commit log -- diffs" -- Fix typos in Simplified Chinese and Traditional Chinese translations. -- Fix typos in Brazilian Portuguese translation. -- Fix typos in Turkish translation. -- Fix typos in Czech translation. -- Fix typos in Swedish translation. -- Improve phrasing in French translation. -- Fix phrasing and spelling in German translation. - -### Removed - -- Section about "changelog" vs "CHANGELOG". - -## [0.3.0](https://github.com/olivierlacan/keep-a-changelog/compare/v0.2.0...v0.3.0) - 2015-12-03 - -### Added - -- RU translation from [@aishek](https://github.com/aishek). -- pt-BR translation from [@tallesl](https://github.com/tallesl). -- es-ES translation from [@ZeliosAriex](https://github.com/ZeliosAriex). - -## [0.2.0](https://github.com/olivierlacan/keep-a-changelog/compare/v0.1.0...v0.2.0) - 2015-10-06 - -### Changed - -- Remove exclusionary mentions of "open source" since this project can -- benefit both "open" and "closed" source projects equally. - -## [0.1.0](https://github.com/olivierlacan/keep-a-changelog/compare/v0.0.8...v0.1.0) - 2015-10-06 - -### Added - -- Answer "Should you ever rewrite a change log?". - -### Changed - -- Improve argument against commit logs. -- Start following [SemVer](https://semver.org) properly. - -## [0.0.8](https://github.com/olivierlacan/keep-a-changelog/compare/v0.0.7...v0.0.8) - 2015-02-17 - -### Changed - -- Update year to match in every README example. -- Reluctantly stop making fun of Brits only, since most of the world -- writes dates in a strange way. - -### Fixed - -- Fix typos in recent README changes. -- Update outdated unreleased diff link. - -## [0.0.7](https://github.com/olivierlacan/keep-a-changelog/compare/v0.0.6...v0.0.7) - 2015-02-16 - -### Added - -- Link, and make it obvious that date format is ISO 8601. - -### Changed - -- Clarified the section on "Is there a standard change log format?". - -### Fixed - -- Fix Markdown links to tag comparison URL with footnote-style links. - -## [0.0.6](https://github.com/olivierlacan/keep-a-changelog/compare/v0.0.5...v0.0.6) - 2014-12-12 - -### Added - -- README section on "yanked" releases. - -## [0.0.5](https://github.com/olivierlacan/keep-a-changelog/compare/v0.0.4...v0.0.5) - 2014-08-09 - -### Added - -- Markdown links to version tags on release headings. -- Unreleased section to gather unreleased changes and encourage note -- keeping prior to releases. - -## [0.0.4](https://github.com/olivierlacan/keep-a-changelog/compare/v0.0.3...v0.0.4) - 2014-08-09 - -### Added - -- Better explanation of the difference between the file ("CHANGELOG") -- and its function "the change log". - -### Changed - -- Refer to a "change log" instead of a "CHANGELOG" throughout the site -- to differentiate between the file and the purpose of the file — the -- logging of changes. - -### Removed - -- Remove empty sections from CHANGELOG, they occupy too much space and -- create too much noise in the file. People will have to assume that the -- missing sections were intentionally left out because they contained no -- notable changes. - -## [0.0.3](https://github.com/olivierlacan/keep-a-changelog/compare/v0.0.2...v0.0.3) - 2014-08-09 - -### Added - -- "Why should I care?" section mentioning The Changelog podcast. - -## [0.0.2](https://github.com/olivierlacan/keep-a-changelog/compare/v0.0.1...v0.0.2) - 2014-07-10 - -### Added - -- Explanation of the recommended reverse chronological release ordering. - -## [0.0.1](https://github.com/olivierlacan/keep-a-changelog/releases/tag/v0.0.1) - 2014-05-31 - -### Added +**This Text is Bold** +*This Text is Italic* -- This CHANGELOG file to hopefully serve as an evolving example of a -- standardized open source project CHANGELOG. -- CNAME file to enable GitHub Pages custom domain -- README now contains answers to common questions about CHANGELOGs -- Good examples and basic guidelines, including proper date formatting. -- Counter-examples: "What makes unicorns cry?" -- `\Class->getFoo()` +`\Class->getFoo()`