Skip to content

Commit 73956e9

Browse files
committed
[TASK] Use delegation for DeclarationBlock -> RuleSet
... rather than inheritance. This will allow `DeclarationBlock` to instead extend `CSSBlockList` in order to support [CSS nesting](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting). This is a slightly-breaking change, since now `CSSBlockList::getAllRuleSets()` will include the `RuleSet` property of the `DeclarationBlock` instead of the `DeclarationBlock` itself. Part of #1170.
1 parent 7e80f08 commit 73956e9

File tree

7 files changed

+160
-32
lines changed

7 files changed

+160
-32
lines changed

config/phpstan-baseline.neon

+12-12
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,24 @@ parameters:
4242
count: 1
4343
path: ../src/RuleSet/DeclarationBlock.php
4444

45+
-
46+
message: '#^Parameters should have "Sabberworm\\CSS\\Rule\\Rule" types as the only types passed to this method$#'
47+
identifier: typePerfect.narrowPublicClassMethodParamType
48+
count: 2
49+
path: ../src/RuleSet/DeclarationBlock.php
50+
4551
-
4652
message: '#^Parameters should have "string" types as the only types passed to this method$#'
4753
identifier: typePerfect.narrowPublicClassMethodParamType
4854
count: 1
4955
path: ../src/RuleSet/DeclarationBlock.php
5056

57+
-
58+
message: '#^Parameters should have "string\|null" types as the only types passed to this method$#'
59+
identifier: typePerfect.narrowPublicClassMethodParamType
60+
count: 2
61+
path: ../src/RuleSet/DeclarationBlock.php
62+
5163
-
5264
message: '#^Returning false in non return bool class method\. Use null with type\|null instead or add bool return type$#'
5365
identifier: typePerfect.nullOverFalse
@@ -60,18 +72,6 @@ parameters:
6072
count: 2
6173
path: ../src/RuleSet/RuleSet.php
6274

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-
69-
-
70-
message: '#^Parameters should have "string" types as the only types passed to this method$#'
71-
identifier: typePerfect.narrowPublicClassMethodParamType
72-
count: 1
73-
path: ../src/RuleSet/RuleSet.php
74-
7575
-
7676
message: '#^Loose comparison via "\!\=" is not allowed\.$#'
7777
identifier: notEqual.notAllowed

src/CSSList/CSSBlockList.php

+4-2
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,16 @@ public function getAllRuleSets(): array
6868
$result[] = $item;
6969
} elseif ($item instanceof CSSBlockList) {
7070
$result = \array_merge($result, $item->getAllRuleSets());
71+
} elseif ($item instanceof DeclarationBlock) {
72+
$result[] = $item->getRuleSet();
7173
}
7274
}
7375

7476
return $result;
7577
}
7678

