Skip to content

Commit ca51e51

Browse files
authored
[BUGFIX] Parse comment(s) immediately preceding selector (part 2) (#1424)
Now comments with no whitespace before the selector are parsed and extracted. `setSelectors()` also receives the same fix, for the unlikely case when someone calls it with a selector string beginning with a comment.
1 parent 515317d commit ca51e51

File tree

3 files changed

+87
-11
lines changed

3 files changed

+87
-11
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Please also have a look at our
2626

2727
- Reject selector comprising only whitespace (#1433)
2828
- Improve recovery parsing when a rogue `}` is encountered (#1425, #1426)
29-
- Parse comment(s) immediately preceding a selector (#1421)
29+
- Parse comment(s) immediately preceding a selector (#1421, #1424)
3030
- Parse consecutive comments (#1421)
3131
- Support attribute selectors with values containing commas in
3232
`DeclarationBlock::setSelectors()` (#1419)

src/RuleSet/DeclarationBlock.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,6 @@ private static function parseSelector(ParserState $parserState, array &$comments
307307
static $stopCharacters = ['{', '}', '\'', '"', '(', ')', ','];
308308

309309
while (true) {
310-
$selectorParts[] = $parserState->consume(1);
311310
$selectorParts[] = $parserState->consumeUntil($stopCharacters, false, false, $comments);
312311
$nextCharacter = $parserState->peek();
313312
switch ($nextCharacter) {
@@ -348,6 +347,7 @@ private static function parseSelector(ParserState $parserState, array &$comments
348347
}
349348
break;
350349
}
350+
$selectorParts[] = $parserState->consume(1);
351351
}
352352

353353
if ($functionNestingLevel !== 0) {

tests/Unit/RuleSet/DeclarationBlockTest.php

Lines changed: 85 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Sabberworm\CSS\Tests\Unit\RuleSet;
66

77
use PHPUnit\Framework\TestCase;
8+
use Sabberworm\CSS\Comment\Comment;
89
use Sabberworm\CSS\CSSElement;
910
use Sabberworm\CSS\CSSList\CSSListItem;
1011
use Sabberworm\CSS\Parsing\ParserState;
@@ -158,21 +159,85 @@ public function parsesTwoCommaSeparatedSelectors(string $firstSelector, string $
158159
self::assertSame([$firstSelector, $secondSelector], self::getSelectorsAsStrings($subject));
159160
}
160161

162+
/**
163+
* @return array<non-empty-string, array{0: non-empty-string, 1: non-empty-string}>
164+
*/
165+
public static function provideSelectorWithAndWithoutComment(): array
166+
{
167+
return [
168+
'comment before' => ['/*comment*/body', 'body'],
169+
'comment after' => ['body/*comment*/', 'body'],
170+
'comment within' => ['./*comment*/teapot', '.teapot'],
171+
'comment within function' => [':not(#your-mug,/*comment*/.their-mug)', ':not(#your-mug,.their-mug)'],
172+
];
173+
}
174+
175+
/**
176+
* @test
177+
*
178+
* @param non-empty-string $selectorWith
179+
* @param non-empty-string $selectorWithout
180+
*
181+
* @dataProvider provideSelectorWithAndWithoutComment
182+
*/
183+
public function parsesSelectorWithComment(string $selectorWith, string $selectorWithout): void
184+
{
185+
$subject = DeclarationBlock::parse(new ParserState($selectorWith . ' {}', Settings::create()));
186+
187+
self::assertInstanceOf(DeclarationBlock::class, $subject);
188+
self::assertSame([$selectorWithout], self::getSelectorsAsStrings($subject));
189+
}
190+
191+
/**
192+
* @test
193+
*
194+
* @param non-empty-string $selector
195+
*
196+
* @dataProvider provideSelectorWithAndWithoutComment
197+
*/
198+
public function parseExtractsCommentFromSelector(string $selector): void
199+
{
200+
$subject = DeclarationBlock::parse(new ParserState($selector . ' {}', Settings::create()));
201+
202+
self::assertInstanceOf(DeclarationBlock::class, $subject);
203+
self::assertSame(['comment'], self::getCommentsAsStrings($subject));
204+
}
205+
206+
/**
207+
* @test
208+
*/
209+
public function parsesSelectorWithTwoComments(): void
210+
{
211+
$subject = DeclarationBlock::parse(new ParserState('/*comment1*/a/*comment2*/ {}', Settings::create()));
212+
213+
self::assertInstanceOf(DeclarationBlock::class, $subject);
214+
self::assertSame(['a'], self::getSelectorsAsStrings($subject));
215+
}
216+
217+
/**
218+
* @test
219+
*/
220+
public function parseExtractsTwoCommentsFromSelector(): void
221+
{
222+
$subject = DeclarationBlock::parse(new ParserState('/*comment1*/a/*comment2*/ {}', Settings::create()));
223+
224+
self::assertInstanceOf(DeclarationBlock::class, $subject);
225+
self::assertSame(['comment1', 'comment2'], self::getCommentsAsStrings($subject));
226+
}
227+
161228
/**
162229
* @return array<non-empty-string, array{0: string, 1: non-empty-string}>
163230
*/
164231
public static function provideInvalidSelectorAndExpectedExceptionMessage(): array
165232
{
166-
// TODO: the `parse` method consumes the first character without inspection,
167-
// so some of the test strings are prefixed with a space.
168233
return [
169-
'no selector' => [' ', 'Token “selector” (literal) not found. Got “{”. [line no: 1]'],
170-
'lone `(`' => [' (', 'Token “)” (literal) not found. Got “{”.'],
171-
'lone `)`' => [' )', 'Token “anything but” (literal) not found. Got “)”.'],
172-
'lone `,`' => [' ,', 'Token “selector” (literal) not found. Got “,”. [line no: 1]'],
234+
'no selector' => ['', 'Token “selector” (literal) not found. Got “{”. [line no: 1]'],
235+
'lone `(`' => ['(', 'Token “)” (literal) not found. Got “{”.'],
236+
'lone `)`' => [')', 'Token “anything but” (literal) not found. Got “)”.'],
237+
'lone `,`' => [',', 'Token “selector” (literal) not found. Got “,”. [line no: 1]'],
173238
'unclosed `(`' => [':not(#your-mug', 'Token “)” (literal) not found. Got “{”.'],
174239
'extra `)`' => [':not(#your-mug))', 'Token “anything but” (literal) not found. Got “)”.'],
175-
'`,` missing left operand' => [' , a', 'Token “selector” (literal) not found. Got “,”. [line no: 1]'],
240+
'`,` missing left operand' => [', a', 'Token “selector” (literal) not found. Got “,”. [line no: 1]'],
176241
'`,` missing right operand' => ['a,', 'Token “selector” (literal) not found. Got “{”. [line no: 1]'],
177242
];
178243
}
@@ -199,8 +264,6 @@ static function (array $testData): array {
199264
/**
200265
* @test
201266
*
202-
* @param non-empty-string $selector
203-
*
204267
* @dataProvider provideInvalidSelector
205268
*/
206269
public function parseSkipsBlockWithInvalidSelector(string $selector): void
@@ -326,6 +389,19 @@ static function (Selector $selectorObject): string {
326389
);
327390
}
328391

392+
/**
393+
* @return list<string>
394+
*/
395+
private static function getCommentsAsStrings(DeclarationBlock $declarationBlock): array
396+
{
397+
return \array_map(
398+
static function (Comment $comment): string {
399+
return $comment->getComment();
400+
},
401+
$declarationBlock->getComments()
402+
);
403+
}
404+
329405
/**
330406
* @test
331407
*/

0 commit comments

Comments
 (0)