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

MyIntervals / PHP-CSS-Parser / 12947225707

24 Jan 2025 09:50AM UTC coverage: 41.942%. Remained the same
12947225707

push

github

web-flow
[CLEANUP] Avoid Hungarian notation in `Color` class (#802)

Also rename some variables to be more descriptive and/or avoid abbreviations.

Co-authored-by: Jake Hotson <jake.github@qzdesign.co.uk>

73 of 77 new or added lines in 1 file covered. (94.81%)

1 existing line in 1 file now uncovered.

864 of 2060 relevant lines covered (41.94%)

6.39 hits per line

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

91.3
/src/Value/Color.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Sabberworm\CSS\Value;
6

7
use Sabberworm\CSS\OutputFormat;
8
use Sabberworm\CSS\Parsing\ParserState;
9
use Sabberworm\CSS\Parsing\UnexpectedEOFException;
10
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
11

12
/**
13
 * `Color's can be input in the form #rrggbb, #rgb or schema(val1, val2, …) but are always stored as an array of
14
 * ('s' => val1, 'c' => val2, 'h' => val3, …) and output in the second form.
15
 */
16
class Color extends CSSFunction
17
{
18
    /**
19
     * @param array<int, Value|string> $colorValues
20
     * @param int $lineNumber
21
     */
22
    public function __construct(array $colorValues, $lineNumber = 0)
24✔
23
    {
24
        parent::__construct(\implode('', \array_keys($colorValues)), $colorValues, ',', $lineNumber);
24✔
25
    }
24✔
26

27
    /**
28
     * @throws UnexpectedEOFException
29
     * @throws UnexpectedTokenException
30
     */
31
    public static function parse(ParserState $parserState, bool $ignoreCase = false): CSSFunction
91✔
32
    {
33
        return
34
            $parserState->comes('#')
91✔
35
            ? self::parseHexColor($parserState)
25✔
36
            : self::parseColorFunction($parserState);
83✔
37
    }
38

39
    /**
40
     * @throws UnexpectedEOFException
41
     * @throws UnexpectedTokenException
42
     */
43
    private static function parseHexColor(ParserState $parserState): CSSFunction
11✔
44
    {
45
        $parserState->consume('#');
11✔
46
        $hexValue = $parserState->parseIdentifier(false);
11✔
47
        if ($parserState->strlen($hexValue) === 3) {
10✔
48
            $hexValue = $hexValue[0] . $hexValue[0] . $hexValue[1] . $hexValue[1] . $hexValue[2] . $hexValue[2];
1✔
49
        } elseif ($parserState->strlen($hexValue) === 4) {
9✔
50
            $hexValue = $hexValue[0] . $hexValue[0] . $hexValue[1] . $hexValue[1] . $hexValue[2] . $hexValue[2]
1✔
51
                . $hexValue[3] . $hexValue[3];
1✔
52
        }
53

54
        if ($parserState->strlen($hexValue) === 8) {
10✔
55
            $colorValues = [
56
                'r' => new Size(\intval($hexValue[0] . $hexValue[1], 16), null, true, $parserState->currentLine()),
2✔
57
                'g' => new Size(\intval($hexValue[2] . $hexValue[3], 16), null, true, $parserState->currentLine()),
2✔
58
                'b' => new Size(\intval($hexValue[4] . $hexValue[5], 16), null, true, $parserState->currentLine()),
2✔
59
                'a' => new Size(
2✔
60
                    \round(self::mapRange(\intval($hexValue[6] . $hexValue[7], 16), 0, 255, 0, 1), 2),
2✔
61
                    null,
2✔
62
                    true,
2✔
63
                    $parserState->currentLine()
2✔
64
                ),
65
            ];
66
        } elseif ($parserState->strlen($hexValue) === 6) {
8✔
67
            $colorValues = [
68
                'r' => new Size(\intval($hexValue[0] . $hexValue[1], 16), null, true, $parserState->currentLine()),
3✔
69
                'g' => new Size(\intval($hexValue[2] . $hexValue[3], 16), null, true, $parserState->currentLine()),
3✔
70
                'b' => new Size(\intval($hexValue[4] . $hexValue[5], 16), null, true, $parserState->currentLine()),
3✔
71
            ];
72
        } else {
73
            throw new UnexpectedTokenException(
5✔
74
                'Invalid hex color value',
5✔
75
                $hexValue,
76
                'custom',
5✔
77
                $parserState->currentLine()
5✔
78
            );
79
        }
80

81
        return new Color($colorValues, $parserState->currentLine());
5✔
82
    }
83

84
    /**
85
     * @throws UnexpectedEOFException
86
     * @throws UnexpectedTokenException
87
     */
88
    private static function parseColorFunction(ParserState $parserState): CSSFunction
62✔
89
    {
90
        $colorValues = [];
62✔
91

92
        $colorMode = $parserState->parseIdentifier(true);
62✔
93
        $parserState->consumeWhiteSpace();
62✔
94
        $parserState->consume('(');
62✔
95

96
        // CSS Color Module Level 4 says that `rgb` and `rgba` are now aliases; likewise `hsl` and `hsla`.
97
        // So, attempt to parse with the `a`, and allow for it not being there.
98
        switch ($colorMode) {
62✔
99
            case 'rgb':
62✔
100
                $colorModeForParsing = 'rgba';
24✔
101
                $mayHaveOptionalAlpha = true;
24✔
102
                break;
24✔
103
            case 'hsl':
38✔
104
                $colorModeForParsing = 'hsla';
14✔
105
                $mayHaveOptionalAlpha = true;
14✔
106
                break;
14✔
107
            case 'rgba':
24✔
108
                // This is handled identically to the following case.
109
            case 'hsla':
3✔
110
                $colorModeForParsing = $colorMode;
24✔
111
                $mayHaveOptionalAlpha = true;
24✔
112
                break;
24✔
113
            default:
NEW
114
                $colorModeForParsing = $colorMode;
×
115
                $mayHaveOptionalAlpha = false;
×
116
        }
117

118
        $containsVar = false;
62✔
119
        $isLegacySyntax = false;
62✔
120
        $expectedArgumentCount = $parserState->strlen($colorModeForParsing);
62✔
121
        for ($argumentIndex = 0; $argumentIndex < $expectedArgumentCount; ++$argumentIndex) {
62✔
122
            $parserState->consumeWhiteSpace();
62✔
123
            if ($parserState->comes('var')) {
62✔
124
                $colorValues[$colorModeForParsing[$argumentIndex]] = CSSFunction::parseIdentifierOrFunction($parserState);
29✔
125
                $containsVar = true;
29✔
126
            } else {
127
                $colorValues[$colorModeForParsing[$argumentIndex]] = Size::parse($parserState, true);
61✔
128
            }
129

130
            // This must be done first, to consume comments as well, so that the `comes` test will work.
131
            $parserState->consumeWhiteSpace();
62✔
132

133
            // With a `var` argument, the function can have fewer arguments.
134
            // And as of CSS Color Module Level 4, the alpha argument is optional.
135
            $canCloseNow =
136
                $containsVar ||
62✔
137
                ($mayHaveOptionalAlpha && $argumentIndex >= $expectedArgumentCount - 2);
62✔
138
            if ($canCloseNow && $parserState->comes(')')) {
62✔
139
                break;
48✔
140
            }
141

142
            // "Legacy" syntax is comma-delimited.
143
            // "Modern" syntax is space-delimited, with `/` as alpha delimiter.
144
            // They cannot be mixed.
145
            if ($argumentIndex === 0) {
61✔
146
                // An immediate closing parenthesis is not valid.
147
                if ($parserState->comes(')')) {
61✔
148
                    throw new UnexpectedTokenException(
4✔
149
                        'Color function with no arguments',
4✔
150
                        '',
4✔
151
                        'custom',
4✔
152
                        $parserState->currentLine()
4✔
153
                    );
154
                }
155
                $isLegacySyntax = $parserState->comes(',');
57✔
156
            }
157

158
            if ($isLegacySyntax && $argumentIndex < ($expectedArgumentCount - 1)) {
57✔
159
                $parserState->consume(',');
35✔
160
            }
161

162
            // In the "modern" syntax, the alpha value must be delimited with `/`.
163
            if (!$isLegacySyntax) {
57✔
164
                if ($containsVar) {
22✔
165
                    // If the `var` substitution encompasses more than one argument,
166
                    // the alpha deliminator may come at any time.
167
                    if ($parserState->comes('/')) {
9✔
168
                        $parserState->consume('/');
9✔
169
                    }
170
                } elseif (($colorModeForParsing[$argumentIndex + 1] ?? '') === 'a') {
17✔
171
                    // Alpha value is the next expected argument.
172
                    // Since a closing parenthesis was not found, a `/` separator is now required.
173
                    $parserState->consume('/');
7✔
174
                }
175
            }
176
        }
177
        $parserState->consume(')');
50✔
178

179
        return
180
            $containsVar
48✔
181
            ? new CSSFunction($colorMode, \array_values($colorValues), ',', $parserState->currentLine())
29✔
182
            : new Color($colorValues, $parserState->currentLine());
48✔
183
    }
184

185
    private static function mapRange(float $value, float $fromMin, float $fromMax, float $toMin, float $toMax): float
2✔
186
    {
187
        $fromRange = $fromMax - $fromMin;
2✔
188
        $toRange = $toMax - $toMin;
2✔
189
        $multiplier = $toRange / $fromRange;
2✔
190
        $newValue = $value - $fromMin;
2✔
191
        $newValue *= $multiplier;
2✔
192
        return $newValue + $toMin;
2✔
193
    }
194

195
    /**
196
     * @return array<int, Value|string>
197
     */
198
    public function getColor()
×
199
    {
200
        return $this->aComponents;
×
201
    }
202

203
    /**
204
     * @param array<int, Value|string> $colorValues
205
     */
NEW
206
    public function setColor(array $colorValues): void
×
207
    {
NEW
208
        $this->setName(\implode('', \array_keys($colorValues)));
×
NEW
209
        $this->aComponents = $colorValues;
×
UNCOV
210
    }
×
211

212
    /**
213
     * @return string
214
     */
215
    public function getColorDescription()
×
216
    {
217
        return $this->getName();
×
218
    }
219

220
    public function __toString(): string
24✔
221
    {
222
        return $this->render(new OutputFormat());
24✔
223
    }
224

225
    public function render(OutputFormat $outputFormat): string
24✔
226
    {
227
        // Shorthand RGB color values
228
        if ($outputFormat->getRGBHashNotation() && \implode('', \array_keys($this->aComponents)) === 'rgb') {
24✔
229
            $result = \sprintf(
7✔
230
                '%02x%02x%02x',
7✔
231
                $this->aComponents['r']->getSize(),
7✔
232
                $this->aComponents['g']->getSize(),
7✔
233
                $this->aComponents['b']->getSize()
7✔
234
            );
235
            return '#' . (($result[0] == $result[1]) && ($result[2] == $result[3]) && ($result[4] == $result[5])
7✔
236
                    ? "$result[0]$result[2]$result[4]" : $result);
7✔
237
        }
238
        return parent::render($outputFormat);
17✔
239
    }
240
}
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