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