diff --git a/README.md b/README.md index e5dd70db..adfa0c79 100644 --- a/README.md +++ b/README.md @@ -720,7 +720,6 @@ classDiagram class Comment { } - RuleSet <|-- DeclarationBlock: inheritance Renderable <|-- CSSListItem: inheritance Commentable <|-- CSSListItem: inheritance Positionable <|.. RuleSet: realization @@ -752,6 +751,8 @@ classDiagram AtRule <|.. KeyFrame: realization CSSBlockList <|-- AtRuleBlockList: inheritance AtRule <|.. AtRuleBlockList: realization + Positionable <|.. DeclarationBlock: realization + CSSListItem <|.. DeclarationBlock: realization CSSFunction <|-- Color: inheritance PrimitiveValue <|-- URL: inheritance RuleValueList <|-- CalcRuleValueList: inheritance @@ -781,6 +782,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 007667a5..716b897b 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 "Sabberworm\\CSS\\Rule\\Rule" types as the only types passed to this method$#' + identifier: typePerfect.narrowPublicClassMethodParamType + count: 1 + path: ../src/RuleSet/DeclarationBlock.php + - message: '#^Parameters should have "string" types as the only types passed to this method$#' identifier: typePerfect.narrowPublicClassMethodParamType @@ -55,15 +61,15 @@ parameters: path: ../src/RuleSet/DeclarationBlock.php - - message: '#^Only booleans are allowed in a negated boolean, string\|null given\.$#' - identifier: booleanNot.exprNotBoolean + message: '#^Parameters should have "string\|null" types as the only types passed to this method$#' + identifier: typePerfect.narrowPublicClassMethodParamType count: 2 - path: ../src/RuleSet/RuleSet.php + path: ../src/RuleSet/DeclarationBlock.php - - message: '#^Parameters should have "string" types as the only types passed to this method$#' - identifier: typePerfect.narrowPublicClassMethodParamType - count: 1 + message: '#^Only booleans are allowed in a negated boolean, string\|null given\.$#' + identifier: booleanNot.exprNotBoolean + count: 2 path: ../src/RuleSet/RuleSet.php - diff --git a/src/CSSList/CSSBlockList.php b/src/CSSList/CSSBlockList.php index 40a6ff0a..0f48691e 100644 --- a/src/CSSList/CSSBlockList.php +++ b/src/CSSList/CSSBlockList.php @@ -68,6 +68,8 @@ public function getAllRuleSets(): array $result[] = $item; } elseif ($item instanceof CSSBlockList) { $result = \array_merge($result, $item->getAllRuleSets()); + } elseif ($item instanceof DeclarationBlock) { + $result[] = $item->getRuleSet(); } } @@ -75,7 +77,7 @@ public function getAllRuleSets(): array } /** - * @param CSSList|Rule|RuleSet|Value $element + * @param CSSList|DeclarationBlock|Rule|RuleSet|Value $element * @param list $result */ protected function allValues( @@ -88,7 +90,7 @@ protected function allValues( foreach ($element->getContents() as $content) { $this->allValues($content, $result, $searchString, $searchInFunctionArguments); } - } elseif ($element instanceof RuleSet) { + } elseif ($element instanceof RuleSet || $element instanceof DeclarationBlock) { foreach ($element->getRules($searchString) as $rule) { $this->allValues($rule, $result, $searchString, $searchInFunctionArguments); } diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index e4125797..cf54e6a9 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -4,31 +4,55 @@ namespace Sabberworm\CSS\RuleSet; +use Sabberworm\CSS\Comment\CommentContainer; 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 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 @@ -67,7 +91,9 @@ public static function parse(ParserState $parserState, ?CSSList $list = null): ? } } $result->setComments($comments); - RuleSet::parseRuleSet($parserState, $result); + + RuleSet::parseRuleSet($parserState, $result->ruleSet); + return $result; } @@ -135,6 +161,63 @@ 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() + * + * @param Rule|string|null $searchPattern + * + * @return array, Rule> + */ + public function getRules($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() + * + * @param Rule|string|null $searchPattern + * + * @return array + */ + public function getRulesAssoc($searchPattern = null): array + { + return $this->ruleSet->getRulesAssoc($searchPattern); + } + + /** + * @see RuleSet::removeRule() + * + * @param Rule|string|null $searchPattern + */ + public function removeRule($searchPattern): void + { + $this->ruleSet->removeRule($searchPattern); + } + /** * @return non-empty-string * @@ -158,7 +241,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 1d5b2b4c..2489bee9 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -23,10 +23,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 CSSListItem, Positionable +class RuleSet implements CSSListItem, Positionable { use CommentContainer; use Position; @@ -250,6 +249,14 @@ public function removeRule($searchPattern): void } } + /** + * @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 f0fee35b..72601802 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -37,7 +37,7 @@ final class ParserTest extends TestCase /** * @test */ - public function parseForOneRuleSetReturnsDocumentWithOneRuleSet(): void + public function parseForOneDeclarationBlockReturnsDocumentWithOneDeclarationBlock(): void { $css = '.thing { left: 10px; }'; $parser = new Parser($css); @@ -48,7 +48,7 @@ public function parseForOneRuleSetReturnsDocumentWithOneRuleSet(): void $cssList = $document->getContents(); self::assertCount(1, $cssList); - self::assertInstanceOf(RuleSet::class, $cssList[0]); + self::assertInstanceOf(DeclarationBlock::class, $cssList[0]); } /** @@ -928,9 +928,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(); @@ -985,6 +985,7 @@ public function lineNumbersParsing(): void && !$contentItem instanceof CSSNamespace && !$contentItem instanceof Import && !$contentItem instanceof RuleSet + && !$contentItem instanceof DeclarationBlock ) { self::fail('Content item is not of an expected type. It\'s a `' . \get_class($contentItem) . '`.'); } @@ -994,6 +995,7 @@ public function lineNumbersParsing(): void if ( !$block instanceof CSSList && !$block instanceof RuleSet + && !$block instanceof DeclarationBlock ) { self::fail( 'KeyFrame content item is not of an expected type. It\'s a `' . \get_class($block) . '`.' @@ -1076,7 +1078,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(); @@ -1097,7 +1099,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); @@ -1113,7 +1115,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(); @@ -1130,7 +1132,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(); @@ -1148,7 +1150,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(); @@ -1166,7 +1168,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 0028288f..36b319fe 100644 --- a/tests/Unit/CSSList/CSSBlockListTest.php +++ b/tests/Unit/CSSList/CSSBlockListTest.php @@ -154,7 +154,7 @@ public function getAllRuleSetsWhenNoContentSetReturnsEmptyArray(): void /** * @test */ - public function getAllRuleSetsReturnsOneDeclarationBlockDirectlySetAsContent(): void + public function getAllRuleSetsReturnsRuleSetFromOneDeclarationBlockDirectlySetAsContent(): void { $subject = new ConcreteCSSBlockList(); @@ -163,7 +163,7 @@ public function getAllRuleSetsReturnsOneDeclarationBlockDirectlySetAsContent(): $result = $subject->getAllRuleSets(); - self::assertSame([$declarationBlock], $result); + self::assertSame([$declarationBlock->getRuleSet()], $result); } /** @@ -184,7 +184,7 @@ public function getAllRuleSetsReturnsOneAtRuleSetDirectlySetAsContent(): void /** * @test */ - public function getAllRuleSetsReturnsMultipleDeclarationBlocksDirectlySetAsContents(): void + public function getAllRuleSetsReturnsRuleSetsFromMultipleDeclarationBlocksDirectlySetAsContents(): void { $subject = new ConcreteCSSBlockList(); @@ -194,7 +194,7 @@ public function getAllRuleSetsReturnsMultipleDeclarationBlocksDirectlySetAsConte $result = $subject->getAllRuleSets(); - self::assertSame([$declarationBlock1, $declarationBlock2], $result); + self::assertSame([$declarationBlock1->getRuleSet(), $declarationBlock2->getRuleSet()], $result); } /** @@ -216,7 +216,7 @@ public function getAllRuleSetsReturnsMultipleAtRuleSetsDirectlySetAsContents(): /** * @test */ - public function getAllRuleSetsReturnsDeclarationBlocksWithinAtRuleBlockList(): void + public function getAllRuleSetsReturnsRuleSetsFromDeclarationBlocksWithinAtRuleBlockList(): void { $subject = new ConcreteCSSBlockList(); @@ -227,7 +227,7 @@ public function getAllRuleSetsReturnsDeclarationBlocksWithinAtRuleBlockList(): v $result = $subject->getAllRuleSets(); - self::assertSame([$declarationBlock], $result); + self::assertSame([$declarationBlock->getRuleSet()], $result); } /**