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

MyIntervals / PHP-CSS-Parser / 13603934497

01 Mar 2025 10:36AM UTC coverage: 55.799%. Remained the same
13603934497

push

github

web-flow
[TASK] Add more native type declarations for `Selector` (#1044)

The return type of `::isValid()` cannot use a native
return type declaration yet as the method's return values
currently are of a different type (#1043).

Part of #811

4 of 5 new or added lines in 1 file covered. (80.0%)

1068 of 1914 relevant lines covered (55.8%)

12.34 hits per line

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

88.46
/src/Property/Selector.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Sabberworm\CSS\Property;
6

7
use Sabberworm\CSS\OutputFormat;
8
use Sabberworm\CSS\Renderable;
9

10
/**
11
 * Class representing a single CSS selector. Selectors have to be split by the comma prior to being passed into this
12
 * class.
13
 */
14
class Selector implements Renderable
15
{
16
    /**
17
     * regexp for specificity calculations
18
     *
19
     * @var string
20
     */
21
    private const NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX = '/
22
        (\\.[\\w]+)                   # classes
23
        |
24
        \\[(\\w+)                     # attributes
25
        |
26
        (\\:(                         # pseudo classes
27
            link|visited|active
28
            |hover|focus
29
            |lang
30
            |target
31
            |enabled|disabled|checked|indeterminate
32
            |root
33
            |nth-child|nth-last-child|nth-of-type|nth-last-of-type
34
            |first-child|last-child|first-of-type|last-of-type
35
            |only-child|only-of-type
36
            |empty|contains
37
        ))
38
        /ix';
39

40
    /**
41
     * regexp for specificity calculations
42
     *
43
     * @var string
44
     */
45
    private const ELEMENTS_AND_PSEUDO_ELEMENTS_RX = '/
46
        ((^|[\\s\\+\\>\\~]+)[\\w]+   # elements
47
        |
48
        \\:{1,2}(                    # pseudo-elements
49
            after|before|first-letter|first-line|selection
50
        ))
51
        /ix';
52

53
    /**
54
     * regexp for specificity calculations
55
     *
56
     * @var string
57
     *
58
     * @internal since 8.5.2
59
     */
60
    public const SELECTOR_VALIDATION_RX = '/
61
        ^(
62
            (?:
63
                [a-zA-Z0-9\\x{00A0}-\\x{FFFF}_^$|*="\'~\\[\\]()\\-\\s\\.:#+>]* # any sequence of valid unescaped characters
64
                (?:\\\\.)?                                                     # a single escaped character
65
                (?:([\'"]).*?(?<!\\\\)\\2)?                                    # a quoted text like [id="example"]
66
            )*
67
        )$
68
        /ux';
69

70
    /**
71
     * @var string
72
     */
73
    private $selector;
74

75
    /**
76
     * @var int|null
77
     */
78
    private $specificity;
79

80
    /**
81
     * @return bool
82
     *
83
     * @internal since V8.8.0
84
     */
NEW
85
    public static function isValid(string $selector)
×
86
    {
87
        return \preg_match(static::SELECTOR_VALIDATION_RX, $selector);
×
88
    }
89

90
    /**
91
     * @param bool $calculateSpecificity @deprecated since V8.8.0, will be removed in V9.0.0
92
     */
93
    public function __construct(string $selector, bool $calculateSpecificity = false)
22✔
94
    {
95
        $this->setSelector($selector);
22✔
96
        if ($calculateSpecificity) {
22✔
97
            $this->getSpecificity();
×
98
        }
99
    }
22✔
100

101
    public function getSelector(): string
7✔
102
    {
103
        return $this->selector;
7✔
104
    }
105

106
    public function setSelector(string $selector): void
22✔
107
    {
108
        $this->selector = \trim($selector);
22✔
109
        $this->specificity = null;
22✔
110
    }
22✔
111

112
    /**
113
     * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead.
114
     */
115
    public function __toString(): string
1✔
116
    {
117
        return $this->getSelector();
1✔
118
    }
119

120
    /**
121
     * @return int<0, max>
122
     */
123
    public function getSpecificity(): int
14✔
124
    {
125
        if ($this->specificity === null) {
14✔
126
            $a = 0;
14✔
127
            /// @todo should exclude \# as well as "#"
128
            $aMatches = null;
14✔
129
            $b = \substr_count($this->selector, '#');
14✔
130
            $c = \preg_match_all(self::NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX, $this->selector, $aMatches);
14✔
131
            $d = \preg_match_all(self::ELEMENTS_AND_PSEUDO_ELEMENTS_RX, $this->selector, $aMatches);
14✔
132
            $this->specificity = ($a * 1000) + ($b * 100) + ($c * 10) + $d;
14✔
133
        }
134
        return $this->specificity;
14✔
135
    }
136

137
    public function render(OutputFormat $outputFormat): string
4✔
138
    {
139
        return $this->getSelector();
4✔
140
    }
141
}
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