From 60dac71ca79d7b3dc4846b1af0b207e252fc3ded Mon Sep 17 00:00:00 2001 From: Jake Hotson Date: Wed, 19 Mar 2025 00:24:59 +0000 Subject: [PATCH 1/9] [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. --- README.md | 5 +- config/phpstan-baseline.neon | 6 ++ src/CSSList/CSSBlockList.php | 2 + src/RuleSet/DeclarationBlock.php | 102 +++++++++++++++++++++++- src/RuleSet/RuleSet.php | 13 ++- tests/ParserTest.php | 22 ++--- tests/RuleSet/DeclarationBlockTest.php | 7 +- tests/Unit/CSSList/CSSBlockListTest.php | 12 +-- 8 files changed, 140 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 9ecdc3e7..4db1f9e7 100644 --- a/README.md +++ b/README.md @@ -726,7 +726,6 @@ classDiagram class Comment { } - RuleSet <|-- DeclarationBlock: inheritance Renderable <|-- CSSElement: inheritance Renderable <|-- CSSListItem: inheritance Commentable <|-- CSSListItem: inheritance @@ -762,6 +761,9 @@ classDiagram AtRule <|.. KeyFrame: realization CSSBlockList <|-- AtRuleBlockList: inheritance AtRule <|.. AtRuleBlockList: realization + Positionable <|.. DeclarationBlock: realization + CSSElement <|.. DeclarationBlock: realization + CSSListItem <|.. DeclarationBlock: realization CSSFunction <|-- Color: inheritance PrimitiveValue <|-- URL: inheritance RuleValueList <|-- CalcRuleValueList: inheritance @@ -791,6 +793,7 @@ classDiagram Charset --> "*" Comment : comments Charset --> "1" CSSString : charset DeclarationBlock --> "*" Selector : selectors + DeclarationBlock --> "*" RuleSet : ruleSet Import --> "*" Comment : comments OutputFormat --> "1" OutputFormat : nextLevelFormat OutputFormat --> "1" OutputFormatter : outputFormatter diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 8d882804..ee3ec2ae 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -48,6 +48,12 @@ parameters: count: 1 path: ../src/RuleSet/DeclarationBlock.php + - + message: '#^Parameters should have "string\|null" types as the only types passed to this method$#' + identifier: typePerfect.narrowPublicClassMethodParamType + count: 2 + path: ../src/RuleSet/DeclarationBlock.php + - message: '#^Loose comparison via "\!\=" is not allowed\.$#' identifier: notEqual.notAllowed diff --git a/src/CSSList/CSSBlockList.php b/src/CSSList/CSSBlockList.php index 2dfd284f..122ac5d1 100644 --- a/src/CSSList/CSSBlockList.php +++ b/src/CSSList/CSSBlockList.php @@ -56,6 +56,8 @@ public function getAllRuleSets(): array $result[] = $item; } elseif ($item instanceof CSSBlockList) { $result = \array_merge($result, $item->getAllRuleSets()); + } elseif ($item instanceof DeclarationBlock) { + $result[] = $item->getRuleSet(); } } diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 314bbae3..3b12c7a0 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -4,31 +4,56 @@ namespace Sabberworm\CSS\RuleSet; +use Sabberworm\CSS\Comment\CommentContainer; +use Sabberworm\CSS\CSSElement; use Sabberworm\CSS\CSSList\CSSList; +use Sabberworm\CSS\CSSList\CSSListItem; use Sabberworm\CSS\CSSList\KeyFrame; use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Parsing\OutputException; use Sabberworm\CSS\Parsing\ParserState; use Sabberworm\CSS\Parsing\UnexpectedEOFException; use Sabberworm\CSS\Parsing\UnexpectedTokenException; +use Sabberworm\CSS\Position\Position; +use Sabberworm\CSS\Position\Positionable; use Sabberworm\CSS\Property\KeyframeSelector; use Sabberworm\CSS\Property\Selector; +use Sabberworm\CSS\Rule\Rule; /** - * This class represents a `RuleSet` constrained by a `Selector`. + * This class includes a `RuleSet` constrained by a `Selector`. * * It contains an array of selector objects (comma-separated in the CSS) as well as the rules to be applied to the * matching elements. * * Declaration blocks usually appear directly inside a `Document` or another `CSSList` (mostly a `MediaQuery`). + * + * Note that `CSSListItem` extends both `Commentable` and `Renderable`, so those interfaces must also be implemented. */ -class DeclarationBlock extends RuleSet +class DeclarationBlock implements CSSElement, CSSListItem, Positionable { + use CommentContainer; + use Position; + /** * @var array */ private $selectors = []; + /** + * @var RuleSet + */ + private $ruleSet; + + /** + * @param int<0, max> $lineNumber + */ + public function __construct(int $lineNumber = 0) + { + $this->setPosition($lineNumber); + $this->ruleSet = new RuleSet($lineNumber); + } + /** * @throws UnexpectedTokenException * @throws UnexpectedEOFException @@ -75,7 +100,9 @@ public static function parse(ParserState $parserState, ?CSSList $list = null): ? } } $result->setComments($comments); - RuleSet::parseRuleSet($parserState, $result); + + RuleSet::parseRuleSet($parserState, $result->ruleSet); + return $result; } @@ -143,6 +170,73 @@ public function getSelectors(): array return $this->selectors; } + public function getRuleSet(): RuleSet + { + return $this->ruleSet; + } + + /** + * @see RuleSet::addRule() + */ + public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void + { + $this->ruleSet->addRule($ruleToAdd, $sibling); + } + + /** + * @see RuleSet::getRules() + * + * @return array, Rule> + */ + public function getRules(?string $searchPattern = null): array + { + return $this->ruleSet->getRules($searchPattern); + } + + /** + * @see RuleSet::setRules() + * + * @param array $rules + */ + public function setRules(array $rules): void + { + $this->ruleSet->setRules($rules); + } + + /** + * @see RuleSet::getRulesAssoc() + * + * @return array + */ + public function getRulesAssoc(?string $searchPattern = null): array + { + return $this->ruleSet->getRulesAssoc($searchPattern); + } + + /** + * @see RuleSet::removeRule() + */ + public function removeRule(Rule $ruleToRemove): void + { + $this->ruleSet->removeRule($ruleToRemove); + } + + /** + * @see RuleSet::removeMatchingRules() + */ + public function removeMatchingRules(string $searchPattern): void + { + $this->ruleSet->removeMatchingRules($searchPattern); + } + + /** + * @see RuleSet::removeAllRules() + */ + public function removeAllRules(): void + { + $this->ruleSet->removeAllRules(); + } + /** * @return non-empty-string * @@ -166,7 +260,7 @@ public function render(OutputFormat $outputFormat): string ); $result .= $outputFormat->getContentAfterDeclarationBlockSelectors(); $result .= $formatter->spaceBeforeOpeningBrace() . '{'; - $result .= $this->renderRules($outputFormat); + $result .= $this->ruleSet->render($outputFormat); $result .= '}'; $result .= $outputFormat->getContentAfterDeclarationBlock(); diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 9ab5e203..521a6ae9 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -24,10 +24,9 @@ * If you want to manipulate a `RuleSet`, use the methods `addRule(Rule $rule)`, `getRules()` and `removeRule($rule)` * (which accepts either a `Rule` or a rule name; optionally suffixed by a dash to remove all related rules). * - * Note that `CSSListItem` extends both `Commentable` and `Renderable`, - * so those interfaces must also be implemented by concrete subclasses. + * Note that `CSSListItem` extends both `Commentable` and `Renderable`, so those interfaces must also be implemented. */ -abstract class RuleSet implements CSSElement, CSSListItem, Positionable, RuleContainer +class RuleSet implements CSSElement, CSSListItem, Positionable, RuleContainer { use CommentContainer; use Position; @@ -293,6 +292,14 @@ public function removeAllRules(): void $this->rules = []; } + /** + * @internal + */ + public function render(OutputFormat $outputFormat): string + { + return $this->renderRules($outputFormat); + } + protected function renderRules(OutputFormat $outputFormat): string { $result = ''; diff --git a/tests/ParserTest.php b/tests/ParserTest.php index da991ee7..1e76148c 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -38,7 +38,7 @@ final class ParserTest extends TestCase /** * @test */ - public function parseForOneRuleSetReturnsDocumentWithOneRuleSet(): void + public function parseForOneDeclarationBlockReturnsDocumentWithOneDeclarationBlock(): void { $css = '.thing { left: 10px; }'; $parser = new Parser($css); @@ -49,7 +49,7 @@ public function parseForOneRuleSetReturnsDocumentWithOneRuleSet(): void $cssList = $document->getContents(); self::assertCount(1, $cssList); - self::assertInstanceOf(RuleSet::class, $cssList[0]); + self::assertInstanceOf(DeclarationBlock::class, $cssList[0]); } /** @@ -929,9 +929,9 @@ public function missingPropertyValueStrict(): void public function missingPropertyValueLenient(): void { $parsed = self::parsedStructureForFile('missing-property-value', Settings::create()->withLenientParsing(true)); - $rulesets = $parsed->getAllRuleSets(); - self::assertCount(1, $rulesets); - $block = $rulesets[0]; + $declarationBlocks = $parsed->getAllDeclarationBlocks(); + self::assertCount(1, $declarationBlocks); + $block = $declarationBlocks[0]; self::assertInstanceOf(DeclarationBlock::class, $block); self::assertEquals([new Selector('div')], $block->getSelectors()); $rules = $block->getRules(); @@ -1058,7 +1058,7 @@ public function commentExtracting(): void // $this->assertSame("* Number 5 *", $fooBarBlockComments[1]->getComment()); // Declaration rules. - self::assertInstanceOf(RuleSet::class, $fooBarBlock); + self::assertInstanceOf(DeclarationBlock::class, $fooBarBlock); $fooBarRules = $fooBarBlock->getRules(); $fooBarRule = $fooBarRules[0]; $fooBarRuleComments = $fooBarRule->getComments(); @@ -1079,7 +1079,7 @@ public function commentExtracting(): void self::assertSame('* Number 10 *', $fooBarComments[0]->getComment()); // Media -> declaration -> rule. - self::assertInstanceOf(RuleSet::class, $mediaRules[0]); + self::assertInstanceOf(DeclarationBlock::class, $mediaRules[0]); $fooBarRules = $mediaRules[0]->getRules(); $fooBarChildComments = $fooBarRules[0]->getComments(); self::assertCount(1, $fooBarChildComments); @@ -1095,7 +1095,7 @@ public function flatCommentExtractingOneComment(): void $document = $parser->parse(); $contents = $document->getContents(); - self::assertInstanceOf(RuleSet::class, $contents[0]); + self::assertInstanceOf(DeclarationBlock::class, $contents[0]); $divRules = $contents[0]->getRules(); $comments = $divRules[0]->getComments(); @@ -1112,7 +1112,7 @@ public function flatCommentExtractingTwoConjoinedCommentsForOneRule(): void $document = $parser->parse(); $contents = $document->getContents(); - self::assertInstanceOf(RuleSet::class, $contents[0]); + self::assertInstanceOf(DeclarationBlock::class, $contents[0]); $divRules = $contents[0]->getRules(); $comments = $divRules[0]->getComments(); @@ -1130,7 +1130,7 @@ public function flatCommentExtractingTwoSpaceSeparatedCommentsForOneRule(): void $document = $parser->parse(); $contents = $document->getContents(); - self::assertInstanceOf(RuleSet::class, $contents[0]); + self::assertInstanceOf(DeclarationBlock::class, $contents[0]); $divRules = $contents[0]->getRules(); $comments = $divRules[0]->getComments(); @@ -1148,7 +1148,7 @@ public function flatCommentExtractingCommentsForTwoRules(): void $document = $parser->parse(); $contents = $document->getContents(); - self::assertInstanceOf(RuleSet::class, $contents[0]); + self::assertInstanceOf(DeclarationBlock::class, $contents[0]); $divRules = $contents[0]->getRules(); $rule1Comments = $divRules[0]->getComments(); $rule2Comments = $divRules[1]->getComments(); diff --git a/tests/RuleSet/DeclarationBlockTest.php b/tests/RuleSet/DeclarationBlockTest.php index 5aaf0662..63411a64 100644 --- a/tests/RuleSet/DeclarationBlockTest.php +++ b/tests/RuleSet/DeclarationBlockTest.php @@ -8,13 +8,12 @@ use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Parser; use Sabberworm\CSS\Rule\Rule; -use Sabberworm\CSS\RuleSet\RuleSet; +use Sabberworm\CSS\RuleSet\DeclarationBlock; use Sabberworm\CSS\Settings as ParserSettings; use Sabberworm\CSS\Value\Size; /** * @covers \Sabberworm\CSS\RuleSet\DeclarationBlock - * @covers \Sabberworm\CSS\RuleSet\RuleSet */ final class DeclarationBlockTest extends TestCase { @@ -31,7 +30,7 @@ public function overrideRules(): void $contents = $document->getContents(); $wrapper = $contents[0]; - self::assertInstanceOf(RuleSet::class, $wrapper); + self::assertInstanceOf(DeclarationBlock::class, $wrapper); self::assertCount(2, $wrapper->getRules()); $wrapper->setRules([$rule]); @@ -52,7 +51,7 @@ public function ruleInsertion(): void $contents = $document->getContents(); $wrapper = $contents[0]; - self::assertInstanceOf(RuleSet::class, $wrapper); + self::assertInstanceOf(DeclarationBlock::class, $wrapper); $leftRules = $wrapper->getRules('left'); self::assertCount(1, $leftRules); diff --git a/tests/Unit/CSSList/CSSBlockListTest.php b/tests/Unit/CSSList/CSSBlockListTest.php index 41f2b41f..ce7e5447 100644 --- a/tests/Unit/CSSList/CSSBlockListTest.php +++ b/tests/Unit/CSSList/CSSBlockListTest.php @@ -157,7 +157,7 @@ public function getAllRuleSetsWhenNoContentSetReturnsEmptyArray(): void /** * @test */ - public function getAllRuleSetsReturnsOneDeclarationBlockDirectlySetAsContent(): void + public function getAllRuleSetsReturnsRuleSetFromOneDeclarationBlockDirectlySetAsContent(): void { $subject = new ConcreteCSSBlockList(); @@ -166,7 +166,7 @@ public function getAllRuleSetsReturnsOneDeclarationBlockDirectlySetAsContent(): $result = $subject->getAllRuleSets(); - self::assertSame([$declarationBlock], $result); + self::assertSame([$declarationBlock->getRuleSet()], $result); } /** @@ -187,7 +187,7 @@ public function getAllRuleSetsReturnsOneAtRuleSetDirectlySetAsContent(): void /** * @test */ - public function getAllRuleSetsReturnsMultipleDeclarationBlocksDirectlySetAsContents(): void + public function getAllRuleSetsReturnsRuleSetsFromMultipleDeclarationBlocksDirectlySetAsContents(): void { $subject = new ConcreteCSSBlockList(); @@ -197,7 +197,7 @@ public function getAllRuleSetsReturnsMultipleDeclarationBlocksDirectlySetAsConte $result = $subject->getAllRuleSets(); - self::assertSame([$declarationBlock1, $declarationBlock2], $result); + self::assertSame([$declarationBlock1->getRuleSet(), $declarationBlock2->getRuleSet()], $result); } /** @@ -219,7 +219,7 @@ public function getAllRuleSetsReturnsMultipleAtRuleSetsDirectlySetAsContents(): /** * @test */ - public function getAllRuleSetsReturnsDeclarationBlocksWithinAtRuleBlockList(): void + public function getAllRuleSetsReturnsRuleSetsFromDeclarationBlocksWithinAtRuleBlockList(): void { $subject = new ConcreteCSSBlockList(); @@ -230,7 +230,7 @@ public function getAllRuleSetsReturnsDeclarationBlocksWithinAtRuleBlockList(): v $result = $subject->getAllRuleSets(); - self::assertSame([$declarationBlock], $result); + self::assertSame([$declarationBlock->getRuleSet()], $result); } /** From f4d56d28dcfd0632bc023e9060d2845ced1b362b Mon Sep 17 00:00:00 2001 From: Jake Hotson Date: Fri, 9 May 2025 19:15:34 +0100 Subject: [PATCH 2/9] Add `RuleContainer` interface for `RuleSet` and `DeclarationBlock` --- config/phpstan-baseline.neon | 6 ------ src/RuleSet/DeclarationBlock.php | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index ee3ec2ae..8d882804 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -48,12 +48,6 @@ parameters: count: 1 path: ../src/RuleSet/DeclarationBlock.php - - - message: '#^Parameters should have "string\|null" types as the only types passed to this method$#' - identifier: typePerfect.narrowPublicClassMethodParamType - count: 2 - path: ../src/RuleSet/DeclarationBlock.php - - message: '#^Loose comparison via "\!\=" is not allowed\.$#' identifier: notEqual.notAllowed diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 3b12c7a0..be01f6d0 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -30,7 +30,7 @@ * * Note that `CSSListItem` extends both `Commentable` and `Renderable`, so those interfaces must also be implemented. */ -class DeclarationBlock implements CSSElement, CSSListItem, Positionable +class DeclarationBlock implements CSSElement, CSSListItem, Positionable, RuleContainer { use CommentContainer; use Position; From d1ef7d3b340c8d037b2ad6ac88a384c92f923ded Mon Sep 17 00:00:00 2001 From: Jake Hotson Date: Thu, 26 Jun 2025 19:03:04 +0100 Subject: [PATCH 3/9] Change default `$lineNumber` to `null`. --- src/RuleSet/DeclarationBlock.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index be01f6d0..edc457a9 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -46,9 +46,9 @@ class DeclarationBlock implements CSSElement, CSSListItem, Positionable, RuleCon private $ruleSet; /** - * @param int<0, max> $lineNumber + * @param int<1, max>|null $lineNumber */ - public function __construct(int $lineNumber = 0) + public function __construct(?int $lineNumber = null) { $this->setPosition($lineNumber); $this->ruleSet = new RuleSet($lineNumber); From d97d3cf76643071ae6cbdd34689012175852c75f Mon Sep 17 00:00:00 2001 From: Jake Hotson Date: Thu, 26 Jun 2025 19:05:56 +0100 Subject: [PATCH 4/9] Restore "represents" in DocBlock description --- src/RuleSet/DeclarationBlock.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index edc457a9..63d4f957 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -21,7 +21,7 @@ use Sabberworm\CSS\Rule\Rule; /** - * This class includes a `RuleSet` constrained by a `Selector`. + * This class represents a `RuleSet` constrained by a `Selector`. * * It contains an array of selector objects (comma-separated in the CSS) as well as the rules to be applied to the * matching elements. From 1b55fb2568b6540ee04b38fc62ba10e07255d325 Mon Sep 17 00:00:00 2001 From: Jake Hotson Date: Thu, 26 Jun 2025 23:13:31 +0100 Subject: [PATCH 5/9] Add trait for testing `RuleContainer` methods Use it in the `TestCase`s for `RuleSet` and `DeclarationBlock`. --- tests/Unit/RuleSet/DeclarationBlockTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Unit/RuleSet/DeclarationBlockTest.php b/tests/Unit/RuleSet/DeclarationBlockTest.php index c84d1f9a..35cbb973 100644 --- a/tests/Unit/RuleSet/DeclarationBlockTest.php +++ b/tests/Unit/RuleSet/DeclarationBlockTest.php @@ -15,6 +15,8 @@ */ final class DeclarationBlockTest extends TestCase { + use RuleContainerTest; + /** * @var DeclarationBlock */ From 50edde7a3530b8019a125d37e7162f5ce47f8cdf Mon Sep 17 00:00:00 2001 From: Jake Hotson Date: Thu, 26 Jun 2025 23:18:07 +0100 Subject: [PATCH 6/9] Update class diagram for `DeclarationBlock` implementing `RuleContainer` --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4db1f9e7..48f798a5 100644 --- a/README.md +++ b/README.md @@ -764,6 +764,7 @@ classDiagram Positionable <|.. DeclarationBlock: realization CSSElement <|.. DeclarationBlock: realization CSSListItem <|.. DeclarationBlock: realization + RuleContainer <|.. DeclarationBlock: realization CSSFunction <|-- Color: inheritance PrimitiveValue <|-- URL: inheritance RuleValueList <|-- CalcRuleValueList: inheritance From b1b8a8f616367900c632b43750781954173e4f47 Mon Sep 17 00:00:00 2001 From: Jake Hotson Date: Fri, 27 Jun 2025 15:20:43 +0100 Subject: [PATCH 7/9] Add tests for `getRuleSet` --- tests/Unit/RuleSet/DeclarationBlockTest.php | 51 +++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tests/Unit/RuleSet/DeclarationBlockTest.php b/tests/Unit/RuleSet/DeclarationBlockTest.php index 35cbb973..431adc93 100644 --- a/tests/Unit/RuleSet/DeclarationBlockTest.php +++ b/tests/Unit/RuleSet/DeclarationBlockTest.php @@ -8,7 +8,9 @@ use Sabberworm\CSS\CSSElement; use Sabberworm\CSS\CSSList\CSSListItem; use Sabberworm\CSS\Position\Positionable; +use Sabberworm\CSS\Rule\Rule; use Sabberworm\CSS\RuleSet\DeclarationBlock; +use Sabberworm\CSS\RuleSet\RuleSet; /** * @covers \Sabberworm\CSS\RuleSet\DeclarationBlock @@ -50,4 +52,53 @@ public function implementsPositionable(): void { self::assertInstanceOf(Positionable::class, $this->subject); } + + /** + * @test + */ + public function getRuleSetOnVirginReturnsARuleSet(): void + { + $result = $this->subject->getRuleSet(); + + self::assertInstanceOf(RuleSet::class, $result); + } + + /** + * @test + */ + public function getRuleSetAfterRulesSetReturnsARuleSet(): void + { + $this->subject->setRules([new Rule('color')]); + + $result = $this->subject->getRuleSet(); + + self::assertInstanceOf(RuleSet::class, $result); + } + + /** + * @test + */ + public function getRuleSetOnVirginReturnsObjectWithoutRules(): void + { + $result = $this->subject->getRuleSet(); + + self::assertSame([], $result->getRules()); + } + + /** + * @test + * + * @param list $propertyNamesToSet + * + * @dataProvider providePropertyNames + */ + public function getRuleSetReturnsObjectWithRulesSet(array $propertyNamesToSet): void + { + $rules = self::createRulesFromPropertyNames($propertyNamesToSet); + $this->subject->setRules($rules); + + $result = $this->subject->getRuleSet(); + + self::assertSame($rules, $result->getRules()); + } } From 85e547ca78e7c241d66ce1090ef9579978aac7af Mon Sep 17 00:00:00 2001 From: Jake Hotson Date: Fri, 27 Jun 2025 15:21:02 +0100 Subject: [PATCH 8/9] Add changelog entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 912d5dba..d1e8fee4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,8 @@ Please also have a look at our ### Changed +- `DeclarationBlock` no longer extends `RuleSet` and instead has a `RuleSet` as + a property; use `getRuleSet()` to access it directly (#1194) - The default line (and column) number is now `null` (not zero) (#1288) - `setPosition()` (in `Rule` and other classes) now has fluent interface, returning itself (#1259) From 697474546bdadaeb38726ead2a298d5ccd7cb7c9 Mon Sep 17 00:00:00 2001 From: Jake Hotson Date: Mon, 30 Jun 2025 01:46:56 +0100 Subject: [PATCH 9/9] Reorder construction --- src/RuleSet/DeclarationBlock.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 63d4f957..35e5a9cf 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -50,8 +50,8 @@ class DeclarationBlock implements CSSElement, CSSListItem, Positionable, RuleCon */ public function __construct(?int $lineNumber = null) { - $this->setPosition($lineNumber); $this->ruleSet = new RuleSet($lineNumber); + $this->setPosition($lineNumber); } /**