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

MyIntervals / PHP-CSS-Parser / 13595516655

28 Feb 2025 07:56PM UTC coverage: 55.225% (-0.06%) from 55.282%
13595516655

Pull #1004

github

web-flow
Merge 65c893ae5 into 19ffd076e
Pull Request #1004: [CLEANUP] Avoid Hungarian notation in `RuleSet`

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

4 existing lines in 1 file now uncovered.

1057 of 1914 relevant lines covered (55.22%)

12.19 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
     * the rules in this rule set, using the property or rule name as the key
29
     *
30
     * @var array<string, Rule>
31
     */
32
    private $rules = [];
33

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

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

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

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

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

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

NEW
113
        $position = \count($this->rules[$propertyOrRuleName]);
×
114

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

NEW
132
        \array_splice($this->rules[$propertyOrRuleName], $position, 0, [$ruleToAdd]);
×
133
    }
×
134

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

UNCOV
179
        return $result;
×
180
    }
181

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

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

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

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

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

297
        if (!$isFirst) {
×
298
            // Had some output
299
            $result .= $outputFormat->spaceAfterRules();
×
300
        }
301

302
        return $outputFormat->removeLastSemicolon($result);
×
303
    }
304

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

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

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