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

MyIntervals / PHP-CSS-Parser / 14183020401

31 Mar 2025 10:51PM UTC coverage: 49.179% (-4.0%) from 53.134%
14183020401

Pull #1194

github

web-flow
Merge 4d7831a98 into 36ed5cdf1
Pull Request #1194: [TASK] Use delegation for `DeclarationBlock` -> `RuleSet`

16 of 28 new or added lines in 3 files covered. (57.14%)

73 existing lines in 1 file now uncovered.

899 of 1828 relevant lines covered (49.18%)

6.9 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\CSSList\CSSList;
9
use Sabberworm\CSS\CSSList\CSSListItem;
10
use Sabberworm\CSS\CSSList\KeyFrame;
11
use Sabberworm\CSS\OutputFormat;
12
use Sabberworm\CSS\Parsing\OutputException;
13
use Sabberworm\CSS\Parsing\ParserState;
14
use Sabberworm\CSS\Parsing\UnexpectedEOFException;
15
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
16
use Sabberworm\CSS\Property\KeyframeSelector;
17
use Sabberworm\CSS\Property\Selector;
18
use Sabberworm\CSS\Renderable;
19
use Sabberworm\CSS\Rule\Rule;
20

21
/**
22
 * This class includes a `RuleSet` constrained by a `Selector`.
23
 *
24
 * It contains an array of selector objects (comma-separated in the CSS) as well as the rules to be applied to the
25
 * matching elements.
26
 *
27
 * Declaration blocks usually appear directly inside a `Document` or another `CSSList` (mostly a `MediaQuery`).
28
 *
29
 * Note that `CSSListItem` extends both `Commentable` and `Renderable`, so those interfaces must also be implemented.
30
 */
31
class DeclarationBlock implements CSSListItem
32
{
33
    use CommentContainer;
34

35
    /**
36
     * @var array<Selector|string>
37
     */
38
    private $selectors = [];
39

40
    /**
41
     * @var RuleSet
42
     */
43
    private $ruleSet;
44

45
    /**
46
     * @var int<0, max>
47
     */
48
    private $lineNumber;
49

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

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

98
        RuleSet::parseRuleSet($parserState, $result->ruleSet);
6✔
99

100
        return $result;
6✔
101
    }
102

103
    /**
104
     * @return int<0, max>
105
     */
NEW
106
    public function getLineNo(): int
×
107
    {
NEW
108
        return $this->lineNumber;
×
109
    }
110

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

148
    /**
149
     * Remove one of the selectors of the block.
150
     *
151
     * @param Selector|string $selectorToRemove
152
     */
153
    public function removeSelector($selectorToRemove): bool
×
154
    {
155
        if ($selectorToRemove instanceof Selector) {
×
156
            $selectorToRemove = $selectorToRemove->getSelector();
×
157
        }
158
        foreach ($this->selectors as $key => $selector) {
×
159
            if ($selector->getSelector() === $selectorToRemove) {
×
160
                unset($this->selectors[$key]);
×
161
                return true;
×
162
            }
163
        }
164
        return false;
×
165
    }
166

167
    /**
168
     * @return array<Selector>
169
     */
170
    public function getSelectors(): array
×
171
    {
172
        return $this->selectors;
×
173
    }
174

NEW
175
    public function getRuleSet(): RuleSet
×
176
    {
NEW
177
        return $this->ruleSet;
×
178
    }
179

180
    /**
181
     * @see RuleSet::addRule()
182
     */
183
    public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void
2✔
184
    {
185
        $this->ruleSet->addRule($ruleToAdd, $sibling);
2✔
186
    }
2✔
187

188
    /**
189
     * @see RuleSet::getRules()
190
     *
191
     * @param Rule|string|null $searchPattern
192
     *
193
     * @return array<int<0, max>, Rule>
194
     */
195
    public function getRules($searchPattern = null): array
2✔
196
    {
197
        return $this->ruleSet->getRules($searchPattern);
2✔
198
    }
199

200
    /**
201
     * @see RuleSet::setRules()
202
     *
203
     * @param array<Rule> $rules
204
     */
205
    public function setRules(array $rules): void
1✔
206
    {
207
        $this->ruleSet->setRules($rules);
1✔
208
    }
1✔
209

210
    /**
211
     * @see RuleSet::getRulesAssoc()
212
     *
213
     * @param Rule|string|null $searchPattern
214
     *
215
     * @return array<string, Rule>
216
     */
NEW
217
    public function getRulesAssoc($searchPattern = null): array
×
218
    {
NEW
219
        return $this->ruleSet->getRulesAssoc($searchPattern);
×
220
    }
221

222
    /**
223
     * @see RuleSet::removeRule()
224
     *
225
     * @param Rule|string|null $searchPattern
226
     */
NEW
227
    public function removeRule($searchPattern): void
×
228
    {
NEW
229
        $this->ruleSet->removeRule($searchPattern);
×
NEW
230
    }
×
231

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

256
        return $result;
6✔
257
    }
258
}
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