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

MyIntervals / PHP-CSS-Parser / 13937293404

19 Mar 2025 02:06AM UTC coverage: 51.445% (-0.05%) from 51.499%
13937293404

Pull #1194

github

web-flow
Merge 73956e91f into 7e80f086f
Pull Request #1194: [TASK] Use delegation for `DeclarationBlock` -> `RuleSet`

24 of 36 new or added lines in 3 files covered. (66.67%)

6 existing lines in 1 file now uncovered.

961 of 1868 relevant lines covered (51.45%)

6.62 hits per line

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

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

3
declare(strict_types=1);
4

5
namespace Sabberworm\CSS\RuleSet;
6

7
use Sabberworm\CSS\Comment\Comment;
8
use Sabberworm\CSS\Comment\Commentable;
9
use Sabberworm\CSS\CSSList\CSSList;
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
class DeclarationBlock implements Commentable, Renderable
30
{
31
    /**
32
     * @var array<Selector|string>
33
     */
34
    private $selectors = [];
35

36
    /**
37
     * @var RuleSet
38
     */
39
    private $ruleSet;
40

41
    /**
42
     * @var int<0, max>
43
     */
44
    private $lineNumber;
45

46
    /**
47
     * @var list<Comment>
48
     */
49
    private $comments = [];
50

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

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

101
        RuleSet::parseRuleSet($parserState, $result->ruleSet);
6✔
102

103
        return $result;
6✔
104
    }
105

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

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

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

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

NEW
178
    public function getRuleSet(): RuleSet
×
179
    {
NEW
180
        return $this->ruleSet;
×
181
    }
182

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

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

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

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

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

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

257
        return $result;
6✔
258
    }
259

260
    /**
261
     * @param list<Comment> $comments
262
     */
263
    public function addComments(array $comments): void
6✔
264
    {
265
        $this->comments = \array_merge($this->comments, $comments);
6✔
266
    }
6✔
267

268
    /**
269
     * @return list<Comment>
270
     */
NEW
271
    public function getComments(): array
×
272
    {
NEW
273
        return $this->comments;
×
274
    }
275

276
    /**
277
     * @param list<Comment> $comments
278
     */
279
    public function setComments(array $comments): void
6✔
280
    {
281
        $this->comments = $comments;
6✔
282
    }
6✔
283
}
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