Skip to content

Commit 090d8af

Browse files
committed
[TASK] Add (and use) a CSSListItem type
This allows a single type to be used for the contents of a `CSSList`, instead of a long list of orred types, and helps with static analysis. Various `assertInstanceOf()` tests are added to the test cases to confirm that the list items are of the type expected. Some `implements` and `exetends` lists are now alphabetically sorted.
1 parent 6393660 commit 090d8af

File tree

9 files changed

+94
-45
lines changed

9 files changed

+94
-45
lines changed

config/phpstan-baseline.neon

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ parameters:
1818
count: 1
1919
path: ../src/CSSList/CSSList.php
2020

21+
-
22+
message: '#^Parameters should have "Sabberworm\\CSS\\CSSList\\CSSListItem\|array" types as the only types passed to this method$#'
23+
identifier: typePerfect.narrowPublicClassMethodParamType
24+
count: 1
25+
path: ../src/CSSList/CSSList.php
26+
2127
-
2228
message: '#^Short ternary operator is not allowed\. Use null coalesce operator if applicable or consider using long ternary\.$#'
2329
identifier: ternary.shortNotAllowed
@@ -48,24 +54,12 @@ parameters:
4854
count: 1
4955
path: ../src/RuleSet/DeclarationBlock.php
5056

51-
-
52-
message: '#^Returning false in non return bool class method\. Use null with type\|null instead or add bool return type$#'
53-
identifier: typePerfect.nullOverFalse
54-
count: 1
55-
path: ../src/RuleSet/DeclarationBlock.php
56-
5757
-
5858
message: '#^Only booleans are allowed in a negated boolean, string\|null given\.$#'
5959
identifier: booleanNot.exprNotBoolean
6060
count: 2
6161
path: ../src/RuleSet/RuleSet.php
6262

63-
-
64-
message: '#^Parameters should have "Sabberworm\\CSS\\Rule\\Rule" types as the only types passed to this method$#'
65-
identifier: typePerfect.narrowPublicClassMethodParamType
66-
count: 1
67-
path: ../src/RuleSet/RuleSet.php
68-
6963
-
7064
message: '#^Parameters should have "string" types as the only types passed to this method$#'
7165
identifier: typePerfect.narrowPublicClassMethodParamType

src/CSSList/CSSList.php

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
*
3232
* It can also contain `Import` and `Charset` objects stemming from at-rules.
3333
*/
34-
abstract class CSSList implements Renderable, Commentable
34+
abstract class CSSList implements Commentable, CSSListItem, Renderable
3535
{
3636
/**
3737
* @var list<Comment>
@@ -41,7 +41,7 @@ abstract class CSSList implements Renderable, Commentable
4141
protected $comments = [];
4242

4343
/**
44-
* @var array<int<0, max>, RuleSet|CSSList|Import|Charset>
44+
* @var array<int<0, max>, CSSListItem>
4545
*
4646
* @internal since 8.8.0
4747
*/
@@ -105,7 +105,10 @@ public static function parseList(ParserState $parserState, CSSList $list): void
105105
}
106106

