Skip to content

Commit 34c8708

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 8c4a77e commit 34c8708

File tree

9 files changed

+190
-29
lines changed

9 files changed

+190
-29
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ Please also have a look at our
2121

2222
### Changed
2323

24+
- `DeclarationBlock` no longer extends `RuleSet` and instead has a `RuleSet` as
25+
a property; use `getRuleSet()` to access it directly (#1194)
2426
- The default line (and column) number is now `null` (not zero) (#1288)
2527
- `setPosition()` (in `Rule` and other classes) now has fluent interface,
2628
returning itself (#1259)

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -753,7 +753,11 @@ classDiagram
753753
CSSFunction <|-- Color: inheritance
754754
Positionable <|.. Comment: realization
755755
Renderable <|.. Comment: realization
756-
RuleSet <|-- DeclarationBlock: inheritance
756+
CSSElement <|.. DeclarationBlock: realization
757+
CSSListItem <|.. DeclarationBlock: realization
758+
Positionable <|.. DeclarationBlock: realization
759+
RuleContainer <|.. DeclarationBlock: realization
760+
DeclarationBlock ..> RuleSet : dependency
757761
DeclarationBlock ..> Selector: dependency
758762
CSSBlockList <|-- Document: inheritance
759763
AtRule <|.. Import: realization

src/CSSList/CSSBlockList.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ public function getAllRuleSets(): array
5656
$result[] = $item;
5757
} elseif ($item instanceof CSSBlockList) {
5858
$result = \array_merge($result, $item->getAllRuleSets());
59+
} elseif ($item instanceof DeclarationBlock) {
60+
$result[] = $item->getRuleSet();
5961
}
6062
}
6163

src/RuleSet/DeclarationBlock.php

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,21 @@
44

55
namespace Sabberworm\CSS\RuleSet;
66

7+
use Sabberworm\CSS\Comment\CommentContainer;
8+
use Sabberworm\CSS\CSSElement;
79
use Sabberworm\CSS\CSSList\CSSList;
10+
use Sabberworm\CSS\CSSList\CSSListItem;
811
use Sabberworm\CSS\CSSList\KeyFrame;
912
use Sabberworm\CSS\OutputFormat;
1013
use Sabberworm\CSS\Parsing\OutputException;
1114
use Sabberworm\CSS\Parsing\ParserState;
1215
use Sabberworm\CSS\Parsing\UnexpectedEOFException;
1316
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
17+
use Sabberworm\CSS\Position\Position;
18+
use Sabberworm\CSS\Position\Positionable;
1419
use Sabberworm\CSS\Property\KeyframeSelector;
1520
use Sabberworm\CSS\Property\Selector;
21+
use Sabberworm\CSS\Rule\Rule;
1622

1723
/**
1824
* This class represents a `RuleSet` constrained by a `Selector`.
@@ -21,14 +27,33 @@
2127
* matching elements.
2228
*
2329
* Declaration blocks usually appear directly inside a `Document` or another `CSSList` (mostly a `MediaQuery`).
30+
*
31+
* Note that `CSSListItem` extends both `Commentable` and `Renderable`, so those interfaces must also be implemented.
2432
*/
25-
class DeclarationBlock extends RuleSet
33+
class DeclarationBlock implements CSSElement, CSSListItem, Positionable, RuleContainer
2634
{
35+
use CommentContainer;
36+
use Position;
37+
2738
/**
2839
* @var array<Selector|string>
2940
*/
3041
private $selectors = [];
3142

43+
/**
44+
* @var RuleSet
45+
*/
46+
private $ruleSet;
47+
48+
/**
49+
* @param int<1, max>|null $lineNumber
50+
*/
51+
public function __construct(?int $lineNumber = null)
52+
{
53+
$this->ruleSet = new RuleSet($lineNumber);
54+
$this->setPosition($lineNumber);
55+
}
56+
3257
/**
3358
* @throws UnexpectedTokenException
3459
* @throws UnexpectedEOFException
@@ -107,7 +132,9 @@ public static function parse(ParserState $parserState, ?CSSList $list = null): ?
107132
}
108133
}
109134
$result->setComments($comments);
110-
RuleSet::parseRuleSet($parserState, $result);
135+
136+
RuleSet::parseRuleSet($parserState, $result->ruleSet);
137+
111138
return $result;
112139
}
113140

@@ -175,6 +202,73 @@ public function getSelectors(): array
175202
return $this->selectors;
176203
}
177204

205+
public function getRuleSet(): RuleSet
206+
{
207+
return $this->ruleSet;
208+
}
209+
210+
/**
211+
* @see RuleSet::addRule()
212+
*/
213+
public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void
214+
{
215+
$this->ruleSet->addRule($ruleToAdd, $sibling);
216+
}
217+
218+
/**
219+
* @see RuleSet::getRules()
220+
*
221+
* @return array<int<0, max>, Rule>
222+
*/
223+
public function getRules(?string $searchPattern = null): array
224+
{
225+
return $this->ruleSet->getRules($searchPattern);
226+
}
227+
228+
/**
229+
* @see RuleSet::setRules()
230+
*
231+
* @param array<Rule> $rules
232+
*/
233+
public function setRules(array $rules): void
234+
{
235+
$this->ruleSet->setRules($rules);
236+
}
237+
238+
/**
239+
* @see RuleSet::getRulesAssoc()
240+
*
241+
* @return array<string, Rule>
242+
*/
243+
public function getRulesAssoc(?string $searchPattern = null): array
244+
{
245+
return $this->ruleSet->getRulesAssoc($searchPattern);
246+
}
247+
248+
/**
249+
* @see RuleSet::removeRule()
250+
*/
251+
public function removeRule(Rule $ruleToRemove): void
252+
{
253+
$this->ruleSet->removeRule($ruleToRemove);
254+
}
255+
256+
/**
257+
* @see RuleSet::removeMatchingRules()
258+
*/
259+
public function removeMatchingRules(string $searchPattern): void
260+
{
261+
$this->ruleSet->removeMatchingRules($searchPattern);
262+
}
263+
264+
/**
265+
* @see RuleSet::removeAllRules()
266+
*/
267+
public function removeAllRules(): void
268+
{
269+
$this->ruleSet->removeAllRules();
270+
}
271+
178272
/**
179273
* @return non-empty-string
180274
*
@@ -198,7 +292,7 @@ public function render(OutputFormat $outputFormat): string
198292
);
199293
$result .= $outputFormat->getContentAfterDeclarationBlockSelectors();
200294
$result .= $formatter->spaceBeforeOpeningBrace() . '{';
201-
$result .= $this->renderRules($outputFormat);
295+
$result .= $this->ruleSet->render($outputFormat);
202296
$result .= '}';
203297
$result .= $outputFormat->getContentAfterDeclarationBlock();
204298

src/RuleSet/RuleSet.php

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,9 @@
2424
* If you want to manipulate a `RuleSet`, use the methods `addRule(Rule $rule)`, `getRules()` and `removeRule($rule)`
2525
* (which accepts either a `Rule` or a rule name; optionally suffixed by a dash to remove all related rules).
2626
*
27-
* Note that `CSSListItem` extends both `Commentable` and `Renderable`,
28-
* so those interfaces must also be implemented by concrete subclasses.
27+
* Note that `CSSListItem` extends both `Commentable` and `Renderable`, so those interfaces must also be implemented.
2928
*/
30-
abstract class RuleSet implements CSSElement, CSSListItem, Positionable, RuleContainer
29+
class RuleSet implements CSSElement, CSSListItem, Positionable, RuleContainer
3130
{
3231
use CommentContainer;
3332
use Position;
@@ -293,6 +292,14 @@ public function removeAllRules(): void
293292
$this->rules = [];
294293
}
295294

295+
/**
296+
* @internal
297+
*/
298+
public function render(OutputFormat $outputFormat): string
299+
{
300+
return $this->renderRules($outputFormat);
301+
}
302+
296303
protected function renderRules(OutputFormat $outputFormat): string
297304
{
298305
$result = '';

tests/ParserTest.php

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ final class ParserTest extends TestCase
3838
/**
3939
* @test
4040
*/
41-
public function parseForOneRuleSetReturnsDocumentWithOneRuleSet(): void
41+
public function parseForOneDeclarationBlockReturnsDocumentWithOneDeclarationBlock(): void
4242
{
4343
$css = '.thing { left: 10px; }';
4444
$parser = new Parser($css);
@@ -49,7 +49,7 @@ public function parseForOneRuleSetReturnsDocumentWithOneRuleSet(): void
4949

5050
$cssList = $document->getContents();
5151
self::assertCount(1, $cssList);
52-
self::assertInstanceOf(RuleSet::class, $cssList[0]);
52+
self::assertInstanceOf(DeclarationBlock::class, $cssList[0]);
5353
}
5454

5555
/**
@@ -929,9 +929,9 @@ public function missingPropertyValueStrict(): void
929929
public function missingPropertyValueLenient(): void
930930
{
931931
$parsed = self::parsedStructureForFile('missing-property-value', Settings::create()->withLenientParsing(true));
932-
$rulesets = $parsed->getAllRuleSets();
933-
self::assertCount(1, $rulesets);
934-
$block = $rulesets[0];
932+
$declarationBlocks = $parsed->getAllDeclarationBlocks();
933+
self::assertCount(1, $declarationBlocks);
934+
$block = $declarationBlocks[0];
935935
self::assertInstanceOf(DeclarationBlock::class, $block);
936936
self::assertEquals([new Selector('div')], $block->getSelectors());
937937
$rules = $block->getRules();
@@ -1058,7 +1058,7 @@ public function commentExtracting(): void
10581058
// $this->assertSame("* Number 5 *", $fooBarBlockComments[1]->getComment());
10591059

10601060
// Declaration rules.
1061-
self::assertInstanceOf(RuleSet::class, $fooBarBlock);
1061+
self::assertInstanceOf(DeclarationBlock::class, $fooBarBlock);
10621062
$fooBarRules = $fooBarBlock->getRules();
10631063
$fooBarRule = $fooBarRules[0];
10641064
$fooBarRuleComments = $fooBarRule->getComments();
@@ -1079,7 +1079,7 @@ public function commentExtracting(): void
10791079
self::assertSame('* Number 10 *', $fooBarComments[0]->getComment());
10801080

10811081
// Media -> declaration -> rule.
1082-
self::assertInstanceOf(RuleSet::class, $mediaRules[0]);
1082+
self::assertInstanceOf(DeclarationBlock::class, $mediaRules[0]);
10831083
$fooBarRules = $mediaRules[0]->getRules();
10841084
$fooBarChildComments = $fooBarRules[0]->getComments();
10851085
self::assertCount(1, $fooBarChildComments);
@@ -1095,7 +1095,7 @@ public function flatCommentExtractingOneComment(): void
10951095
$document = $parser->parse();
10961096

10971097
$contents = $document->getContents();
1098-
self::assertInstanceOf(RuleSet::class, $contents[0]);
1098+
self::assertInstanceOf(DeclarationBlock::class, $contents[0]);
10991099
$divRules = $contents[0]->getRules();
11001100
$comments = $divRules[0]->getComments();
11011101

@@ -1112,7 +1112,7 @@ public function flatCommentExtractingTwoConjoinedCommentsForOneRule(): void
11121112
$document = $parser->parse();
11131113

11141114
$contents = $document->getContents();
1115-
self::assertInstanceOf(RuleSet::class, $contents[0]);
1115+
self::assertInstanceOf(DeclarationBlock::class, $contents[0]);
11161116
$divRules = $contents[0]->getRules();
11171117
$comments = $divRules[0]->getComments();
11181118

@@ -1130,7 +1130,7 @@ public function flatCommentExtractingTwoSpaceSeparatedCommentsForOneRule(): void
11301130
$document = $parser->parse();
11311131

11321132
$contents = $document->getContents();
1133-
self::assertInstanceOf(RuleSet::class, $contents[0]);
1133+
self::assertInstanceOf(DeclarationBlock::class, $contents[0]);
11341134
$divRules = $contents[0]->getRules();
11351135
$comments = $divRules[0]->getComments();
11361136

@@ -1148,7 +1148,7 @@ public function flatCommentExtractingCommentsForTwoRules(): void
11481148
$document = $parser->parse();
11491149

11501150
$contents = $document->getContents();
1151-
self::assertInstanceOf(RuleSet::class, $contents[0]);
1151+
self::assertInstanceOf(DeclarationBlock::class, $contents[0]);
11521152
$divRules = $contents[0]->getRules();
11531153
$rule1Comments = $divRules[0]->getComments();
11541154
$rule2Comments = $divRules[1]->getComments();

tests/RuleSet/DeclarationBlockTest.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,12 @@
88
use Sabberworm\CSS\OutputFormat;
99
use Sabberworm\CSS\Parser;
1010
use Sabberworm\CSS\Rule\Rule;
11-
use Sabberworm\CSS\RuleSet\RuleSet;
11+
use Sabberworm\CSS\RuleSet\DeclarationBlock;
1212
use Sabberworm\CSS\Settings as ParserSettings;
1313
use Sabberworm\CSS\Value\Size;
1414

1515
/**
1616
* @covers \Sabberworm\CSS\RuleSet\DeclarationBlock
17-
* @covers \Sabberworm\CSS\RuleSet\RuleSet
1817
*/
1918
final class DeclarationBlockTest extends TestCase
2019
{
@@ -31,7 +30,7 @@ public function overrideRules(): void
3130
$contents = $document->getContents();
3231
$wrapper = $contents[0];
3332

34-
self::assertInstanceOf(RuleSet::class, $wrapper);
33+
self::assertInstanceOf(DeclarationBlock::class, $wrapper);
3534
self::assertCount(2, $wrapper->getRules());
3635
$wrapper->setRules([$rule]);
3736

@@ -52,7 +51,7 @@ public function ruleInsertion(): void
5251
$contents = $document->getContents();
5352
$wrapper = $contents[0];
5453

55-
self::assertInstanceOf(RuleSet::class, $wrapper);
54+
self::assertInstanceOf(DeclarationBlock::class, $wrapper);
5655

5756
$leftRules = $wrapper->getRules('left');
5857
self::assertCount(1, $leftRules);

tests/Unit/CSSList/CSSBlockListTest.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ public function getAllRuleSetsWhenNoContentSetReturnsEmptyArray(): void
157157
/**
158158
* @test
159159
*/
160-
public function getAllRuleSetsReturnsOneDeclarationBlockDirectlySetAsContent(): void
160+
public function getAllRuleSetsReturnsRuleSetFromOneDeclarationBlockDirectlySetAsContent(): void
161161
{
162162
$subject = new ConcreteCSSBlockList();
163163

@@ -166,7 +166,7 @@ public function getAllRuleSetsReturnsOneDeclarationBlockDirectlySetAsContent():
166166

167167
$result = $subject->getAllRuleSets();
168168

169-
self::assertSame([$declarationBlock], $result);
169+
self::assertSame([$declarationBlock->getRuleSet()], $result);
170170
}
171171

172172
/**
@@ -187,7 +187,7 @@ public function getAllRuleSetsReturnsOneAtRuleSetDirectlySetAsContent(): void
187187
/**
188188
* @test
189189
*/
190-
public function getAllRuleSetsReturnsMultipleDeclarationBlocksDirectlySetAsContents(): void
190+
public function getAllRuleSetsReturnsRuleSetsFromMultipleDeclarationBlocksDirectlySetAsContents(): void
191191
{
192192
$subject = new ConcreteCSSBlockList();
193193

@@ -197,7 +197,7 @@ public function getAllRuleSetsReturnsMultipleDeclarationBlocksDirectlySetAsConte
197197

198198
$result = $subject->getAllRuleSets();
199199

200-
self::assertSame([$declarationBlock1, $declarationBlock2], $result);
200+
self::assertSame([$declarationBlock1->getRuleSet(), $declarationBlock2->getRuleSet()], $result);
201201
}
202202

203203
/**
@@ -219,7 +219,7 @@ public function getAllRuleSetsReturnsMultipleAtRuleSetsDirectlySetAsContents():
219219
/**
220220
* @test
221221
*/
222-
public function getAllRuleSetsReturnsDeclarationBlocksWithinAtRuleBlockList(): void
222+
public function getAllRuleSetsReturnsRuleSetsFromDeclarationBlocksWithinAtRuleBlockList(): void
223223
{
224224
$subject = new ConcreteCSSBlockList();
225225

@@ -230,7 +230,7 @@ public function getAllRuleSetsReturnsDeclarationBlocksWithinAtRuleBlockList(): v
230230

231231
$result = $subject->getAllRuleSets();
232232

233-
self::assertSame([$declarationBlock], $result);
233+
self::assertSame([$declarationBlock->getRuleSet()], $result);
234234
}
235235

236236
/**

0 commit comments

Comments
 (0)