Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
74.77% |
83 / 111 |
|
25.00% |
1 / 4 |
CRAP | |
0.00% |
0 / 1 |
| Less_Tree_Mixin_Call | |
74.77% |
83 / 111 |
|
25.00% |
1 / 4 |
87.54 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
| compile | |
81.82% |
72 / 88 |
|
0.00% |
0 / 1 |
45.23 | |||
| 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 | /** @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 | } |