7779
/**
78-
* @param CSSList|Rule|RuleSet|Value $element
80+
* @param CSSList|DeclarationBlock|Rule|RuleSet|Value $element
7981
* @param list<Value> $result
8082
*/
8183
protected function allValues(
@@ -88,7 +90,7 @@ protected function allValues(
8890
foreach ($element->getContents() as $content) {
8991
$this->allValues($content, $result, $searchString, $searchInFunctionArguments);
9092
}
91-
} elseif ($element instanceof RuleSet) {
93+
} elseif ($element instanceof RuleSet || $element instanceof DeclarationBlock) {
9294
foreach ($element->getRules($searchString) as $rule) {
9395
$this->allValues($rule, $result, $searchString, $searchInFunctionArguments);
9496
}

src/CSSList/CSSList.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -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>, RuleSet|CSSList|DeclarationBlock|Import|Charset>
4545
*
4646
* @internal since 8.8.0
4747
*/

src/RuleSet/DeclarationBlock.php

+123-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace Sabberworm\CSS\RuleSet;
66

7+
use Sabberworm\CSS\Comment\Comment;
8+
use Sabberworm\CSS\Comment\Commentable;
79
use Sabberworm\CSS\CSSList\CSSList;
810
use Sabberworm\CSS\CSSList\KeyFrame;
911
use Sabberworm\CSS\OutputFormat;
@@ -13,22 +15,48 @@
1315
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
1416
use Sabberworm\CSS\Property\KeyframeSelector;
1517
use Sabberworm\CSS\Property\Selector;
18+
use Sabberworm\CSS\Renderable;
19+
use Sabberworm\CSS\Rule\Rule;
1620

1721
/**
18-
* This class represents a `RuleSet` constrained by a `Selector`.
22+
* This class includes a `RuleSet` constrained by a `Selector`.
1923
*
2024
* It contains an array of selector objects (comma-separated in the CSS) as well as the rules to be applied to the
2125
* matching elements.
2226
*
2327
* Declaration blocks usually appear directly inside a `Document` or another `CSSList` (mostly a `MediaQuery`).
2428
*/
25-
class DeclarationBlock extends RuleSet
29+
class DeclarationBlock implements Commentable, Renderable
2630
{
2731
/**
2832
* @var array<Selector|string>
2933
*/
3034
private $selectors = [];
3135

36+
/**
37+
* @var RuleSet
38+
*/
39+
private $ruleSet;
40+
41+
/**
42+
* @var int<0, max>
43+
*/
44+
private $lineNumber;
45+
46+
/**
47+
* @var list<Comment>
48+
*/
49+
private $comments = [];
50+
51+
/**
52+
* @param int<0, max> $lineNumber
53+
*/
54+
public function __construct(int $lineNumber = 0)
55+
{
56+
$this->lineNumber = $lineNumber;
57+
$this->ruleSet = new RuleSet($lineNumber);
58+
}
59+
3260
/**
3361
* @return DeclarationBlock|false
3462
*
@@ -69,10 +97,20 @@ public static function parse(ParserState $parserState, ?CSSList $list = null)
6997
}
7098
}
7199
$result->setComments($comments);
72-
RuleSet::parseRuleSet($parserState, $result);
100+
101+
RuleSet::parseRuleSet($parserState, $result->ruleSet);
102+
73103
return $result;
74104
}
75105

106+
/**
107+
* @return int<0, max>
108+
*/
109+
public function getLineNo(): int
110+
{
111+
return $this->lineNumber;
112+
}
113+
76114
/**
77115
* @param array<Selector|string>|string $selectors
78116
*
@@ -137,6 +175,63 @@ public function getSelectors(): array
137175
return $this->selectors;
138176
}
139177

178+
public function getRuleSet(): RuleSet
179+
{
180+
return $this->ruleSet;
181+
}
182+
183+
/**
184+
* @see RuleSet::addRule()
185+
*/
186+
public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void
187+
{
188+
$this->ruleSet->addRule($ruleToAdd, $sibling);
189+
}
190+
191+
/**
192+
* @see RuleSet::getRules()
193+
*
194+
* @param Rule|string|null $searchPattern
195+
*
196+
* @return array<int<0, max>, Rule>
197+
*/
198+
public function getRules($searchPattern = null): array
199+
{
200+
return $this->ruleSet->getRules($searchPattern);
201+
}
202+
203+
/**
204+
* @see RuleSet::setRules()
205+
*
206+
* @param array<Rule> $rules
207+
*/
208+
public function setRules(array $rules): void
209+
{
210+
$this->ruleSet->setRules($rules);
211+
}
212+
213+
/**
214+
* @see RuleSet::getRulesAssoc()
215+
*
216+
* @param Rule|string|null $searchPattern
217+
*
218+
* @return array<string, Rule>
219+
*/
220+
public function getRulesAssoc($searchPattern = null): array
221+
{
222+
return $this->ruleSet->getRulesAssoc($searchPattern);
223+
}
224+
225+
/**
226+
* @see RuleSet::removeRule()
227+
*
228+
* @param Rule|string|null $searchPattern
229+
*/
230+
public function removeRule($searchPattern): void
231+
{
232+
$this->ruleSet->removeRule($searchPattern);
233+
}
234+
140235
/**
141236
* @throws OutputException
142237
*/
@@ -155,10 +250,34 @@ public function render(OutputFormat $outputFormat): string
155250
);
156251
$result .= $outputFormat->getContentAfterDeclarationBlockSelectors();
157252
$result .= $formatter->spaceBeforeOpeningBrace() . '{';
158-
$result .= $this->renderRules($outputFormat);
253+
$result .= $this->ruleSet->render($outputFormat);
159254
$result .= '}';
160255
$result .= $outputFormat->getContentAfterDeclarationBlock();
161256

162257
return $result;
163258
}
259+
260+
/**
261+
* @param list<Comment> $comments
262+
*/
263+
public function addComments(array $comments): void
264+
{
265+
$this->comments = \array_merge($this->comments, $comments);
266+
}
267+
268+
/**
269+
* @return list<Comment>
270+
*/
271+
public function getComments(): array
272+
{
273+
return $this->comments;
274+
}
275+
276+
/**
277+
* @param list<Comment> $comments
278+
*/
279+
public function setComments(array $comments): void
280+
{
281+
$this->comments = $comments;
282+
}
164283
}

src/RuleSet/RuleSet.php

+9-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
* If you want to manipulate a `RuleSet`, use the methods `addRule(Rule $rule)`, `getRules()` and `removeRule($rule)`
2323
* (which accepts either a `Rule` or a rule name; optionally suffixed by a dash to remove all related rules).
2424
*/
25-
abstract class RuleSet implements Renderable, Commentable
25+
class RuleSet implements Renderable, Commentable
2626
{
2727
/**
2828
* the rules in this rule set, using the property name as the key,
@@ -265,6 +265,14 @@ public function removeRule($searchPattern): void
265265
}
266266
}
267267

268+
/**
269+
* @internal
270+
*/
271+
public function render(OutputFormat $outputFormat): string
272+
{
273+
return $this->renderRules($outputFormat);
274+
}
275+
268276
protected function renderRules(OutputFormat $outputFormat): string
269277
{
270278
$result = '';

tests/ParserTest.php

+5-6
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
use Sabberworm\CSS\Property\Selector;
2020
use Sabberworm\CSS\RuleSet\AtRuleSet;
2121
use Sabberworm\CSS\RuleSet\DeclarationBlock;
22-
use Sabberworm\CSS\RuleSet\RuleSet;
2322
use Sabberworm\CSS\Settings;
2423
use Sabberworm\CSS\Value\CalcFunction;
2524
use Sabberworm\CSS\Value\Color;
@@ -35,7 +34,7 @@ final class ParserTest extends TestCase
3534
/**
3635
* @test
3736
*/
38-
public function parseForOneRuleSetReturnsDocumentWithOneRuleSet(): void
37+
public function parseForOneDeclarationBlockReturnsDocumentWithOneDeclarationBlock(): void
3938
{
4039
$css = '.thing { left: 10px; }';
4140
$parser = new Parser($css);
@@ -46,7 +45,7 @@ public function parseForOneRuleSetReturnsDocumentWithOneRuleSet(): void
4645

4746
$cssList = $document->getContents();
4847
self::assertCount(1, $cssList);
49-
self::assertInstanceOf(RuleSet::class, $cssList[0]);
48+
self::assertInstanceOf(DeclarationBlock::class, $cssList[0]);
5049
}
5150

5251
/**
@@ -926,9 +925,9 @@ public function missingPropertyValueStrict(): void
926925
public function missingPropertyValueLenient(): void
927926
{
928927
$parsed = self::parsedStructureForFile('missing-property-value', Settings::create()->withLenientParsing(true));
929-
$rulesets = $parsed->getAllRuleSets();
930-
self::assertCount(1, $rulesets);
931-
$block = $rulesets[0];
928+
$declarationBlocks = $parsed->getAllDeclarationBlocks();
929+
self::assertCount(1, $declarationBlocks);
930+
$block = $declarationBlocks[0];
932931
self::assertInstanceOf(DeclarationBlock::class, $block);
933932
self::assertEquals([new Selector('div')], $block->getSelectors());
934933
$rules = $block->getRules();

tests/Unit/CSSList/CSSBlockListTest.php

+6-6
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ public function getAllRuleSetsWhenNoContentSetReturnsEmptyArray(): void
154154
/**
155155
* @test
156156
*/
157-
public function getAllRuleSetsReturnsOneDeclarationBlockDirectlySetAsContent(): void
157+
public function getAllRuleSetsReturnsRuleSetFromOneDeclarationBlockDirectlySetAsContent(): void
158158
{
159159
$subject = new ConcreteCSSBlockList();
160160

@@ -163,7 +163,7 @@ public function getAllRuleSetsReturnsOneDeclarationBlockDirectlySetAsContent():
163163

164164
$result = $subject->getAllRuleSets();
165165

166-
self::assertSame([$declarationBlock], $result);
166+
self::assertSame([$declarationBlock->getRuleSet()], $result);
167167
}
168168

169169
/**
@@ -184,7 +184,7 @@ public function getAllRuleSetsReturnsOneAtRuleSetDirectlySetAsContent(): void
184184
/**
185185
* @test
186186
*/
187-
public function getAllRuleSetsReturnsMultipleDeclarationBlocksDirectlySetAsContents(): void
187+
public function getAllRuleSetsReturnsRuleSetsFromMultipleDeclarationBlocksDirectlySetAsContents(): void
188188
{
189189
$subject = new ConcreteCSSBlockList();
190190

@@ -194,7 +194,7 @@ public function getAllRuleSetsReturnsMultipleDeclarationBlocksDirectlySetAsConte
194194

195195
$result = $subject->getAllRuleSets();
196196

197-
self::assertSame([$declarationBlock1, $declarationBlock2], $result);
197+
self::assertSame([$declarationBlock1->getRuleSet(), $declarationBlock2->getRuleSet()], $result);
198198
}
199199

200200
/**
@@ -216,7 +216,7 @@ public function getAllRuleSetsReturnsMultipleAtRuleSetsDirectlySetAsContents():
216216
/**
217217
* @test
218218
*/
219-
public function getAllRuleSetsReturnsDeclarationBlocksWithinAtRuleBlockList(): void
219+
public function getAllRuleSetsReturnsRuleSetsFromDeclarationBlocksWithinAtRuleBlockList(): void
220220
{
221221
$subject = new ConcreteCSSBlockList();
222222

@@ -227,7 +227,7 @@ public function getAllRuleSetsReturnsDeclarationBlocksWithinAtRuleBlockList(): v
227227

228228
$result = $subject->getAllRuleSets();
229229

230-
self::assertSame([$declarationBlock], $result);
230+
self::assertSame([$declarationBlock->getRuleSet()], $result);
231231
}
232232

233233
/**

0 commit comments

Comments
 (0)