From ae2e8dc9a87efd3d3acd3f70fdde091d0f22caa3 Mon Sep 17 00:00:00 2001 From: Jake Hotson Date: Sat, 13 Dec 2025 18:59:46 +0000 Subject: [PATCH] [BUGFIX] Skip erroneous `}` when parsing `CSSList` This allows parsing of the next item, if valid, rather than dropping it. --- CHANGELOG.md | 2 +- src/CSSList/CSSList.php | 3 ++- tests/Unit/CSSList/CSSListTest.php | 38 ++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index caf9aa4e..421d9419 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ Please also have a look at our ### Fixed -- Improve recovery parsing when a rogue `}` is encountered (#1425) +- Improve recovery parsing when a rogue `}` is encountered (#1425, #1426) - Parse comment(s) immediately preceding a selector (#1421) - Parse consecutive comments (#1421) - Support attribute selectors with values containing commas in diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 56495f38..84ece7f8 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -133,7 +133,8 @@ private static function parseListItem(ParserState $parserState, CSSList $list) } elseif ($parserState->comes('}')) { if ($isRoot) { if ($parserState->getSettings()->usesLenientParsing()) { - return DeclarationBlock::parse($parserState) ?? false; + $parserState->consume(1); + return self::parseListItem($parserState, $list); } else { throw new SourceException('Unopened {', $parserState->currentLine()); } diff --git a/tests/Unit/CSSList/CSSListTest.php b/tests/Unit/CSSList/CSSListTest.php index 523ada09..548acbb7 100644 --- a/tests/Unit/CSSList/CSSListTest.php +++ b/tests/Unit/CSSList/CSSListTest.php @@ -7,10 +7,15 @@ use PHPUnit\Framework\TestCase; use Sabberworm\CSS\Comment\Commentable; use Sabberworm\CSS\CSSElement; +use Sabberworm\CSS\CSSList\CSSList; use Sabberworm\CSS\CSSList\CSSListItem; +use Sabberworm\CSS\CSSList\Document; +use Sabberworm\CSS\OutputFormat; +use Sabberworm\CSS\Parsing\ParserState; use Sabberworm\CSS\Property\Selector; use Sabberworm\CSS\Renderable; use Sabberworm\CSS\RuleSet\DeclarationBlock; +use Sabberworm\CSS\Settings; use Sabberworm\CSS\Tests\Unit\CSSList\Fixtures\ConcreteCSSList; /** @@ -342,4 +347,37 @@ public function removeDeclarationBlockBySelectorRemovesMultipleBlocksWithStringS self::assertSame([], $subject->getContents()); } + + /** + * The content provided must (currently) be in the same format as the expected rendering. + * + * @return array + */ + public function provideValidContentForParsing(): array + { + return [ + 'at-import rule' => ['@import url("foo.css");'], + 'rule with declaration block' => ['a {color: green;}'], + ]; + } + + /** + * @test + * + * @param non-empty-string $followingContent + * + * @dataProvider provideValidContentForParsing + */ + public function parseListAtRootLevelSkipsErroneousClosingBraceAndParsesFollowingContent( + string $followingContent + ): void { + $parserState = new ParserState('}' . $followingContent, Settings::create()); + // The subject needs to be a `Document`, as that is currently the test for 'root level'. + // Otherwise `}` will be treated as 'end of list'. + $subject = new Document(); + + CSSList::parseList($parserState, $subject); + + self::assertSame($followingContent, $subject->render(new OutputFormat())); + } }