• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MyIntervals / PHP-CSS-Parser / 14449499370

14 Apr 2025 03:25PM UTC coverage: 52.421% (-3.8%) from 56.257%
14449499370

Pull #1194

github

web-flow
Merge 1f908cf10 into 9dbc6a644
Pull Request #1194: [TASK] Use delegation for `DeclarationBlock` -> `RuleSet`

17 of 26 new or added lines in 3 files covered. (65.38%)

70 existing lines in 1 file now uncovered.

942 of 1797 relevant lines covered (52.42%)

7.68 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

65.91
/src/RuleSet/DeclarationBlock.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Sabberworm\CSS\RuleSet;
6

7
use Sabberworm\CSS\Comment\CommentContainer;
8
use Sabberworm\CSS\CSSElement;
9
use Sabberworm\CSS\CSSList\CSSList;
10
use Sabberworm\CSS\CSSList\CSSListItem;
11
use Sabberworm\CSS\CSSList\KeyFrame;
12
use Sabberworm\CSS\OutputFormat;
13
use Sabberworm\CSS\Parsing\OutputException;
14
use Sabberworm\CSS\Parsing\ParserState;
15
use Sabberworm\CSS\Parsing\UnexpectedEOFException;
16
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
17
use Sabberworm\CSS\Position\Position;
18
use Sabberworm\CSS\Position\Positionable;
19
use Sabberworm\CSS\Property\KeyframeSelector;
20
use Sabberworm\CSS\Property\Selector;
21
use Sabberworm\CSS\Rule\Rule;
22

23
/**
24
 * This class includes a `RuleSet` constrained by a `Selector`.
25
 *
26
 * It contains an array of selector objects (comma-separated in the CSS) as well as the rules to be applied to the
27
 * matching elements.
28
 *
29
 * 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.
32
 */
