Skip to content

Commit ff7c17f

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 11214f0 commit ff7c17f

File tree

6 files changed

+154
-25
lines changed

6 files changed

+154
-25
lines changed

config/phpstan-baseline.neon

+12-6
Original file line numberDiff line numberDiff line change
@@ -48,22 +48,28 @@ parameters:
4848
count: 1
4949
path: ../src/RuleSet/DeclarationBlock.php
5050

51+
-
52+
message: '#^Parameters should have "Sabberworm\\CSS\\Rule\\Rule" types as the only types passed to this method$#'
53+
identifier: typePerfect.narrowPublicClassMethodParamType
54+
count: 2
55+
path: ../src/RuleSet/DeclarationBlock.php
56+
5157
-
5258
message: '#^Parameters should have "string" types as the only types passed to this method$#'
5359
identifier: typePerfect.narrowPublicClassMethodParamType
5460
count: 1
5561
path: ../src/RuleSet/DeclarationBlock.php
5662

5763
-
58-
message: '#^Only booleans are allowed in a negated boolean, string\|null given\.$#'
59-
identifier: booleanNot.exprNotBoolean
64+
message: '#^Parameters should have "string\|null" types as the only types passed to this method$#'
65+
identifier: typePerfect.narrowPublicClassMethodParamType
6066
count: 2
61-
path: ../src/RuleSet/RuleSet.php
67+
path: ../src/RuleSet/DeclarationBlock.php
6268

6369
-
64-
message: '#^Parameters should have "string" types as the only types passed to this method$#'
65-
identifier: typePerfect.narrowPublicClassMethodParamType
66-
count: 1
70+
message: '#^Only booleans are allowed in a negated boolean, string\|null given\.$#'
71+
identifier: booleanNot.exprNotBoolean
72+
count: 2
6773
path: ../src/RuleSet/RuleSet.php
6874

6975
-

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/RuleSet/DeclarationBlock.php

+118-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,43 @@
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+
* @param int<0, max> $lineNumber
48+
*/
49+
public function __construct(int $lineNumber = 0)
50+
{
51+
$this->lineNumber = $lineNumber;
52+
$this->ruleSet = new RuleSet($lineNumber);
53+
}
54+
3255
/**
3356
* @throws UnexpectedTokenException
3457
* @throws UnexpectedEOFException
@@ -67,10 +90,20 @@ public static function parse(ParserState $parserState, ?CSSList $list = null): ?
6790
}
6891
}
6992
$result->setComments($comments);
70-
RuleSet::parseRuleSet($parserState, $result);
93+
94+
RuleSet::parseRuleSet($parserState, $result->ruleSet);
95+
7196
return $result;
7297
}
7398

99+
/**
100+
* @return int<0, max>
101+
*/
102+
public function getLineNo(): int
103+
{
104+
return $this->lineNumber;
105+
}
106+
74107
/**
75108
* @param array<Selector|string>|string $selectors
76109
*
@@ -135,6 +168,63 @@ public function getSelectors(): array
135168
return $this->selectors;
136169
}
137170

171+
public function getRuleSet(): RuleSet
172+
{
173+
return $this->ruleSet;
174+
}
175+
176+
/**
177+
* @see RuleSet::addRule()
178+
*/
179+
public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void
180+
{
181+
$this->ruleSet->addRule($ruleToAdd, $sibling);
182+
}
183+
184+
/**
185+
* @see RuleSet::getRules()
186+
*
187+
* @param Rule|string|null $searchPattern
188+
*
189+
* @return array<int<0, max>, Rule>
190+
*/
191+
public function getRules($searchPattern = null): array
192+
{
193+
return $this->ruleSet->getRules($searchPattern);
194+
}
195+
196+
/**
197+
* @see RuleSet::setRules()
198+
*
199+
* @param array<Rule> $rules
200+
*/
201+
public function setRules(array $rules): void
202+
{
203+
$this->ruleSet->setRules($rules);
204+
}
205+
206+
/**
207+
* @see RuleSet::getRulesAssoc()
208+
*
209+
* @param Rule|string|null $searchPattern
210+
*
211+
* @return array<string, Rule>
212+
*/
213+
public function getRulesAssoc($searchPattern = null): array
214+
{
215+
return $this->ruleSet->getRulesAssoc($searchPattern);
216+
}
217+
218+
/**
219+
* @see RuleSet::removeRule()
220+
*
221+
* @param Rule|string|null $searchPattern
222+
*/
223+
public function removeRule($searchPattern): void
224+
{
225+
$this->ruleSet->removeRule($searchPattern);
226+
}
227+
138228
/**
139229
* @return non-empty-string
140230
*
@@ -155,10 +245,34 @@ public function render(OutputFormat $outputFormat): string
155245
);
156246
$result .= $outputFormat->getContentAfterDeclarationBlockSelectors();
157247
$result .= $formatter->spaceBeforeOpeningBrace() . '{';
158-
$result .= $this->renderRules($outputFormat);
248+
$result .= $this->ruleSet->render($outputFormat);
159249
$result .= '}';
160250
$result .= $outputFormat->getContentAfterDeclarationBlock();
161251

162252
return $result;
163253
}
254+
255+
/**
256+
* @param list<Comment> $comments
257+
*/
258+
public function addComments(array $comments): void
259+
{
260+
$this->comments = \array_merge($this->comments, $comments);
261+
}
262+
263+
/**
264+
* @return list<Comment>
265+
*/
266+
public function getComments(): array
267+
{
268+
return $this->comments;
269+
}
270+
271+
/**
272+
* @param list<Comment> $comments
273+
*/
274+
public function setComments(array $comments): void
275+
{
276+
$this->comments = $comments;
277+
}
164278
}

