Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
74.77% covered (warning)
74.77%
83 / 111
25.00% covered (danger)
25.00%
1 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
Less_Tree_Mixin_Call
74.77% covered (warning)
74.77%
83 / 111
25.00% covered (danger)
25.00%
1 / 4
87.54
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 compile
81.82% covered (warning)
81.82%
72 / 88
0.00% covered (danger)
0.00%
0 / 1
45.23
 Format
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
30
 IsRecursive
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
6.10
1<?php
2/**
3 * @private
4 */
5class Less_Tree_Mixin_Call extends Less_Tree {
6
7    /** @var Less_Tree_Selector */
8    public $selector;
9    /** @var array[] */
10    public $arguments;
11    /** @var int */
12    public $index;
13    /** @var array */
14    public $currentFileInfo;
15
16    /** @var bool */
17    public $important;
18
19    public function __construct( $elements, $args, $index, $currentFileInfo, $important = false ) {
20        $this->selector = new Less_Tree_Selector( $elements );
21        $this->arguments = $args;
22        $this->index = $index;
23        $this->currentFileInfo = $currentFileInfo;
24        $this->important = $important;
25    }
26
27    /**
28     * @see less-2.5.3.js#MixinCall.prototype.eval
29     */
30    public function compile( $env ) {
31        $rules = [];
32        $match = false;
33        $isOneFound = false;
34        $candidates = [];
35        $conditionResult = [];
36        $this->selector = $this->selector->compile( $env );
37        $args = [];
38        foreach ( $this->arguments as $a ) {
39            $argValue = $a['value']->compile( $env );
40            if ( !empty( $a['expand'] ) && is_array( $argValue->value ) ) {
41                foreach ( $argValue->value as $value ) {
42                    $args[] = [ 'name' => null, 'value' => $value ];
43                }
44            } else {
45                $args[] = [ 'name' => $a['name'], 'value' => $argValue ];
46            }
47        }
48
49        $defNone = 0;
50        $defTrue = 1;
51        $defFalse = 2;
52        foreach ( $env->frames as $frame ) {
53            $noArgumentsFilter = static function ( $rule ) use ( $env ) {
54                return $rule->matchArgs( [], $env );
55            };
56            $mixins = $frame->find( $this->selector, null, $noArgumentsFilter );
57            if ( !$mixins ) {
58                continue;
59            }
60
61            $isOneFound = true;
62
63            // To make `default()` function independent of definition order we have two "subpasses" here.
64            // At first we evaluate each guard *twice* (with `default() == true` and `default() == false`),
65            // and build candidate list with corresponding flags. Then, when we know all possible matches,
66            // we make a final decision.
67
68            $mixins_len = count( $mixins );
69            for ( $m = 0; $m < $mixins_len; $m++ ) {
70                $mixin = $mixins[$m]["rule"];
71                $mixinPath = $mixins[$m]["path"];
72
73                if ( $this->IsRecursive( $env, $mixin ) ) {
74                    continue;
75                }
76
77                if ( $mixin->matchArgs( $args, $env ) ) {
78
79                    // less-2.5.3.js#MixinCall calcDefGroup()
80                    $group = -1;
81                    for ( $f = 0; $f < 2; $f++ ) {
82                        $conditionResult[$f] = true;
83                        Less_Tree_DefaultFunc::value( $f );
84                        for ( $p = 0; $p < count( $mixinPath ) && $conditionResult[$f]; $p++ ) {
85                            $namespace = $mixinPath[$p] ?? null;
86                            if ( isset( $namespace ) && method_exists( $namespace, "matchCondition" ) ) {
87                                $conditionResult[$f] = $conditionResult[$f] && $namespace->matchCondition( null, $env );
88                            }
89                        }
90                        if ( method_exists( $mixin, "matchCondition" ) ) {
91                            $conditionResult[$f] = $conditionResult[$f] && $mixin->matchCondition( $args, $env );
92                        }
93                    }
94                    // PhanTypeInvalidDimOffset -- False positive
95                    '@phan-var array{0:bool,1:bool} $conditionResult';
96                    if ( $conditionResult[0] || $conditionResult[1] ) {
97                        if ( $conditionResult[0] != $conditionResult[1] ) {
98                            $group = $conditionResult[1] ?
99                                $defTrue : $defFalse;
100                        } else {
101                            $group = $defNone;
102                        }
103
104                    }
105
106                    $candidate = [ 'mixin' => $mixin, 'group' => $group ];
107
108                    if ( $candidate["group"] !== -1 ) {
109                        $candidates[] = $candidate;
110                    }
111
112                    $match = true;
113                }
114            }
115
116            Less_Tree_DefaultFunc::reset();
117
118            $count = [ 0, 0, 0 ];
119            for ( $m = 0; $m < count( $candidates ); $m++ ) {
120                $count[ $candidates[$m]['group'] ]++;
121            }
122
123            if ( $count[$defNone] > 0 ) {
124                $defaultResult = $defFalse;
125            } else {
126                $defaultResult = $defTrue;
127                if ( ( $count[$defTrue] + $count[$defFalse] ) > 1 ) {
128                    throw new Exception( 'Ambiguous use of `default()` found when matching for `' . $this->format( $args ) . '`' );
129                }
130            }
131
132            $candidates_length = count( $candidates );
133            for ( $m = 0; $m < $candidates_length; $m++ ) {
134                $candidate = $candidates[$m]['group'];
135                if ( ( $candidate === $defNone ) || ( $candidate === $defaultResult ) ) {
136                    try {
137                        $mixin = $candidates[$m]['mixin'];
138                        if ( !( $mixin instanceof Less_Tree_Mixin_Definition ) ) {
139                            $originalRuleset = $mixin instanceof Less_Tree_Ruleset ? $mixin->originalRuleset : $mixin;
140                            $mixin = new Less_Tree_Mixin_Definition( '', [], $mixin->rules, null, false );
141                            $mixin->originalRuleset = $originalRuleset;
142                        }
143                        $rules = array_merge( $rules, $mixin->evalCall( $env, $args, $this->important )->rules );
144                    } catch ( Exception $e ) {
145                        throw new Less_Exception_Compiler( $e->getMessage(), null, null, $this->currentFileInfo );
146                    }
147                }
148            }
149
150            if ( $match ) {
151                if ( !$this->currentFileInfo || !isset( $this->currentFileInfo['reference'] ) || !$this->currentFileInfo['reference'] ) {
152                    Less_Tree::ReferencedArray( $rules );
153                }
154
155                return $rules;
156            }
157        }
158
159        if ( $isOneFound ) {
160            $selectorName = $this->selector->toCSS();
161            throw new Less_Exception_Compiler(
162                'No matching definition was found for ' . $selectorName . ' with args `' . $this->Format( $args ) . '`',
163                null,
164                $this->index,
165                $this->currentFileInfo
166            );
167
168        } else {
169            throw new Less_Exception_Compiler(
170                trim( $this->selector->toCSS() ) . " is undefined in " . $this->currentFileInfo['filename'],
171                null,
172                $this->index
173            );
174        }
175    }
176
177    /**
178     * Format the args for use in exception messages
179     */
180    private function Format( $args ) {
181        $message = [];
182        if ( $args ) {
183            foreach ( $args as $a ) {
184                $argValue = '';
185                if ( $a['name'] ) {
186                    $argValue .= $a['name'] . ':';
187                }
188                if ( $a['value'] instanceof Less_Tree ) {
189                    $argValue .= $a['value']->toCSS();
190                } else {
191                    $argValue .= '???';
192                }
193                $message[] = $argValue;
194            }
195        }
196        return implode( ', ', $message );
197    }
198
199    /**
200     * Are we in a recursive mixin call?
201     *
202     * @return bool
203     */
204    private function IsRecursive( $env, $mixin ) {
205        foreach ( $env->frames as $recur_frame ) {
206            if ( !( $mixin instanceof Less_Tree_Mixin_Definition ) ) {
207
208                if ( $mixin === $recur_frame ) {
209                    return true;
210                }
211
212                if ( isset( $recur_frame->originalRuleset ) && $mixin->ruleset_id === $recur_frame->originalRuleset ) {
213                    return true;
214                }
215            }
216        }
217
218        return false;
219    }
220
221}