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