Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
97.24% |
141 / 145 |
|
89.47% |
17 / 19 |
CRAP | |
0.00% |
0 / 1 |
Less_Visitor_toCSS | |
97.24% |
141 / 145 |
|
89.47% |
17 / 19 |
80 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
run | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
visitRule | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
visitMixinDefinition | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
visitExtend | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
visitComment | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
visitMedia | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
visitDirective | |
95.24% |
20 / 21 |
|
0.00% |
0 / 1 |
10 | |||
checkPropertiesInRoot | |
57.14% |
4 / 7 |
|
0.00% |
0 / 1 |
8.83 | |||
visitRuleset | |
100.00% |
26 / 26 |
|
100.00% |
1 / 1 |
10 | |||
visitAnonymous | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
visitImport | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
visitRulesetRoot | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
visitRulesetPaths | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
6 | |||
_removeDuplicateRules | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
8 | |||
_mergeRules | |
100.00% |
27 / 27 |
|
100.00% |
1 / 1 |
11 | |||
toExpression | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
toValue | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
hasVisibleChild | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
7 |
1 | <?php |
2 | /** |
3 | * @private |
4 | */ |
5 | class 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 | } |