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

MyIntervals / PHP-CSS-Parser / 13630858383

03 Mar 2025 12:26PM UTC coverage: 55.749% (-0.06%) from 55.808%
13630858383

push

github

web-flow
[CLEANUP] Improve some variable names in `RuleSet` (#1039)

Part of #756.

0 of 19 new or added lines in 1 file covered. (0.0%)

1 existing line in 1 file now uncovered.

1057 of 1896 relevant lines covered (55.75%)

12.18 hits per line

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

0.0
/src/RuleSet/RuleSet.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\OutputFormat;
10
use Sabberworm\CSS\Parsing\ParserState;
11
use Sabberworm\CSS\Parsing\UnexpectedEOFException;
12
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
13
use Sabberworm\CSS\Renderable;
14
use Sabberworm\CSS\Rule\Rule;
15

16
/**
17
 * This class is a container for individual 'Rule's.
18
 *
19
 * The most common form of a rule set is one constrained by a selector, i.e., a `DeclarationBlock`.
20
 * However, unknown `AtRule`s (like `@font-face`) are rule sets as well.
21
 *
22
 * If you want to manipulate a `RuleSet`, use the methods `addRule(Rule $rule)`, `getRules()` and `removeRule($rule)`
23
 * (which accepts either a `Rule` or a rule name; optionally suffixed by a dash to remove all related rules).
24
 */
25
abstract class RuleSet implements Renderable, Commentable
26
{
27
    /**
28
     * @var array<string, Rule>
29
     */
30
    private $rules = [];
31

32
    /**
33
     * @var int<0, max>
34
     *
35
     * @internal since 8.8.0
36
     */
37
    protected $lineNumber;
38

39
    /**
40
     * @var array<array-key, Comment>
41
     *
42
     * @internal since 8.8.0
43
     */
44
    protected $comments = [];
45

46
    /**
47
     * @param int<0, max> $lineNumber
48
     */
49
    public function __construct($lineNumber = 0)
×
50
    {
51
        $this->lineNumber = $lineNumber;
×
52
    }
×
53

54
    /**
55
     * @throws UnexpectedTokenException
56
     * @throws UnexpectedEOFException
57
     *
58
     * @internal since V8.8.0
59
     */
60
    public static function parseRuleSet(ParserState $parserState, RuleSet $ruleSet): void
×
61
    {
62
        while ($parserState->comes(';')) {
×
63
            $parserState->consume(';');
×
64
        }
65
        while (!$parserState->comes('}')) {
×
66
            $rule = null;
×
67
            if ($parserState->getSettings()->usesLenientParsing()) {
×
68
                try {
69
                    $rule = Rule::parse($parserState);
×
70
                } catch (UnexpectedTokenException $e) {
×
71
                    try {
72
                        $consumedText = $parserState->consumeUntil(["\n", ';', '}'], true);
×
73
                        // We need to “unfind” the matches to the end of the ruleSet as this will be matched later
74
                        if ($parserState->streql(\substr($consumedText, -1), '}')) {
×
75
                            $parserState->backtrack(1);
×
76
                        } else {
77
                            while ($parserState->comes(';')) {
×
78
                                $parserState->consume(';');
×
79
                            }
80
                        }
81
                    } catch (UnexpectedTokenException $e) {
×
82
                        // We’ve reached the end of the document. Just close the RuleSet.
83
                        return;
×
84
                    }
85
                }
86
            } else {
87
                $rule = Rule::parse($parserState);
×
88
            }
89
            if ($rule instanceof Rule) {
×
90
                $ruleSet->addRule($rule);
×
91
            }
92
        }
93
        $parserState->consume('}');
×
94
    }
×
95

96
    /**
97
     * @return int<0, max>
98
     */
99
    public function getLineNo(): int
×
100
    {
101
        return $this->lineNumber;
×
102
    }
103

104
    public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void
×
105
    {
NEW
106
        $propertyName = $ruleToAdd->getRule();
×
NEW
107
        if (!isset($this->rules[$propertyName])) {
×
NEW
108
            $this->rules[$propertyName] = [];
×
109
        }
110

NEW
111
        $position = \count($this->rules[$propertyName]);
×
112

113
        if ($sibling !== null) {
×
NEW
114
            $iSiblingPos = \array_search($sibling, $this->rules[$propertyName], true);
×
115
            if ($iSiblingPos !== false) {
×
116
                $position = $iSiblingPos;
×
117
                $ruleToAdd->setPosition($sibling->getLineNo(), $sibling->getColNo() - 1);
×
118
            }
119
        }
120
        if ($ruleToAdd->getLineNo() === 0 && $ruleToAdd->getColNo() === 0) {
×
121
            //this node is added manually, give it the next best line
122
            $rules = $this->getRules();
×
123
            $pos = \count($rules);
×
124
            if ($pos > 0) {
×
125
                $last = $rules[$pos - 1];
×
126
                $ruleToAdd->setPosition($last->getLineNo() + 1, 0);
×
127
            }
128
        }
129

NEW
130
        \array_splice($this->rules[$propertyName], $position, 0, [$ruleToAdd]);
×
131
    }
×
132

133
    /**
134
     * Returns all rules matching the given rule name
135
     *
136
     * @example $ruleSet->getRules('font') // returns array(0 => $rule, …) or array().
137
     *
138
     * @example $ruleSet->getRules('font-')
139
     *          //returns an array of all rules either beginning with font- or matching font.
140
     *
141
     * @param Rule|string|null $searchPattern
142
     *        Pattern to search for. If null, returns all rules.
143
     *        If the pattern ends with a dash, all rules starting with the pattern are returned
144
     *        as well as one matching the pattern with the dash excluded.
145
     *        Passing a Rule behaves like calling `getRules($mRule->getRule())`.
146
     *
147
     * @return array<int, Rule>
148
     */
149
    public function getRules($searchPattern = null)
×
150
    {
151
        if ($searchPattern instanceof Rule) {
×
152
            $searchPattern = $searchPattern->getRule();
×
153
        }
154
        /** @var array<int, Rule> $result */
155
        $result = [];
×
NEW
156
        foreach ($this->rules as $propertyName => $rules) {
×
157
            // Either no search rule is given or the search rule matches the found rule exactly
158
            // or the search rule ends in “-” and the found rule starts with the search rule.
159
            if (
NEW
160
                !$searchPattern || $propertyName === $searchPattern
×
161
                || (
162
                    \strrpos($searchPattern, '-') === \strlen($searchPattern) - \strlen('-')
×
NEW
163
                    && (\strpos($propertyName, $searchPattern) === 0
×
NEW
164
                        || $propertyName === \substr($searchPattern, 0, -1))
×
165
                )
166
            ) {
167
                $result = \array_merge($result, $rules);
×
168
            }
169
        }
170
        \usort($result, static function (Rule $first, Rule $second): int {
171
            if ($first->getLineNo() === $second->getLineNo()) {
×
172
                return $first->getColNo() - $second->getColNo();
×
173
            }
174
            return $first->getLineNo() - $second->getLineNo();
×
175
        });
×
176
        return $result;
×
177
    }
178

179
    /**
180
     * Overrides all the rules of this set.
181
     *
182
     * @param array<array-key, Rule> $rules The rules to override with.
183
     */
184
    public function setRules(array $rules): void
×
185
    {
186
        $this->rules = [];
×
187
        foreach ($rules as $rule) {
×
188
            $this->addRule($rule);
×
189
        }
190
    }
×
191

192
    /**
193
     * Returns all rules matching the given pattern and returns them in an associative array with the rule’s name
194
     * as keys. This method exists mainly for backwards-compatibility and is really only partially useful.
195
     *
196
     * Note: This method loses some information: Calling this (with an argument of `background-`) on a declaration block
197
     * like `{ background-color: green; background-color; rgba(0, 127, 0, 0.7); }` will only yield an associative array
198
     * containing the rgba-valued rule while `getRules()` would yield an indexed array containing both.
199
     *
200
     * @param Rule|string|null $searchPattern
201
     *        Pattern to search for. If null, returns all rules. If the pattern ends with a dash,
202
     *        all rules starting with the pattern are returned as well as one matching the pattern with the dash
203
     *        excluded. Passing a Rule behaves like calling `getRules($mRule->getRule())`.
204
     *
205
     * @return array<string, Rule>
206
     */
207
    public function getRulesAssoc($searchPattern = null)
×
208
    {
209
        /** @var array<string, Rule> $result */
210
        $result = [];
×
211
        foreach ($this->getRules($searchPattern) as $rule) {
×
212
            $result[$rule->getRule()] = $rule;
×
213
        }
214
        return $result;
×
215
    }
216

217
    /**
218
     * Removes a rule from this RuleSet. This accepts all the possible values that `getRules()` accepts.
219
     *
220
     * If given a Rule, it will only remove this particular rule (by identity).
221
     * If given a name, it will remove all rules by that name.
222
     *
223
     * Note: this is different from pre-v.2.0 behaviour of PHP-CSS-Parser, where passing a Rule instance would
224
     * remove all rules with the same name. To get the old behaviour, use `removeRule($rule->getRule())`.
225
     *
226
     * @param Rule|string|null $searchPattern
227
     *        pattern to remove. If null, all rules are removed. If the pattern ends in a dash,
228
     *        all rules starting with the pattern are removed as well as one matching the pattern with the dash
229
     *        excluded. Passing a Rule behaves matches by identity.
230
     */
231
    public function removeRule($searchPattern): void
×
232
    {
233
        if ($searchPattern instanceof Rule) {
×
NEW
234
            $propertyName = $searchPattern->getRule();
×
NEW
235
            if (!isset($this->rules[$propertyName])) {
×
UNCOV
236
                return;
×
237
            }
NEW
238
            foreach ($this->rules[$propertyName] as $key => $rule) {
×
239
                if ($rule === $searchPattern) {
×
NEW
240
                    unset($this->rules[$propertyName][$key]);
×
241
                }
242
            }
243
        } else {
NEW
244
            foreach ($this->rules as $propertyName => $rules) {
×
245
                // Either no search rule is given or the search rule matches the found rule exactly
246
                // or the search rule ends in “-” and the found rule starts with the search rule or equals it
247
                // (without the trailing dash).
248
                if (
NEW
249
                    !$searchPattern || $propertyName === $searchPattern
×
250
                    || (\strrpos($searchPattern, '-') === \strlen($searchPattern) - \strlen('-')
×
NEW
251
                        && (\strpos($propertyName, $searchPattern) === 0
×
NEW
252
                            || $propertyName === \substr($searchPattern, 0, -1)))
×
253
                ) {
NEW
254
                    unset($this->rules[$propertyName]);
×
255
                }
256
            }
257
        }
258
    }
×
259

260
    /**
261
     * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead.
262
     */
263
    public function __toString(): string
×
264
    {
265
        return $this->render(new OutputFormat());
×
266
    }
267

268
    /**
269
     * @return string
270
     */
271
    protected function renderRules(OutputFormat $outputFormat)
×
272
    {
273
        $result = '';
×
274
        $isFirst = true;
×
275
        $nextLevelFormat = $outputFormat->nextLevel();
×
276
        foreach ($this->rules as $rules) {
×
277
            foreach ($rules as $rule) {
×
278
                $renderedRule = $nextLevelFormat->safely(static function () use ($rule, $nextLevelFormat): string {
279
                    return $rule->render($nextLevelFormat);
×
280
                });
×
281
                if ($renderedRule === null) {
×
282
                    continue;
×
283
                }
284
                if ($isFirst) {
×
285
                    $isFirst = false;
×
286
                    $result .= $nextLevelFormat->spaceBeforeRules();
×
287
                } else {
288
                    $result .= $nextLevelFormat->spaceBetweenRules();
×
289
                }
290
                $result .= $renderedRule;
×
291
            }
292
        }
293

294
        if (!$isFirst) {
×
295
            // Had some output
296
            $result .= $outputFormat->spaceAfterRules();
×
297
        }
298

299
        return $outputFormat->removeLastSemicolon($result);
×
300
    }
301

302
    /**
303
     * @param array<string, Comment> $comments
304
     */
305
    public function addComments(array $comments): void
×
306
    {
307
        $this->comments = \array_merge($this->comments, $comments);
×
308
    }
×
309

310
    /**
311
     * @return array<string, Comment>
312
     */
313
    public function getComments(): array
×
314
    {
315
        return $this->comments;
×
316
    }
317

318
    /**
319
     * @param array<string, Comment> $comments
320
     */
321
    public function setComments(array $comments): void
×
322
    {
323
        $this->comments = $comments;
×
324
    }
×
325
}
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