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

MyIntervals / PHP-CSS-Parser / 13598851535

01 Mar 2025 12:24AM UTC coverage: 55.753% (+0.5%) from 55.282%
13598851535

Pull #1025

github

web-flow
Merge efe60346a into 2093b19f0
Pull Request #1025: [TASK] Add tests for the selector specificity

1066 of 1912 relevant lines covered (55.75%)

12.33 hits per line

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

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

3
declare(strict_types=1);
4

5
namespace Sabberworm\CSS\Property;
6

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

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

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

67
    /**
68
     * @var string
69
     */
70
    private $selector;
71

72
    /**
73
     * @var int|null
74
     */
75
    private $specificity;
76

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

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

101
    /**
102
     * @return string
103
     */
104
    public function getSelector()
3✔
105
    {
106
        return $this->selector;
3✔
107
    }
108

109
    /**
110
     * @param string $selector
111
     */
112
    public function setSelector($selector): void
17✔
113
    {
114
        $this->selector = \trim($selector);
17✔
115
        $this->specificity = null;
17✔
116
    }
17✔
117

118
    /**
119
     * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead.
120
     */
121
    public function __toString(): string
1✔
122
    {
123
        return $this->getSelector();
1✔
124
    }
125

126
    /**
127
     * @return int
128
     */
129
    public function getSpecificity()
14✔
130
    {
131
        if ($this->specificity === null) {
14✔
132
            $a = 0;
14✔
133
            /// @todo should exclude \# as well as "#"
134
            $aMatches = null;
14✔
135
            $b = \substr_count($this->selector, '#');
14✔
136
            $c = \preg_match_all(self::NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX, $this->selector, $aMatches);
14✔
137
            $d = \preg_match_all(self::ELEMENTS_AND_PSEUDO_ELEMENTS_RX, $this->selector, $aMatches);
14✔
138
            $this->specificity = ($a * 1000) + ($b * 100) + ($c * 10) + $d;
14✔
139
        }
140
        return $this->specificity;
14✔
141
    }
142
}
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

© 2025 Coveralls, Inc