Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
FunctionMatcher
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
2 / 2
10
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 generateMatches
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
6
1<?php
2declare( strict_types = 1 );
3
4/**
5 * @file
6 * @license https://opensource.org/licenses/Apache-2.0 Apache-2.0
7 */
8
9namespace Wikimedia\CSS\Grammar;
10
11use Closure;
12use InvalidArgumentException;
13use Wikimedia\CSS\Objects\ComponentValueList;
14use Wikimedia\CSS\Objects\CSSFunction;
15
16/**
17 * Matcher that matches a CSSFunction
18 *
19 * The grammar definitions in the standards seem to be written assuming they're
20 * being passed a sequence of Tokens only, even though they're defined over a
21 * sequence of ComponentValues (which includes SimpleBlocks and CSSFunctions)
22 * instead.
23 *
24 * Thus, to be safe you'll want to use this when a grammar specifies something
25 * like `FUNCTION <stuff> ')'`.
26 */
27class FunctionMatcher extends Matcher {
28    /** @var callable|null Function name */
29    protected $nameCheck;
30
31    /** @var Matcher */
32    protected $matcher;
33
34    /**
35     * @param string|Closure|null $name Function name, case-insensitive, or a
36     *  function to check the name.
37     * @param Matcher $matcher Matcher for the contents of the function
38     */
39    public function __construct( $name, Matcher $matcher ) {
40        if ( is_string( $name ) ) {
41            $this->nameCheck = static function ( $s ) use ( $name ) {
42                return !strcasecmp( $s, $name );
43            };
44        } elseif ( is_callable( $name ) || $name === null ) {
45            $this->nameCheck = $name;
46        } else {
47            throw new InvalidArgumentException( '$name must be a string, callable, or null' );
48        }
49        $this->matcher = $matcher;
50    }
51
52    /** @inheritDoc */
53    protected function generateMatches( ComponentValueList $values, $start, array $options ) {
54        $cv = $values[$start] ?? null;
55        if ( $cv instanceof CSSFunction &&
56            ( !$this->nameCheck || ( $this->nameCheck )( $cv->getName() ) )
57        ) {
58            // To successfully match, our sub-Matcher needs to match the whole
59            // content of the function.
60            $l = $cv->getValue()->count();
61            $s = $this->next( $cv->getValue(), -1, $options );
62            foreach ( $this->matcher->generateMatches( $cv->getValue(), $s, $options ) as $match ) {
63                if ( $match->getNext() === $l ) {
64                    // Matched the whole content of the function, so yield the
65                    // token after the function.
66                    yield $this->makeMatch( $values, $start, $this->next( $values, $start, $options ), $match );
67                    return;
68                }
69            }
70        }
71    }
72}