From 0806e294c87e8626489f0ec32cfdf72c8adbbf60 Mon Sep 17 00:00:00 2001 From: Jake Hotson Date: Thu, 11 Dec 2025 11:35:59 +0000 Subject: [PATCH] [BUGFIX] Parse comment(s) immediately preceding selector Also parse consecutive comments. --- CHANGELOG.md | 2 + src/Parsing/ParserState.php | 23 +++++- tests/ParserTest.php | 7 +- tests/Unit/Parsing/ParserStateTest.php | 100 +++++++++++++++++++++++++ 4 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 tests/Unit/Parsing/ParserStateTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 74bdef76e..ffbd588f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,8 @@ Please also have a look at our ### Fixed +- Parse comment(s) immediately preceding a selector (#1421) +- Parse consecutive comments (#1421) - Support attribute selectors with values containing commas in `DeclarationBlock::setSelectors()` (#1419) - Allow `removeDeclarationBlockBySelector()` to be order-insensitve (#1406) diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 722cd92c6..e17c3f3ab 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -343,6 +343,7 @@ public function consumeUntil( $consumedCharacters = ''; $start = $this->currentPosition; + $comments = \array_merge($comments, $this->consumeComments()); while (!$this->isEnd()) { $character = $this->consume(1); if (\in_array($character, $stopCharacters, true)) { @@ -354,10 +355,7 @@ public function consumeUntil( return $consumedCharacters; } $consumedCharacters .= $character; - $comment = $this->consumeComment(); - if ($comment instanceof Comment) { - $comments[] = $comment; - } + $comments = \array_merge($comments, $this->consumeComments()); } if (\in_array(self::EOF, $stopCharacters, true)) { @@ -455,4 +453,21 @@ private function strsplit(string $string): array return $result; } + + /** + * @return list + */ + private function consumeComments(): array + { + $comments = []; + + while (true) { + $comment = $this->consumeComment(); + if ($comment instanceof Comment) { + $comments[] = $comment; + } else { + return $comments; + } + } + } } diff --git a/tests/ParserTest.php b/tests/ParserTest.php index 7604feb31..5b360bdae 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -1048,10 +1048,9 @@ public function commentExtracting(): void $fooBarBlock = $nodes[1]; self::assertInstanceOf(Commentable::class, $fooBarBlock); $fooBarBlockComments = $fooBarBlock->getComments(); - // TODO Support comments in selectors. - // $this->assertCount(2, $fooBarBlockComments); - // $this->assertSame("* Number 4 *", $fooBarBlockComments[0]->getComment()); - // $this->assertSame("* Number 5 *", $fooBarBlockComments[1]->getComment()); + self::assertCount(2, $fooBarBlockComments); + self::assertSame(' Number 4 ', $fooBarBlockComments[0]->getComment()); + self::assertSame(' Number 5 ', $fooBarBlockComments[1]->getComment()); // Declaration rules. self::assertInstanceOf(DeclarationBlock::class, $fooBarBlock); diff --git a/tests/Unit/Parsing/ParserStateTest.php b/tests/Unit/Parsing/ParserStateTest.php new file mode 100644 index 000000000..245b083f8 --- /dev/null +++ b/tests/Unit/Parsing/ParserStateTest.php @@ -0,0 +1,100 @@ + + * } + * > + */ + public static function provideTextForConsumptionWithComments(): array + { + return [ + 'comment at start' => [ + 'text' => '/*comment*/hello{', + 'stopCharacter' => '{', + 'expectedConsumedText' => 'hello', + 'expectedComments' => ['comment'], + ], + 'comment at end' => [ + 'text' => 'hello/*comment*/{', + 'stopCharacter' => '{', + 'expectedConsumedText' => 'hello', + 'expectedComments' => ['comment'], + ], + 'comment in middle' => [ + 'text' => 'hell/*comment*/o{', + 'stopCharacter' => '{', + 'expectedConsumedText' => 'hello', + 'expectedComments' => ['comment'], + ], + 'two comments at start' => [ + 'text' => '/*comment1*//*comment2*/hello{', + 'stopCharacter' => '{', + 'expectedConsumedText' => 'hello', + 'expectedComments' => ['comment1', 'comment2'], + ], + 'two comments at end' => [ + 'text' => 'hello/*comment1*//*comment2*/{', + 'stopCharacter' => '{', + 'expectedConsumedText' => 'hello', + 'expectedComments' => ['comment1', 'comment2'], + ], + 'two comments interspersed' => [ + 'text' => 'he/*comment1*/ll/*comment2*/o{', + 'stopCharacter' => '{', + 'expectedConsumedText' => 'hello', + 'expectedComments' => ['comment1', 'comment2'], + ], + ]; + } + + /** + * @test + * + * @param non-empty-string $text + * @param non-empty-string $stopCharacter + * @param non-empty-string $expectedConsumedText + * @param non-empty-list $expectedComments + * + * @dataProvider provideTextForConsumptionWithComments + */ + public function consumeUntilExtractsComments( + string $text, + string $stopCharacter, + string $expectedConsumedText, + array $expectedComments + ): void { + $subject = new ParserState($text, Settings::create()); + + $comments = []; + $result = $subject->consumeUntil($stopCharacter, false, false, $comments); + + self::assertSame($expectedConsumedText, $result); + $commentsAsText = \array_map( + static function (Comment $comment): string { + return $comment->getComment(); + }, + $comments + ); + self::assertSame($expectedComments, $commentsAsText); + } +}