src/RuleSet/RuleSet.php

+9-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
* Note that `CSSListItem` extends both `Commentable` and `Renderable`,
2626
* so those interfaces must also be implemented by concrete subclasses.
2727
*/
28-
abstract class RuleSet implements CSSListItem
28+
class RuleSet implements CSSListItem
2929
{
3030
use CommentContainer;
3131

@@ -263,6 +263,14 @@ public function removeRule($searchPattern): void
263263
}
264264
}
265265

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

tests/ParserTest.php

+5-6
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
use Sabberworm\CSS\Property\Selector;
2222
use Sabberworm\CSS\RuleSet\AtRuleSet;
2323
use Sabberworm\CSS\RuleSet\DeclarationBlock;
24-
use Sabberworm\CSS\RuleSet\RuleSet;
2524
use Sabberworm\CSS\Settings;
2625
use Sabberworm\CSS\Value\CalcFunction;
2726
use Sabberworm\CSS\Value\Color;
@@ -37,7 +36,7 @@ final class ParserTest extends TestCase
3736
/**
3837
* @test
3938
*/
40-
public function parseForOneRuleSetReturnsDocumentWithOneRuleSet(): void
39+
public function parseForOneDeclarationBlockReturnsDocumentWithOneDeclarationBlock(): void
4140
{
4241
$css = '.thing { left: 10px; }';
4342
$parser = new Parser($css);
@@ -48,7 +47,7 @@ public function parseForOneRuleSetReturnsDocumentWithOneRuleSet(): void
4847

4948
$cssList = $document->getContents();
5049
self::assertCount(1, $cssList);
51-
self::assertInstanceOf(RuleSet::class, $cssList[0]);
50+
self::assertInstanceOf(DeclarationBlock::class, $cssList[0]);
5251
}
5352

5453
/**
@@ -928,9 +927,9 @@ public function missingPropertyValueStrict(): void
928927
public function missingPropertyValueLenient(): void
929928
{
930929
$parsed = self::parsedStructureForFile('missing-property-value', Settings::create()->withLenientParsing(true));
931-
$rulesets = $parsed->getAllRuleSets();
932-
self::assertCount(1, $rulesets);
933-
$block = $rulesets[0];
930+
$declarationBlocks = $parsed->getAllDeclarationBlocks();
931+
self::assertCount(1, $declarationBlocks);
932+
$block = $declarationBlocks[0];
934933
self::assertInstanceOf(DeclarationBlock::class, $block);
935934
self::assertEquals([new Selector('div')], $block->getSelectors());
936935
$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)