107107
/**
108-
* @return AtRuleBlockList|KeyFrame|Charset|CSSNamespace|Import|AtRuleSet|DeclarationBlock|false|null
108+
* @return CSSListItem|false|null
109+
* If `null` is returned, it means the end of the list has been reached.
110+
* If `false` is returned, it means an invalid item has been encountered,
111+
* but parsing of the next item should proceed.
109112
*
110113
* @throws SourceException
111114
* @throws UnexpectedEOFException
@@ -139,7 +142,7 @@ private static function parseListItem(ParserState $parserState, CSSList $list)
139142
} elseif ($parserState->comes('}')) {
140143
if ($isRoot) {
141144
if ($parserState->getSettings()->usesLenientParsing()) {
142-
return DeclarationBlock::parse($parserState);
145+
return DeclarationBlock::parse($parserState) ?? false;
143146
} else {
144147
throw new SourceException('Unopened {', $parserState->currentLine());
145148
}
@@ -148,18 +151,16 @@ private static function parseListItem(ParserState $parserState, CSSList $list)
148151
return null;
149152
}
150153
} else {
151-
return DeclarationBlock::parse($parserState, $list);
154+
return DeclarationBlock::parse($parserState, $list) ?? false;
152155
}
153156
}
154157

155158
/**
156-
* @return AtRuleBlockList|KeyFrame|Charset|CSSNamespace|Import|AtRuleSet|null
157-
*
158159
* @throws SourceException
159160
* @throws UnexpectedTokenException
160161
* @throws UnexpectedEOFException
161162
*/
162-
private static function parseAtRule(ParserState $parserState)
163+
private static function parseAtRule(ParserState $parserState): ?CSSListItem
163164
{
164165
$parserState->consume('@');
165166
$identifier = $parserState->parseIdentifier();
@@ -262,28 +263,24 @@ public function getLineNo(): int
262263

263264
/**
264265
* Prepends an item to the list of contents.
265-
*
266-
* @param RuleSet|CSSList|Import|Charset $item
267266
*/
268-
public function prepend($item): void
267+
public function prepend(CSSListItem $item): void
269268
{
270269
\array_unshift($this->contents, $item);
271270
}
272271

273272
/**
274273
* Appends an item to the list of contents.
275-
*
276-
* @param RuleSet|CSSList|Import|Charset $item
277274
*/
278-
public function append($item): void
275+
public function append(CSSListItem $item): void
279276
{
280277
$this->contents[] = $item;
281278
}
282279

283280
/**
284281
* Splices the list of contents.
285282
*
286-
* @param array<int, RuleSet|CSSList|Import|Charset> $replacement
283+
* @param array<int, CSSListItem> $replacement
287284
*/
288285
public function splice(int $offset, ?int $length = null, ?array $replacement = null): void
289286
{
@@ -293,11 +290,8 @@ public function splice(int $offset, ?int $length = null, ?array $replacement = n
293290
/**
294291
* Inserts an item in the CSS list before its sibling. If the desired sibling cannot be found,
295292
* the item is appended at the end.
296-
*
297-
* @param RuleSet|CSSList|Import|Charset $item
298-
* @param RuleSet|CSSList|Import|Charset $sibling
299293
*/
300-
public function insertBefore($item, $sibling): void
294+
public function insertBefore(CSSListItem $item, CSSListItem $sibling): void
301295
{
302296
if (\in_array($sibling, $this->contents, true)) {
303297
$this->replace($sibling, [$item, $sibling]);
@@ -309,13 +303,13 @@ public function insertBefore($item, $sibling): void
309303
/**
310304
* Removes an item from the CSS list.
311305
*
312-
* @param RuleSet|Import|Charset|CSSList $itemToRemove
306+
* @param CSSListItem $itemToRemove
313307
* May be a `RuleSet` (most likely a `DeclarationBlock`), an `Import`,
314308
* a `Charset` or another `CSSList` (most likely a `MediaQuery`)
315309
*
316310
* @return bool whether the item was removed
317311
*/
318-
public function remove($itemToRemove): bool
312+
public function remove(CSSListItem $itemToRemove): bool
319313
{
320314
$key = \array_search($itemToRemove, $this->contents, true);
321315
if ($key !== false) {
@@ -329,12 +323,12 @@ public function remove($itemToRemove): bool
329323
/**
330324
* Replaces an item from the CSS list.
331325
*
332-
* @param RuleSet|Import|Charset|CSSList $oldItem
326+
* @param CSSListItem $oldItem
333327
* May be a `RuleSet` (most likely a `DeclarationBlock`), an `Import`, a `Charset`
334328
* or another `CSSList` (most likely a `MediaQuery`)
335-
* @param RuleSet|Import|Charset|CSSList|array<RuleSet|Import|Charset|CSSList> $newItem
329+
* @param CSSListItem|array<CSSListItem> $newItem
336330
*/
337-
public function replace($oldItem, $newItem): bool
331+
public function replace(CSSListItem $oldItem, $newItem): bool
338332
{
339333
$key = \array_search($oldItem, $this->contents, true);
340334
if ($key !== false) {
@@ -350,7 +344,7 @@ public function replace($oldItem, $newItem): bool
350344
}
351345

352346
/**
353-
* @param array<int, RuleSet|Import|Charset|CSSList> $contents
347+
* @param array<int, CSSListItem> $contents
354348
*/
355349
public function setContents(array $contents): void
356350
{
@@ -441,7 +435,7 @@ abstract public function isRootList(): bool;
441435
/**
442436
* Returns the stored items.
443437
*
444-
* @return array<int<0, max>, RuleSet|Import|Charset|CSSList>
438+
* @return array<int<0, max>, CSSListItem>
445439
*/
446440
public function getContents(): array
447441
{

src/CSSList/CSSListItem.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Sabberworm\CSS\CSSList;
6+
7+
use Sabberworm\CSS\Comment\Commentable;
8+
use Sabberworm\CSS\Renderable;
9+
10+
/**
11+
* Represents anything that can be in the `$contents` of a `CSSList`.
12+
*
13+
* The interface does not define any methods to implement.
14+
* It's purpose is to allow a single type to be specified for `CSSList::$contents` and manipulation methods thereof.
15+
* It extends `Commentable` and `Renderable` because all `CSSListItem`s are both.
16+
* This allows implementations to call methods from those interfaces without any additional type checks.
17+
*/
18+
interface CSSListItem extends Commentable, Renderable {}

src/Property/AtRule.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
namespace Sabberworm\CSS\Property;
66

77
use Sabberworm\CSS\Comment\Commentable;
8+
use Sabberworm\CSS\CSSList\CSSListItem;
89
use Sabberworm\CSS\Renderable;
910

10-
interface AtRule extends Renderable, Commentable
11+
interface AtRule extends Commentable, CSSListItem, Renderable
1112
{
1213
/**
1314
* Since there are more set rules than block rules,

src/RuleSet/DeclarationBlock.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,12 @@ class DeclarationBlock extends RuleSet
3030
private $selectors = [];
3131

3232
/**
33-
* @return DeclarationBlock|false
34-
*
3533
* @throws UnexpectedTokenException
3634
* @throws UnexpectedEOFException
3735
*
3836
* @internal since V8.8.0
3937
*/
40-
public static function parse(ParserState $parserState, ?CSSList $list = null)
38+
public static function parse(ParserState $parserState, ?CSSList $list = null): ?DeclarationBlock
4139
{
4240
$comments = [];
4341
$result = new DeclarationBlock($parserState->currentLine());
@@ -63,7 +61,7 @@ public static function parse(ParserState $parserState, ?CSSList $list = null)
6361
if (!$parserState->comes('}')) {
6462
$parserState->consumeUntil('}', false, true);
6563
}
66-
return false;
64+
return null;
6765
} else {
6866
throw $e;
6967
}

src/RuleSet/RuleSet.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Sabberworm\CSS\Comment\Comment;
88
use Sabberworm\CSS\Comment\Commentable;
9+
use Sabberworm\CSS\CSSList\CSSListItem;
910
use Sabberworm\CSS\OutputFormat;
1011
use Sabberworm\CSS\Parsing\ParserState;
1112
use Sabberworm\CSS\Parsing\UnexpectedEOFException;
@@ -22,7 +23,7 @@
2223
* If you want to manipulate a `RuleSet`, use the methods `addRule(Rule $rule)`, `getRules()` and `removeRule($rule)`
2324
* (which accepts either a `Rule` or a rule name; optionally suffixed by a dash to remove all related rules).
2425
*/
25-
abstract class RuleSet implements Renderable, Commentable
26+
abstract class RuleSet implements Commentable, CSSListItem, Renderable
2627
{
2728
/**
2829
* the rules in this rule set, using the property name as the key,

tests/CSSList/AtRuleBlockListTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Sabberworm\CSS\Tests\CSSList;
66

77
use PHPUnit\Framework\TestCase;
8+
use Sabberworm\CSS\CSSList\AtRuleBlockList;
89
use Sabberworm\CSS\Parser;
910
use Sabberworm\CSS\Settings;
1011

@@ -60,6 +61,7 @@ public function parsesRuleNameOfMediaQueries(string $css): void
6061
$contents = (new Parser($css))->parse()->getContents();
6162
$atRuleBlockList = $contents[0];
6263

64+
self::assertInstanceOf(AtRuleBlockList::class, $atRuleBlockList);
6365
self::assertSame('media', $atRuleBlockList->atRuleName());
6466
}
6567

@@ -73,6 +75,7 @@ public function parsesArgumentsOfMediaQueries(string $css): void
7375
$contents = (new Parser($css))->parse()->getContents();
7476
$atRuleBlockList = $contents[0];
7577

78+
self::assertInstanceOf(AtRuleBlockList::class, $atRuleBlockList);
7679
self::assertSame('(min-width: 768px)', $atRuleBlockList->atRuleArgs());
7780
}
7881

0 commit comments

Comments
 (0)