Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
81.37% |
83 / 102 |
|
25.00% |
1 / 4 |
CRAP | |
0.00% |
0 / 1 |
Less_Tree_Mixin_Call | |
81.37% |
83 / 102 |
|
25.00% |
1 / 4 |
64.52 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
compile | |
91.14% |
72 / 79 |
|
0.00% |
0 / 1 |
37.95 | |||
Format | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
30 | |||
IsRecursive | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
6.10 |
1 | <?php |
2 | /** |
3 | * @private |
4 | */ |
5 | class 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 | } |