diff --git a/CHANGELOG.md b/CHANGELOG.md index 2064f81f..506b6b1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,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) diff --git a/README.md b/README.md index 29253a34..b6008004 100644 --- a/README.md +++ b/README.md @@ -753,7 +753,11 @@ classDiagram CSSFunction <|-- Color: inheritance Positionable <|.. Comment: realization Renderable <|.. Comment: realization - RuleSet <|-- DeclarationBlock: inheritance + CSSElement <|.. DeclarationBlock: realization + CSSListItem <|.. DeclarationBlock: realization + Positionable <|.. DeclarationBlock: realization + RuleContainer <|.. DeclarationBlock: realization + DeclarationBlock ..> RuleSet : dependency DeclarationBlock ..> Selector: dependency CSSBlockList <|-- Document: inheritance AtRule <|.. Import: realization 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 68926deb..4d077546 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -4,15 +4,21 @@ 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`. @@ -21,14 +27,33 @@ * 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, RuleContainer { + use CommentContainer; + use Position; + /** * @var array */ private $selectors = []; + /** + * @var RuleSet + */ + private $ruleSet; + + /** + * @param int<1, max>|null $lineNumber + */ + public function __construct(?int $lineNumber = null) + { + $this->ruleSet = new RuleSet($lineNumber); + $this->setPosition($lineNumber); + } + /** * @throws UnexpectedTokenException * @throws UnexpectedEOFException @@ -107,7 +132,9 @@ public static function parse(ParserState $parserState, ?CSSList $list = null): ? } } $result->setComments($comments); - RuleSet::parseRuleSet($parserState, $result); + + RuleSet::parseRuleSet($parserState, $result->getRuleSet()); + return $result; } @@ -175,6 +202,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 * @@ -198,7 +292,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/tests/ParserTest.php b/tests/ParserTest.php index 15af0fd7..0217eba9 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]); } /** @@ -926,9 +926,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(); @@ -1055,7 +1055,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(); @@ -1076,7 +1076,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); @@ -1092,7 +1092,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(); @@ -1109,7 +1109,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(); @@ -1127,7 +1127,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(); @@ -1145,7 +1145,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); } /** diff --git a/tests/Unit/RuleSet/DeclarationBlockTest.php b/tests/Unit/RuleSet/DeclarationBlockTest.php index 3d85d98c..4b20e9fc 100644 --- a/tests/Unit/RuleSet/DeclarationBlockTest.php +++ b/tests/Unit/RuleSet/DeclarationBlockTest.php @@ -10,7 +10,9 @@ use Sabberworm\CSS\Parsing\ParserState; use Sabberworm\CSS\Position\Positionable; use Sabberworm\CSS\Property\Selector; +use Sabberworm\CSS\Rule\Rule; use Sabberworm\CSS\RuleSet\DeclarationBlock; +use Sabberworm\CSS\RuleSet\RuleSet; use Sabberworm\CSS\Settings; use TRegx\PhpUnit\DataProviders\DataProvider; @@ -19,6 +21,8 @@ */ final class DeclarationBlockTest extends TestCase { + use RuleContainerTest; + /** * @var DeclarationBlock */ @@ -199,4 +203,79 @@ static function (Selector $selectorObject): string { $declarationBlock->getSelectors() ); } + + /** + * @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()); + } + + /** + * @test + */ + public function getRuleSetByDefaultReturnsObjectWithNullLineNumber(): void + { + $result = $this->subject->getRuleSet(); + + self::assertNull($result->getLineNumber()); + } + + /** + * @test + * + * @param int<1, max>|null $lineNumber + * + * @dataProvider provideLineNumber + */ + public function getRuleSetReturnsObjectWithLineNumberPassedToConstructor(?int $lineNumber): void + { + $subject = new DeclarationBlock($lineNumber); + + $result = $subject->getRuleSet(); + + self::assertSame($lineNumber, $result->getLineNumber()); + } }