33
class DeclarationBlock implements CSSElement, CSSListItem, Positionable
34
{
35
    use CommentContainer;
36
    use Position;
37

38
    /**
39
     * @var array<Selector|string>
40
     */
41
    private $selectors = [];
42

43
    /**
44
     * @var RuleSet
45
     */
46
    private $ruleSet;
47

48
    /**
49
     * @param int<0, max> $lineNumber
50
     */
51
    public function __construct(int $lineNumber = 0)
11✔
52
    {
53
        $this->setPosition($lineNumber);
11✔
54
        $this->ruleSet = new RuleSet($lineNumber);
11✔
55
    }
11✔
56

57
    /**
58
     * @throws UnexpectedTokenException
59
     * @throws UnexpectedEOFException
60
     *
61
     * @internal since V8.8.0
62
     */
63
    public static function parse(ParserState $parserState, ?CSSList $list = null): ?DeclarationBlock
9✔
64
    {
65
        $comments = [];
9✔
66
        $result = new DeclarationBlock($parserState->currentLine());
9✔
67
        try {
68
            $selectorParts = [];
9✔
69
            do {
70
                $selectorParts[] = $parserState->consume(1)
9✔
71
                    . $parserState->consumeUntil(['{', '}', '\'', '"'], false, false, $comments);
9✔
72
                if (\in_array($parserState->peek(), ['\'', '"'], true) && \substr(\end($selectorParts), -1) != '\\') {
9✔
73
                    if (!isset($stringWrapperCharacter)) {
×
74
                        $stringWrapperCharacter = $parserState->peek();
×
75
                    } elseif ($stringWrapperCharacter === $parserState->peek()) {
×
76
                        unset($stringWrapperCharacter);
×
77
                    }
78
                }
79
            } while (!\in_array($parserState->peek(), ['{', '}'], true) || isset($stringWrapperCharacter));
9✔
80
            $result->setSelectors(\implode('', $selectorParts), $list);
9✔
81
            if ($parserState->comes('{')) {
6✔
82
                $parserState->consume(1);
6✔
83
            }
84
        } catch (UnexpectedTokenException $e) {
3✔
85
            if ($parserState->getSettings()->usesLenientParsing()) {
3✔
86
                if (!$parserState->comes('}')) {
3✔
87
                    $parserState->consumeUntil('}', false, true);
1✔
88
                }
89
                return null;
3✔
90
            } else {
91
                throw $e;
×
92
            }
93
        }
94
        $result->setComments($comments);
6✔
95

96
        RuleSet::parseRuleSet($parserState, $result->ruleSet);
6✔
97

98
        return $result;
6✔
99
    }
100

101
    /**
102
     * @param array<Selector|string>|string $selectors
103
     *
104
     * @throws UnexpectedTokenException
105
     */
106
    public function setSelectors($selectors, ?CSSList $list = null): void
10✔
107
    {
108
        if (\is_array($selectors)) {
10✔
109
            $this->selectors = $selectors;
1✔
110
        } else {
111
            $this->selectors = \explode(',', $selectors);
9✔
112
        }
113
        foreach ($this->selectors as $key => $selector) {
10✔
114
            if (!($selector instanceof Selector)) {
10✔
115
                if ($list === null || !($list instanceof KeyFrame)) {
9✔
116
                    if (!Selector::isValid($selector)) {
9✔
117
                        throw new UnexpectedTokenException(
3✔
118
                            "Selector did not match '" . Selector::SELECTOR_VALIDATION_RX . "'.",
3✔
119
                            $selectors,
120
                            'custom'
3✔
121
                        );
122
                    }
123
                    $this->selectors[$key] = new Selector($selector);
6✔
124
                } else {
125
                    if (!KeyframeSelector::isValid($selector)) {
×
126
                        throw new UnexpectedTokenException(
×
127
                            "Selector did not match '" . KeyframeSelector::SELECTOR_VALIDATION_RX . "'.",
×
128
                            $selector,
129
                            'custom'
×
130
                        );
131
                    }
132
                    $this->selectors[$key] = new KeyframeSelector($selector);
×
133
                }
134
            }
135
        }
136
    }
7✔
137

138
    /**
139
     * Remove one of the selectors of the block.
140
     *
141
     * @param Selector|string $selectorToRemove
142
     */
143
    public function removeSelector($selectorToRemove): bool
×
144
    {
145
        if ($selectorToRemove instanceof Selector) {
×
146
            $selectorToRemove = $selectorToRemove->getSelector();
×
147
        }
148
        foreach ($this->selectors as $key => $selector) {
×
149
            if ($selector->getSelector() === $selectorToRemove) {
×
150
                unset($this->selectors[$key]);
×
151
                return true;
×
152
            }
153
        }
154
        return false;
×
155
    }
156

157
    /**
158
     * @return array<Selector>
159
     */
160
    public function getSelectors(): array
×
161
    {
162
        return $this->selectors;
×
163
    }
164

NEW
165
    public function getRuleSet(): RuleSet
×
166
    {
NEW
167
        return $this->ruleSet;
×
168
    }
169

170
    /**
171
     * @see RuleSet::addRule()
172
     */
173
    public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void
2✔
174
    {
175
        $this->ruleSet->addRule($ruleToAdd, $sibling);
2✔
176
    }
2✔
177

178
    /**
179
     * @see RuleSet::getRules()
180
     *
181
     * @param Rule|string|null $searchPattern
182
     *
183
     * @return array<int<0, max>, Rule>
184
     */
185
    public function getRules($searchPattern = null): array
2✔
186
    {
187
        return $this->ruleSet->getRules($searchPattern);
2✔
188
    }
189

190
    /**
191
     * @see RuleSet::setRules()
192
     *
193
     * @param array<Rule> $rules
194
     */
195
    public function setRules(array $rules): void
1✔
196
    {
197
        $this->ruleSet->setRules($rules);
1✔
198
    }
1✔
199

200
    /**
201
     * @see RuleSet::getRulesAssoc()
202
     *
203
     * @param Rule|string|null $searchPattern
204
     *
205
     * @return array<string, Rule>
206
     */
NEW
207
    public function getRulesAssoc($searchPattern = null): array
×
208
    {
NEW
209
        return $this->ruleSet->getRulesAssoc($searchPattern);
×
210
    }
211

212
    /**
213
     * @see RuleSet::removeRule()
214
     *
215
     * @param Rule|string|null $searchPattern
216
     */
NEW
217
    public function removeRule($searchPattern): void
×
218
    {
NEW
219
        $this->ruleSet->removeRule($searchPattern);
×
NEW
220
    }
×
221

222
    /**
223
     * @return non-empty-string
224
     *
225
     * @throws OutputException
226
     */
227
    public function render(OutputFormat $outputFormat): string
6✔
228
    {
229
        $formatter = $outputFormat->getFormatter();
6✔
230
        $result = $formatter->comments($this);
6✔
231
        if (\count($this->selectors) === 0) {
6✔
232
            // If all the selectors have been removed, this declaration block becomes invalid
233
            throw new OutputException(
×
234
                'Attempt to print declaration block with missing selector',
×
235
                $this->getLineNumber()
×
236
            );
237
        }
238
        $result .= $outputFormat->getContentBeforeDeclarationBlock();
6✔
239
        $result .= $formatter->implode(
6✔
240
            $formatter->spaceBeforeSelectorSeparator() . ',' . $formatter->spaceAfterSelectorSeparator(),
6✔
241
            $this->selectors
6✔
242
        );
243
        $result .= $outputFormat->getContentAfterDeclarationBlockSelectors();
6✔
244
        $result .= $formatter->spaceBeforeOpeningBrace() . '{';
6✔
245
        $result .= $this->ruleSet->render($outputFormat);
6✔
246
        $result .= '}';
6✔
247
        $result .= $outputFormat->getContentAfterDeclarationBlock();
6✔
248

249
        return $result;
6✔
250
    }
251
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc