From 0481b91f32c6add5425f46eaf841095b2f4b6df4 Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 13 Apr 2026 22:17:48 +0200 Subject: [PATCH 1/3] Fix unindented paragraph being absorbed as list item continuation When a list item contained a nested block (e.g. an indented code block) followed by a blank line and an unindented paragraph, the paragraph was being collected as lazy continuation of the list item instead of ending the list. After a blank line inside nested content collection, content dropping back to base indent must break out so the parent parser handles it as a new block. --- src/Parser/BlockParser.php | 8 +++++ tests/TestCase/NestedBlocksInListsTest.php | 35 ++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/Parser/BlockParser.php b/src/Parser/BlockParser.php index 3483f53..7c8af24 100644 --- a/src/Parser/BlockParser.php +++ b/src/Parser/BlockParser.php @@ -1500,6 +1500,14 @@ protected function tryParseList(Node $parent, array $lines, int $start): ?int if ($itemInfo !== null && $itemInfo['type'] === $listInfo['type'] && $itemInfo['marker'] === $listInfo['marker'] && $sameStyle) { break; } + // After a blank line, content dropping back to base indent + // starts a new block outside the list - let parent handle it. + if ($sawBlankLine) { + $lastItemHadBlankAfter = true; + $brokeForParentContent = true; + + break; + } // Content at base indent that's not a matching list marker // Check if it's a block element - if so, end list content collection // Use isBlockElementStart() which detects blocks regardless of mode diff --git a/tests/TestCase/NestedBlocksInListsTest.php b/tests/TestCase/NestedBlocksInListsTest.php index a54fe4a..b635fdc 100644 --- a/tests/TestCase/NestedBlocksInListsTest.php +++ b/tests/TestCase/NestedBlocksInListsTest.php @@ -633,4 +633,39 @@ public function testIssue83CodeBlockCase(): void // Should NOT be inline code $this->assertStringNotContainsString('

', $result); } + + /** + * After a nested block inside a list item and a blank line, an + * unindented paragraph must terminate the list rather than being + * absorbed as a sub-item of the previous list item. + * + * @see https://github.com/php-collective/djot-php/issues/176 + */ + public function testIssue176UnindentedParagraphAfterNestedCodeBlockEndsList(): void + { + $djot = <<<'DJOT' +1. Item 1 +2. Item 2 + + ``` + Example + ``` + +New list: + +* New item 1 +* New item 2 +DJOT; + + $result = $this->converter->convert($djot); + + $this->assertStringContainsString('', $result); + // The "New list:" paragraph must appear after the ordered list closes. + $olClose = strpos($result, ''); + $paragraph = strpos($result, '

New list:

'); + $this->assertNotFalse($paragraph); + $this->assertGreaterThan($olClose, $paragraph); + // And the following bullet list must be a sibling, not nested in
  • . + $this->assertMatchesRegularExpression('#\s*

    New list:

    \s*
      #', $result); + } } From 134cf3128ca2e94dfa8d6e853328bdcf27d1637e Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 13 Apr 2026 22:22:26 +0200 Subject: [PATCH 2/3] Fix anonymous class keyword spacing for Universal CS rule --- tests/TestCase/Extension/HeadingReferenceExtensionTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/TestCase/Extension/HeadingReferenceExtensionTest.php b/tests/TestCase/Extension/HeadingReferenceExtensionTest.php index 15cfcc6..ef5ec00 100644 --- a/tests/TestCase/Extension/HeadingReferenceExtensionTest.php +++ b/tests/TestCase/Extension/HeadingReferenceExtensionTest.php @@ -251,7 +251,7 @@ public function testHeadingWithNoTextIsIgnored(): void public function testUserAuthoredLinkWithMatchingPlaceholderIsNotRewritten(): void { - $extension = new class ('heading-ref') extends HeadingReferenceExtension { + $extension = new class('heading-ref') extends HeadingReferenceExtension { protected function generatePlaceholderPrefix(): string { return 'collision-placeholder-'; From c54dcc7691c2ad0bdb881bc9fe7b4abeb7017b32 Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 13 Apr 2026 22:36:54 +0200 Subject: [PATCH 3/3] Exclude conflicting PSR12 anon class rule in phpcs config PhpCollective enables both PSR12 and Universal.WhiteSpace.AnonClassKeywordSpacing which disagree on whether a space must appear between 'class' and '(' in anonymous classes. Exclude the PSR12 sub-rule so CI (which enforces Universal) matches local runs. --- phpcs.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/phpcs.xml b/phpcs.xml index 3cf4581..759198c 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -8,7 +8,10 @@ */vendor/* - + + + +