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

MyIntervals / PHP-CSS-Parser / 15946589277

28 Jun 2025 05:35PM UTC coverage: 57.912% (+0.006%) from 57.906%
15946589277

Pull #1294

github

web-flow
Merge 3fe6d1317 into 11e634c0b
Pull Request #1294: [CLEANUP] Tidy up `DeclarationBlock::parse()`

5 of 11 new or added lines in 1 file covered. (45.45%)

11 existing lines in 1 file now uncovered.

1054 of 1820 relevant lines covered (57.91%)

16.67 hits per line

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

66.22
/src/RuleSet/DeclarationBlock.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Sabberworm\CSS\RuleSet;
6

7
use Sabberworm\CSS\CSSList\CSSList;
8
use Sabberworm\CSS\CSSList\KeyFrame;
9
use Sabberworm\CSS\OutputFormat;
10
use Sabberworm\CSS\Parsing\OutputException;
11
use Sabberworm\CSS\Parsing\ParserState;
12
use Sabberworm\CSS\Parsing\UnexpectedEOFException;
13
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
14
use Sabberworm\CSS\Property\KeyframeSelector;
15
use Sabberworm\CSS\Property\Selector;
16

17
/**
18
 * This class represents a `RuleSet` constrained by a `Selector`.
19
 *
20
 * It contains an array of selector objects (comma-separated in the CSS) as well as the rules to be applied to the
21
 * matching elements.
22
 *
23
 * Declaration blocks usually appear directly inside a `Document` or another `CSSList` (mostly a `MediaQuery`).
24
 */
