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

MyIntervals / PHP-CSS-Parser / 13921221957

18 Mar 2025 10:41AM UTC coverage: 51.499% (-5.3%) from 56.839%
13921221957

push

github

web-flow
[TASK] Reduce and finetune the scope of `@covers` annotations (#1188)

The legacy tests are not very focused. Until we have split them
up, try to avoid false positives for code coverage.

Also add `@covers` annotations for the parent classes of the
tested classes.

945 of 1835 relevant lines covered (51.5%)

6.71 hits per line

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

74.75
/src/Value/Value.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Sabberworm\CSS\Value;
6

7
use Sabberworm\CSS\Parsing\ParserState;
8
use Sabberworm\CSS\Parsing\SourceException;
9
use Sabberworm\CSS\Parsing\UnexpectedEOFException;
10
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
11
use Sabberworm\CSS\Renderable;
12

13
/**
14
 * Abstract base class for specific classes of CSS values: `Size`, `Color`, `CSSString` and `URL`, and another
15
 * abstract subclass `ValueList`.
16
 */
17
abstract class Value implements Renderable
18
{
19
    /**
20
     * @var int<0, max>
21
     *
22
     * @internal since 8.8.0
23
     */
24
    protected $lineNumber;
25

26
    /**
27
     * @param int<0, max> $lineNumber
28
     */
29
    public function __construct(int $lineNumber = 0)
58✔
30
    {
31
        $this->lineNumber = $lineNumber;
58✔
32
    }
58✔
33

34
    /**
35
     * @param array<array-key, string> $listDelimiters
36
     *
37
     * @return Value|string
38
     *
39
     * @throws UnexpectedTokenException
40
     * @throws UnexpectedEOFException
41
     *
42
     * @internal since V8.8.0
43
     */
44
    public static function parseValue(ParserState $parserState, array $listDelimiters = [])
12✔
45
    {
46
        /** @var array<int, Value|string> $stack */
47
        $stack = [];
12✔
48
        $parserState->consumeWhiteSpace();
12✔
49
        //Build a list of delimiters and parsed values
50
        while (
51
        !($parserState->comes('}') || $parserState->comes(';') || $parserState->comes('!')
12✔
52
            || $parserState->comes(')')
12✔
53
            || $parserState->isEnd())
12✔
54
        ) {
55
            if (\count($stack) > 0) {
12✔
56
                $foundDelimiter = false;
11✔
57
                foreach ($listDelimiters as $delimiter) {
11✔
58
                    if ($parserState->comes($delimiter)) {
11✔
59
                        \array_push($stack, $parserState->consume($delimiter));
11✔
60
                        $parserState->consumeWhiteSpace();
11✔
61
                        $foundDelimiter = true;
11✔
62
                        break;
11✔
63
                    }
64
                }
65
                if (!$foundDelimiter) {
11✔
66
                    //Whitespace was the list delimiter
67
                    \array_push($stack, ' ');
10✔
68
                }
69
            }
70
            \array_push($stack, self::parsePrimitiveValue($parserState));
12✔
71
            $parserState->consumeWhiteSpace();
12✔
72
        }
73
        // Convert the list to list objects
74
        foreach ($listDelimiters as $delimiter) {
12✔
75
            $stackSize = \count($stack);
12✔
76
            if ($stackSize === 1) {
12✔
77
                return $stack[0];
12✔
78
            }
79
            $newStack = [];
11✔
80
            for ($offset = 0; $offset < $stackSize; ++$offset) {
11✔
81
                if ($offset === ($stackSize - 1) || $delimiter !== $stack[$offset + 1]) {
11✔
82
                    $newStack[] = $stack[$offset];
11✔
83
                    continue;
11✔
84
                }
85
                $length = 2; //Number of elements to be joined
11✔
86
                for ($i = $offset + 3; $i < $stackSize; $i += 2, ++$length) {
11✔
87
                    if ($delimiter !== $stack[$i]) {
10✔
88
                        break;
1✔
89
                    }
90
                }
91
                $list = new RuleValueList($delimiter, $parserState->currentLine());
11✔
92
                for ($i = $offset; $i - $offset < $length * 2; $i += 2) {
11✔
93
                    $list->addListComponent($stack[$i]);
11✔
94
                }
95
                $newStack[] = $list;
11✔
96
                $offset += $length * 2 - 2;
11✔
97
            }
98
            $stack = $newStack;
11✔
99
        }
100
        if (!isset($stack[0])) {
11✔
101
            throw new UnexpectedTokenException(
×
102
                " {$parserState->peek()} ",
×
103
                $parserState->peek(1, -1) . $parserState->peek(2),
×
104
                'literal',
×
105
                $parserState->currentLine()
×
106
            );
107
        }
108
        return $stack[0];
11✔
109
    }
110

111
    /**
112
     * @return CSSFunction|string
113
     *
114
     * @throws UnexpectedEOFException
115
     * @throws UnexpectedTokenException
116
     *
117
     * @internal since V8.8.0
118
     */
119
    public static function parseIdentifierOrFunction(ParserState $parserState, bool $ignoreCase = false)
12✔
120
    {
121
        $anchor = $parserState->anchor();
12✔
122
        $result = $parserState->parseIdentifier($ignoreCase);
12✔
123

124
        if ($parserState->comes('(')) {
12✔
125
            $anchor->backtrack();
12✔
126
            if ($parserState->streql('url', $result)) {
12✔
127
                $result = URL::parse($parserState);
×
128
            } elseif ($parserState->streql('calc', $result)) {
12✔
129
                $result = CalcFunction::parse($parserState);
1✔
130
            } else {
131
                $result = CSSFunction::parse($parserState, $ignoreCase);
11✔
132
            }
133
        }
134

135
        return $result;
12✔
136
    }
137

138
    /**
139
     * @return CSSFunction|CSSString|LineName|Size|URL|string
140
     *
141
     * @throws UnexpectedEOFException
142
     * @throws UnexpectedTokenException
143
     * @throws SourceException
144
     *
145
     * @internal since V8.8.0
146
     */
147
    public static function parsePrimitiveValue(ParserState $parserState)
12✔
148
    {
149
        $value = null;
12✔
150
        $parserState->consumeWhiteSpace();
12✔
151
        if (
152
            \is_numeric($parserState->peek())
12✔
153
            || ($parserState->comes('-.')
12✔
154
                && \is_numeric($parserState->peek(1, 2)))
12✔
155
            || (($parserState->comes('-') || $parserState->comes('.')) && \is_numeric($parserState->peek(1, 1)))
12✔
156
        ) {
157
            $value = Size::parse($parserState);
12✔
158
        } elseif ($parserState->comes('#') || $parserState->comes('rgb', true) || $parserState->comes('hsl', true)) {
12✔
159
            $value = Color::parse($parserState);
×
160
        } elseif ($parserState->comes("'") || $parserState->comes('"')) {
12✔
161
            $value = CSSString::parse($parserState);
×
162
        } elseif ($parserState->comes('progid:') && $parserState->getSettings()->usesLenientParsing()) {
12✔
163
            $value = self::parseMicrosoftFilter($parserState);
×
164
        } elseif ($parserState->comes('[')) {
12✔
165
            $value = LineName::parse($parserState);
×
166
        } elseif ($parserState->comes('U+')) {
12✔
167
            $value = self::parseUnicodeRangeValue($parserState);
×
168
        } else {
169
            $nextCharacter = $parserState->peek(1);
12✔
170
            try {
171
                $value = self::parseIdentifierOrFunction($parserState);
12✔
172
            } catch (UnexpectedTokenException $e) {
9✔
173
                if (\in_array($nextCharacter, ['+', '-', '*', '/'], true)) {
9✔
174
                    $value = $parserState->consume(1);
9✔
175
                } else {
176
                    throw $e;
×
177
                }
178
            }
179
        }
180
        $parserState->consumeWhiteSpace();
12✔
181

182
        return $value;
12✔
183
    }
184

185
    /**
186
     * @throws UnexpectedEOFException
187
     * @throws UnexpectedTokenException
188
     */
189
    private static function parseMicrosoftFilter(ParserState $parserState): CSSFunction
×
190
    {
191
        $function = $parserState->consumeUntil('(', false, true);
×
192
        $arguments = Value::parseValue($parserState, [',', '=']);
×
193
        return new CSSFunction($function, $arguments, ',', $parserState->currentLine());
×
194
    }
195

196
    /**
197
     * @throws UnexpectedEOFException
198
     * @throws UnexpectedTokenException
199
     */
200
    private static function parseUnicodeRangeValue(ParserState $parserState): string
×
201
    {
202
        $codepointMaxLength = 6; // Code points outside BMP can use up to six digits
×
203
        $range = '';
×
204
        $parserState->consume('U+');
×
205
        do {
206
            if ($parserState->comes('-')) {
×
207
                $codepointMaxLength = 13; // Max length is 2 six-digit code points + the dash(-) between them
×
208
            }
209
            $range .= $parserState->consume(1);
×
210
        } while (\strlen($range) < $codepointMaxLength && \preg_match('/[A-Fa-f0-9\\?-]/', $parserState->peek()));
×
211

212
        return "U+{$range}";
×
213
    }
214

215
    /**
216
     * @return int<0, max>
217
     */
218
    public function getLineNo(): int
6✔
219
    {
220
        return $this->lineNumber;
6✔
221
    }
222
}
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