Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
52 / 52
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
Util
100.00% covered (success)
100.00%
52 / 52
100.00% covered (success)
100.00%
4 / 4
26
100.00% covered (success)
100.00%
1 / 1
 assertAllInstanceOf
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 assertAllTokensOfType
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
5
 findFirstNonWhitespace
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
6
 stringify
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
1 / 1
11
1<?php
2/**
3 * @file
4 * @license https://opensource.org/licenses/Apache-2.0 Apache-2.0
5 */
6
7namespace Wikimedia\CSS;
8
9use InvalidArgumentException;
10use Wikimedia\CSS\Objects\ComponentValue;
11use Wikimedia\CSS\Objects\ComponentValueList;
12use Wikimedia\CSS\Objects\CSSObject;
13use Wikimedia\CSS\Objects\Token;
14use Wikimedia\CSS\Objects\TokenList;
15
16/**
17 * Static utility functions
18 */
19class Util {
20
21    /**
22     * Check that all elements in an array implement a particular class
23     * @param array $array
24     * @param string $class
25     * @param string $what Describe the array being checked
26     * @throws InvalidArgumentException
27     */
28    public static function assertAllInstanceOf( array $array, $class, $what ) {
29        foreach ( $array as $k => $v ) {
30            if ( !$v instanceof $class ) {
31                $vtype = is_object( $v ) ? get_class( $v ) : gettype( $v );
32                throw new InvalidArgumentException(
33                    "$what may only contain instances of $class" .
34                        " (found $vtype at index $k)"
35                );
36            }
37        }
38    }
39
40    /**
41     * Check that a set of tokens are all the same type
42     * @param Token[] $array
43     * @param string $type
44     * @param string $what Describe the array being checked
45     * @throws InvalidArgumentException
46     */
47    public static function assertAllTokensOfType( array $array, $type, $what ) {
48        foreach ( $array as $k => $v ) {
49            if ( !$v instanceof Token ) {
50                $vtype = is_object( $v ) ? get_class( $v ) : gettype( $v );
51                throw new InvalidArgumentException(
52                    "$what may only contain instances of " . Token::class .
53                        " (found $vtype at index $k)"
54                );
55            }
56            if ( $v->type() !== $type ) {
57                throw new InvalidArgumentException(
58                    "$what may only contain \"$type\" tokens" .
59                        " (found \"{$v->type()}\" at index $k)"
60                );
61            }
62        }
63    }
64
65    /**
66     * Find the first non-whitespace ComponentValue in a list
67     * @param TokenList|ComponentValueList $list
68     * @return ComponentValue|null
69     */
70    public static function findFirstNonWhitespace( $list ) {
71        if ( !$list instanceof TokenList && !$list instanceof ComponentValueList ) {
72            throw new InvalidArgumentException( 'List must be TokenList or ComponentValueList' );
73        }
74        foreach ( $list as $v ) {
75            if ( !$v instanceof Token || $v->type() !== Token::T_WHITESPACE ) {
76                return $v;
77            }
78        }
79        return null;
80    }
81
82    /**
83     * Turn a CSSObject into a string
84     * @param CSSObject|CSSObject[] $object
85     * @param array $options Serialization options:
86     *  - minify: (bool) Skip comments and insignificant tokens
87     * @return string
88     */
89    public static function stringify( $object, $options = [] ) {
90        if ( is_array( $object ) ) {
91            $tokens = array_reduce( $object, static function ( array $carry, CSSObject $item ) {
92                return array_merge( $carry, $item->toTokenArray() );
93            }, [] );
94        } else {
95            $tokens = $object->toTokenArray();
96        }
97        if ( !$tokens ) {
98            return '';
99        }
100
101        if ( !empty( $options['minify'] ) ) {
102            // Last second check for significant whitespace
103            $e = count( $tokens ) - 1;
104            for ( $i = 1; $i < $e; $i++ ) {
105                $t = $tokens[$i];
106                if ( $t->type() === Token::T_WHITESPACE && !$t->significant() &&
107                    Token::separate( $tokens[$i - 1], $tokens[$i + 1] )
108                ) {
109                    $tokens[$i] = $t->copyWithSignificance( true );
110                }
111            }
112
113            // Filter!
114            $tokens = array_filter( $tokens, static function ( $t ) {
115                return $t->significant();
116            } );
117        }
118
119        $prev = reset( $tokens );
120        $ret = (string)$prev;
121        $urangeHack = 0;
122        // @phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
123        while ( ( $token = next( $tokens ) ) !== false ) {
124            // Avoid serializing tokens that are part of a <urange> with extraneous comments
125            // by checking for a hack-flag in the type.
126            // @see Wikimedia\CSS\Matcher\UrangeMatcher
127            // @phan-suppress-next-line PhanAccessMethodInternal
128            $urangeHack = max( $urangeHack, $prev->urangeHack() );
129
130            if ( --$urangeHack <= 0 && Token::separate( $prev, $token ) ) {
131                // Per https://www.w3.org/TR/2019/CR-css-syntax-3-20190716/#serialization
132                $ret .= '/**/';
133            }
134            $ret .= (string)$token;
135            $prev = $token;
136        }
137        return $ret;
138    }
139}