25
class DeclarationBlock extends RuleSet
26
{
27
    /**
28
     * @var array<Selector|string>
29
     */
30
    private $selectors = [];
31

32
    /**
33
     * @throws UnexpectedTokenException
34
     * @throws UnexpectedEOFException
35
     *
36
     * @internal since V8.8.0
37
     */
38
    public static function parse(ParserState $parserState, ?CSSList $list = null): ?DeclarationBlock
9✔
39
    {
40
        $comments = [];
9✔
41
        $result = new DeclarationBlock($parserState->currentLine());
9✔
42
        try {
43
            $selectorParts = [];
9✔
44
            do {
45
                $selectorParts[] = $parserState->consume(1)
9✔
46
                    . $parserState->consumeUntil(['{', '}', '\'', '"'], false, false, $comments);
9✔
47
                $nextCharacter = $parserState->peek();
9✔
48
                switch ($nextCharacter) {
9✔
49
                    case '\'':
9✔
50
                        // The fallthrough is intentional.
51
                    case '"':
9✔
NEW
52
                        if (!isset($stringWrapperCharacter)) {
×
NEW
53
                            $stringWrapperCharacter = $nextCharacter;
×
NEW
54
                        } elseif ($stringWrapperCharacter === $nextCharacter) {
×
NEW
55
                            if (\substr(\end($selectorParts), -1) !== '\\') {
×
NEW
56
                                unset($stringWrapperCharacter);
×
57
                            }
58
                        }
NEW
59
                        break;
×
60
                }
61
            } while (!\in_array($nextCharacter, ['{', '}'], true) || isset($stringWrapperCharacter));
9✔
62
            $result->setSelectors(\implode('', $selectorParts), $list);
9✔
63
            if ($parserState->comes('{')) {
6✔
64
                $parserState->consume(1);
6✔
65
            }
66
        } catch (UnexpectedTokenException $e) {
3✔
67
            if ($parserState->getSettings()->usesLenientParsing()) {
3✔
68
                if (!$parserState->comes('}')) {
3✔
69
                    $parserState->consumeUntil('}', false, true);
1✔
70
                }
71
                return null;
3✔
72
            } else {
UNCOV
73
                throw $e;
×
74
            }
75
        }
76
        $result->setComments($comments);
6✔
77
        RuleSet::parseRuleSet($parserState, $result);
6✔
78
        return $result;
6✔
79
    }
80

81
    /**
82
     * @param array<Selector|string>|string $selectors
83
     *
84
     * @throws UnexpectedTokenException
85
     */
86
    public function setSelectors($selectors, ?CSSList $list = null): void
10✔
87
    {
88
        if (\is_array($selectors)) {
10✔
89
            $this->selectors = $selectors;
1✔
90
        } else {
91
            $this->selectors = \explode(',', $selectors);
9✔
92
        }
93
        foreach ($this->selectors as $key => $selector) {
10✔
94
            if (!($selector instanceof Selector)) {
10✔
95
                if ($list === null || !($list instanceof KeyFrame)) {
9✔
96
                    if (!Selector::isValid($selector)) {
9✔
97
                        throw new UnexpectedTokenException(
3✔
98
                            "Selector did not match '" . Selector::SELECTOR_VALIDATION_RX . "'.",
3✔
99
                            $selectors,
100
                            'custom'
3✔
101
                        );
102
                    }
103
                    $this->selectors[$key] = new Selector($selector);
6✔
104
                } else {
UNCOV
105
                    if (!KeyframeSelector::isValid($selector)) {
×
106
                        throw new UnexpectedTokenException(
×
107
                            "Selector did not match '" . KeyframeSelector::SELECTOR_VALIDATION_RX . "'.",
×
108
                            $selector,
UNCOV
109
                            'custom'
×
110
                        );
111
                    }
UNCOV
112
                    $this->selectors[$key] = new KeyframeSelector($selector);
×
113
                }
114
            }
115
        }
116
    }
7✔
117

118
    /**
119
     * Remove one of the selectors of the block.
120
     *
121
     * @param Selector|string $selectorToRemove
122
     */
UNCOV
123
    public function removeSelector($selectorToRemove): bool
×
124
    {
UNCOV
125
        if ($selectorToRemove instanceof Selector) {
×
126
            $selectorToRemove = $selectorToRemove->getSelector();
×
127
        }
UNCOV
128
        foreach ($this->selectors as $key => $selector) {
×
129
            if ($selector->getSelector() === $selectorToRemove) {
×
130
                unset($this->selectors[$key]);
×
131
                return true;
×
132
            }
133
        }
UNCOV
134
        return false;
×
135
    }
136

137
    /**
138
     * @return array<Selector>
139
     */
UNCOV
140
    public function getSelectors(): array
×
141
    {
UNCOV
142
        return $this->selectors;
×
143
    }
144

145
    /**
146
     * @return non-empty-string
147
     *
148
     * @throws OutputException
149
     */
150
    public function render(OutputFormat $outputFormat): string
6✔
151
    {
152
        $formatter = $outputFormat->getFormatter();
6✔
153
        $result = $formatter->comments($this);
6✔
154
        if (\count($this->selectors) === 0) {
6✔
155
            // If all the selectors have been removed, this declaration block becomes invalid
UNCOV
156
            throw new OutputException(
×
157
                'Attempt to print declaration block with missing selector',
×
158
                $this->getLineNumber()
×
159
            );
160
        }
161
        $result .= $outputFormat->getContentBeforeDeclarationBlock();
6✔
162
        $result .= $formatter->implode(
6✔
163
            $formatter->spaceBeforeSelectorSeparator() . ',' . $formatter->spaceAfterSelectorSeparator(),
6✔
164
            $this->selectors
6✔
165
        );
166
        $result .= $outputFormat->getContentAfterDeclarationBlockSelectors();
6✔
167
        $result .= $formatter->spaceBeforeOpeningBrace() . '{';
6✔
168
        $result .= $this->renderRules($outputFormat);
6✔
169
        $result .= '}';
6✔
170
        $result .= $outputFormat->getContentAfterDeclarationBlock();
6✔
171

172
        return $result;
6✔
173
    }
174
}
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