Skip to content

Commit dbb2627

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 afdca11 commit dbb2627

File tree

8 files changed

+140
-29
lines changed

8 files changed

+140
-29
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -726,7 +726,6 @@ classDiagram
726726
class Comment {
727727
}
728728
729-
RuleSet <|-- DeclarationBlock: inheritance
730729
Renderable <|-- CSSElement: inheritance
731730
Renderable <|-- CSSListItem: inheritance
732731
Commentable <|-- CSSListItem: inheritance
@@ -762,6 +761,9 @@ classDiagram
762761
AtRule <|.. KeyFrame: realization
763762
CSSBlockList <|-- AtRuleBlockList: inheritance
764763
AtRule <|.. AtRuleBlockList: realization
764+
Positionable <|.. DeclarationBlock: realization
765+
CSSElement <|.. DeclarationBlock: realization
766+
CSSListItem <|.. DeclarationBlock: realization
765767
CSSFunction <|-- Color: inheritance
766768
PrimitiveValue <|-- URL: inheritance
767769
RuleValueList <|-- CalcRuleValueList: inheritance
@@ -791,6 +793,7 @@ classDiagram
791793
Charset --> "*" Comment : comments
792794
Charset --> "1" CSSString : charset
793795
DeclarationBlock --> "*" Selector : selectors
796+
DeclarationBlock --> "*" RuleSet : ruleSet
794797
Import --> "*" Comment : comments
795798
OutputFormat --> "1" OutputFormat : nextLevelFormat
796799
OutputFormat --> "1" OutputFormatter : outputFormatter

config/phpstan-baseline.neon

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ parameters:
5454
count: 1
5555
path: ../src/RuleSet/DeclarationBlock.php
5656

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+
5763
-
5864
message: '#^Loose comparison via "\!\=" is not allowed\.$#'
5965
identifier: notEqual.notAllowed

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: 98 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,56 @@
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
/**
18-
* This class represents a `RuleSet` constrained by a `Selector`.
24+
* This class includes a `RuleSet` constrained by a `Selector`.
1925
*
2026
* It contains an array of selector objects (comma-separated in the CSS) as well as the rules to be applied to the
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
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<0, max> $lineNumber
50+
*/
51+
public function __construct(int $lineNumber = 0)
52+
{
53+
$this->setPosition($lineNumber);
54+
$this->ruleSet = new RuleSet($lineNumber);
55+
}
56+
3257
/**
3358
* @throws UnexpectedTokenException
3459
* @throws UnexpectedEOFException
@@ -67,7 +92,9 @@ public static function parse(ParserState $parserState, ?CSSList $list = null): ?
6792
}
6893
}
6994
$result->setComments($comments);
70-
RuleSet::parseRuleSet($parserState, $result);
95+
96+
RuleSet::parseRuleSet($parserState, $result->ruleSet);
97+
7198
return $result;
7299
}
73100

@@ -135,6 +162,73 @@ public function getSelectors(): array
135162
return $this->selectors;
136163
}
137164

165+
public function getRuleSet(): RuleSet
166+
{
167+
return $this->ruleSet;
168+
}
169+
170+
/**
171+
* @see RuleSet::addRule()
172+
*/
173+
public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void
174+
{
175+
$this->ruleSet->addRule($ruleToAdd, $sibling);
176+
}
177+
178+
/**
179+
* @see RuleSet::getRules()
180+
*
181+
* @return array<int<0, max>, Rule>
182+
*/
183+
public function getRules(?string $searchPattern = null): array
184+
{
185+
return $this->ruleSet->getRules($searchPattern);
186+
}
187+
188+
/**
189+
* @see RuleSet::setRules()
190+
*
191+
* @param array<Rule> $rules
192+
*/
193+
public function setRules(array $rules): void
194+
{
195+
$this->ruleSet->setRules($rules);
196+
}
197+
198+
/**
199+
* @see RuleSet::getRulesAssoc()
200+
*
201+
* @return array<string, Rule>
202+
*/
203+
public function getRulesAssoc(?string $searchPattern = null): array
204+
{
205+
return $this->ruleSet->getRulesAssoc($searchPattern);
206+
}
207+
208+
/**
209+
* @see RuleSet::removeRule()
210+
*/
211+
public function removeRule(Rule $ruleToRemove): void
212+
{
213+
$this->ruleSet->removeRule($ruleToRemove);
214+
}
215+
216+
/**
217+
* @see RuleSet::removeMatchingRules()
218+
*/
219+
public function removeMatchingRules(string $searchPattern): void
220+
{
221+
$this->ruleSet->removeMatchingRules($searchPattern);
222+
}
223+
224+
/**
225+
* @see RuleSet::removeAllRules()
226+
*/
227+
public function removeAllRules(): void
228+
{
229+
$this->ruleSet->removeAllRules();
230+
}
231+
138232
/**
139233
* @return non-empty-string
140234
*
@@ -158,7 +252,7 @@ public function render(OutputFormat $outputFormat): string
158252
);
159253
$result .= $outputFormat->getContentAfterDeclarationBlockSelectors();
160254
$result .= $formatter->spaceBeforeOpeningBrace() . '{';
161-
$result .= $this->renderRules($outputFormat);
255+
$result .= $this->ruleSet->render($outputFormat);
162256
$result .= '}';
163257
$result .= $outputFormat->getContentAfterDeclarationBlock();
164258

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;
@@ -264,6 +263,14 @@ public function removeAllRules(): void
264263
$this->rules = [];
265264
}
266265

266+
/**
267+
* @internal
268+
*/
269+
public function render(OutputFormat $outputFormat): string
270+
{
271+
return $this->renderRules($outputFormat);
272+
}
273+
267274
protected function renderRules(OutputFormat $outputFormat): string
268275
{
269276
$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);

0 commit comments

Comments
 (0)