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