Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.24% covered (success)
97.24%
141 / 145
89.47% covered (warning)
89.47%
17 / 19
CRAP
0.00% covered (danger)
0.00%
0 / 1
Less_Visitor_toCSS
97.24% covered (success)
97.24%
141 / 145
89.47% covered (warning)
89.47%
17 / 19
80
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 run
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 visitDeclaration
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 visitMixinDefinition
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 visitExtend
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 visitComment
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 visitMedia
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 visitAtRule
95.24% covered (success)
95.24%
20 / 21
0.00% covered (danger)
0.00%
0 / 1
10
 checkPropertiesInRoot
57.14% covered (warning)
57.14%
4 / 7
0.00% covered (danger)
0.00%
0 / 1
8.83
 visitRuleset
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
100.00%
1 / 1
10
 visitAnonymous
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 visitImport
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 visitRulesetRoot
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 visitRulesetPaths
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
6
 _removeDuplicateRules
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
8
 _mergeRules
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
1 / 1
11
 toExpression
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 toValue
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 hasVisibleChild
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
7
1<?php
2/**
3 * @private
4 */
5class Less_Visitor_toCSS extends Less_VisitorReplacing {
6
7    /** @var bool|null */
8    private $charset;
9
10    public function __construct() {
11        parent::__construct();
12    }
13
14    /**
15     * @param Less_Tree_Ruleset $root
16     */
17    public function run( $root ) {
18        return $this->visitObj( $root );
19    }
20
21    public function visitDeclaration( $declNode ) {
22        if ( $declNode->variable ) {
23            return [];
24        }
25        return $declNode;
26    }
27
28    public function visitMixinDefinition( $mixinNode ) {
29        // mixin definitions do not get eval'd - this means they keep state
30        // so we have to clear that state here so it isn't used if toCSS is called twice
31        $mixinNode->frames = [];
32        return [];
33    }
34
35    public function visitExtend() {
36        return [];
37    }
38
39    public function visitComment( $commentNode ) {
40        if ( $commentNode->isSilent() ) {
41            return [];
42        }
43        return $commentNode;
44    }
45
46    public function visitMedia( $mediaNode, &$visitDeeper ) {
47        $mediaNode->accept( $this );
48        $visitDeeper = false;
49
50        if ( !$mediaNode->rules ) {
51            return [];
52        }
53        return $mediaNode;
54    }
55
56    public function visitAtRule( $atRuleNode, &$visitDeeper ) {
57        if ( $atRuleNode->name === '@charset' ) {
58            if ( !$atRuleNode->getIsReferenced() ) {
59                return;
60            }
61            if ( isset( $this->charset ) && $this->charset ) {
62                // NOTE: Skip debugInfo handling (not implemented)
63                return;
64            }
65            $this->charset = true;
66        }
67
68        if ( $atRuleNode->rules ) {
69            self::_mergeRules( $atRuleNode->rules[0]->rules );
70            // process childs
71            $atRuleNode->accept( $this );
72            $visitDeeper = false;
73
74            // the directive was directly referenced and therefore needs to be shown in the output
75            if ( $atRuleNode->getIsReferenced() ) {
76                return $atRuleNode;
77            }
78
79            if ( !$atRuleNode->rules ) {
80                return;
81            }
82            if ( $this->hasVisibleChild( $atRuleNode ) ) {
83                // marking as referenced in case the directive is stored inside another directive
84                $atRuleNode->markReferenced();
85                return $atRuleNode;
86            }
87                // The directive was not directly referenced and does not contain anything that
88                //was referenced. Therefore it must not be shown in output.
89                return;
90        } else {
91            if ( !$atRuleNode->getIsReferenced() ) {
92                return;
93            }
94        }
95
96        return $atRuleNode;
97    }
98
99    public function checkPropertiesInRoot( $rulesetNode ) {
100        if ( !$rulesetNode->firstRoot ) {
101            return;
102        }
103
104        foreach ( $rulesetNode->rules as $ruleNode ) {
105            if ( $ruleNode instanceof Less_Tree_Declaration && !$ruleNode->variable ) {
106                $msg = "properties must be inside selector blocks, they cannot be in the root. Index " . $ruleNode->index .
107                    ( $ruleNode->currentFileInfo ? ' Filename: ' . $ruleNode->currentFileInfo['filename'] : null );
108                throw new Less_Exception_Compiler( $msg );
109            }
110        }
111    }
112
113    public function visitRuleset( $rulesetNode, &$visitDeeper ) {
114        $visitDeeper = false;
115
116        $this->checkPropertiesInRoot( $rulesetNode );
117
118        if ( $rulesetNode->root ) {
119            return $this->visitRulesetRoot( $rulesetNode );
120        }
121
122        $rulesets = [];
123        $rulesetNode->paths = $this->visitRulesetPaths( $rulesetNode );
124
125        // Compile rules and rulesets
126        $nodeRuleCnt = $rulesetNode->rules ? count( $rulesetNode->rules ) : 0;
127        for ( $i = 0; $i < $nodeRuleCnt; ) {
128            $rule = $rulesetNode->rules[$i];
129
130            if ( property_exists( $rule, 'rules' ) ) {
131                // visit because we are moving them out from being a child
132                $rulesets[] = $this->visitObj( $rule );
133                array_splice( $rulesetNode->rules, $i, 1 );
134                $nodeRuleCnt--;
135                continue;
136            }
137            $i++;
138        }
139
140        // accept the visitor to remove rules and refactor itself
141        // then we can decide now whether we want it or not
142        if ( $nodeRuleCnt > 0 ) {
143            $rulesetNode->accept( $this );
144
145            if ( $rulesetNode->rules ) {
146
147                if ( count( $rulesetNode->rules ) > 1 ) {
148                    self::_mergeRules( $rulesetNode->rules );
149                    $this->_removeDuplicateRules( $rulesetNode->rules );
150                }
151
152                // now decide whether we keep the ruleset
153                if ( $rulesetNode->paths ) {
154                    // array_unshift($rulesets, $rulesetNode);
155                    array_splice( $rulesets, 0, 0, [ $rulesetNode ] );
156                }
157            }
158
159        }
160
161        if ( count( $rulesets ) === 1 ) {
162            return $rulesets[0];
163        }
164        return $rulesets;
165    }
166
167    public function visitAnonymous( $anonymousNode ) {
168        if ( !$anonymousNode->getIsReferenced() ) {
169            return;
170        }
171
172        $anonymousNode->accept( $this );
173        return $anonymousNode;
174    }
175
176    public function visitImport( $importNode ) {
177        if ( isset( $importNode->path->currentFileInfo["reference"] ) && $importNode->css ) {
178            return;
179        }
180        return $importNode;
181    }
182
183    /**
184     * Helper function for visitiRuleset
185     *
186     * return array|Less_Tree_Ruleset
187     */
188    private function visitRulesetRoot( $rulesetNode ) {
189        $rulesetNode->accept( $this );
190        if ( $rulesetNode->firstRoot || $rulesetNode->rules ) {
191            return $rulesetNode;
192        }
193        return [];
194    }
195
196    /**
197     * Helper function for visitRuleset()
198     *
199     * @return array
200     */
201    private function visitRulesetPaths( $rulesetNode ) {
202        $paths = [];
203        foreach ( $rulesetNode->paths as $p ) {
204            if ( $p[0]->elements[0]->combinator === ' ' ) {
205                $p[0]->elements[0]->combinator = '';
206            }
207
208            foreach ( $p as $pi ) {
209                if ( $pi->getIsReferenced() && $pi->getIsOutput() ) {
210                    $paths[] = $p;
211                    break;
212                }
213            }
214        }
215
216        return $paths;
217    }
218
219    protected function _removeDuplicateRules( &$rules ) {
220        // remove duplicates
221        $ruleCache = [];
222        for ( $i = count( $rules ) - 1; $i >= 0; $i-- ) {
223            $rule = $rules[$i];
224            if ( $rule instanceof Less_Tree_Declaration || $rule instanceof Less_Tree_NameValue ) {
225
226                if ( !isset( $ruleCache[$rule->name] ) ) {
227                    $ruleCache[$rule->name] = $rule;
228                } else {
229                    $ruleList =& $ruleCache[$rule->name];
230
231                    if ( $ruleList instanceof Less_Tree_Declaration || $ruleList instanceof Less_Tree_NameValue ) {
232                        $ruleList = $ruleCache[$rule->name] = [ $ruleCache[$rule->name]->toCSS() ];
233                    }
234
235                    $ruleCSS = $rule->toCSS();
236                    if ( in_array( $ruleCSS, $ruleList ) ) {
237                        array_splice( $rules, $i, 1 );
238                    } else {
239                        $ruleList[] = $ruleCSS;
240                    }
241                }
242            }
243        }
244    }
245
246    public static function _mergeRules( &$rules ) {
247        $groups = [];
248
249        // obj($rules);
250
251        $rules_len = count( $rules );
252        for ( $i = 0; $i < $rules_len; $i++ ) {
253            $rule = $rules[$i];
254
255            if ( ( $rule instanceof Less_Tree_Declaration ) && $rule->merge ) {
256
257                $key = $rule->name;
258                if ( $rule->important ) {
259                    $key .= ',!';
260                }
261
262                if ( !isset( $groups[$key] ) ) {
263                    $groups[$key] = [];
264                } else {
265                    array_splice( $rules, $i--, 1 );
266                    $rules_len--;
267                }
268
269                $groups[$key][] = $rule;
270            }
271        }
272
273        foreach ( $groups as $parts ) {
274
275            if ( count( $parts ) > 1 ) {
276                $rule = $parts[0];
277                $spacedGroups = [];
278                $lastSpacedGroup = [];
279                $parts_mapped = [];
280                foreach ( $parts as $p ) {
281                    if ( $p->merge === '+' ) {
282                        if ( $lastSpacedGroup ) {
283                            $spacedGroups[] = self::toExpression( $lastSpacedGroup );
284                        }
285                        $lastSpacedGroup = [];
286                    }
287                    $lastSpacedGroup[] = $p;
288                }
289
290                $spacedGroups[] = self::toExpression( $lastSpacedGroup );
291                $rule->value = self::toValue( $spacedGroups );
292            }
293        }
294    }
295
296    public static function toExpression( $values ) {
297        $mapped = [];
298        foreach ( $values as $p ) {
299            $mapped[] = $p->value;
300        }
301        return new Less_Tree_Expression( $mapped );
302    }
303
304    public static function toValue( $values ) {
305        // return new Less_Tree_Value($values); ??
306
307        $mapped = [];
308        foreach ( $values as $p ) {
309            $mapped[] = $p;
310        }
311        return new Less_Tree_Value( $mapped );
312    }
313
314    public function hasVisibleChild( $atRuleNode ) {
315        // prepare list of childs
316        $rule = $bodyRules = $atRuleNode->rules;
317        // if there is only one nested ruleset and that one has no path, then it is
318        //just fake ruleset that got not replaced and we need to look inside it to
319        //get real childs
320        if ( count( $bodyRules ) === 1 && ( !$bodyRules[0]->paths || count( $bodyRules[0]->paths ) === 0 ) ) {
321            $bodyRules = $bodyRules[0]->rules;
322        }
323        foreach ( $bodyRules as $rule ) {
324            if ( method_exists( $rule, 'getIsReferenced' ) && $rule->getIsReferenced() ) {
325                // the directive contains something that was referenced (likely by extend)
326                //therefore it needs to be shown in output too
327                return true;
328            }
329        }
330        return false;
331    }
332}