Completed
Pull Request — master (#20)
by Fredrik
03:15
created

lessc::multiplyMedia()   C

Complexity

Conditions 8
Paths 4

Size

Total Lines 26
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
dl 0
loc 26
rs 5.3846
c 1
b 0
f 1
cc 8
eloc 15
nc 4
nop 2
1
<?php
2
// @codingStandardsIgnoreStart
3
/**
4
 * lessphp v0.4.0
5
 * http://leafo.net/lessphp
6
 *
7
 * LESS CSS compiler, adapted from http://lesscss.org
8
 *
9
 * Copyright 2013, Leaf Corcoran <[email protected]>
10
 * Licensed under MIT or GPLv3, see LICENSE
11
 */
12
13
14
/**
15
 * The LESS compiler and parser.
16
 *
17
 * Converting LESS to CSS is a three stage process. The incoming file is parsed
18
 * by `lessc_parser` into a syntax tree, then it is compiled into another tree
19
 * representing the CSS structure by `lessc`. The CSS tree is fed into a
20
 * formatter, like `lessc_formatter` which then outputs CSS as a string.
21
 *
22
 * During the first compile, all values are *reduced*, which means that their
23
 * types are brought to the lowest form before being dump as strings. This
24
 * handles math equations, variable dereferences, and the like.
25
 *
26
 * The `parse` function of `lessc` is the entry point.
27
 *
28
 * In summary:
29
 *
30
 * The `lessc` class creates an instance of the parser, feeds it LESS code,
31
 * then transforms the resulting tree to a CSS tree. This class also holds the
32
 * evaluation context, such as all available mixins and variables at any given
33
 * time.
34
 *
35
 * The `lessc_parser` class is only concerned with parsing its input.
36
 *
37
 * The `lessc_formatter` takes a CSS tree, and dumps it to a formatted string,
38
 * handling things like indentation.
39
 */
40
class lessc {
41
	static public $VERSION = "v0.4.0";
42
43
	static public $TRUE = array("keyword", "true");
44
	static public $FALSE = array("keyword", "false");
45
46
	protected $libFunctions = array();
47
	protected $registeredVars = array();
48
	protected $preserveComments = false;
49
50
	public $vPrefix = '@'; // prefix of abstract properties
51
	public $mPrefix = '$'; // prefix of abstract blocks
52
	public $parentSelector = '&';
53
54
	public $importDisabled = false;
55
	public $importDir = '';
56
57
	protected $numberPrecision = null;
58
59
	protected $allParsedFiles = array();
60
61
	// set to the parser that generated the current line when compiling
62
	// so we know how to create error messages
63
	protected $sourceParser = null;
64
	protected $sourceLoc = null;
65
66
	static protected $nextImportId = 0; // uniquely identify imports
67
68
	// attempts to find the path of an import url, returns null for css files
69
	protected function findImport($url) {
70
		foreach ((array)$this->importDir as $dir) {
71
			$full = $dir.(substr($dir, -1) != '/' ? '/' : '').$url;
72
			if ($this->fileExists($file = $full.'.less') || $this->fileExists($file = $full)) {
73
				return $file;
74
			}
75
		}
76
77
		return null;
78
	}
79
80
	protected function fileExists($name) {
81
		return is_file($name);
82
	}
83
84
	static public function compressList($items, $delim) {
85
		if (!isset($items[1]) && isset($items[0])) return $items[0];
86
		else return array('list', $delim, $items);
87
	}
88
89
	static public function preg_quote($what) {
90
		return preg_quote($what, '/');
91
	}
92
93
	protected function tryImport($importPath, $parentBlock, $out) {
94
		if ($importPath[0] == "function" && $importPath[1] == "url") {
95
			$importPath = $this->flattenList($importPath[2]);
96
		}
97
98
		$str = $this->coerceString($importPath);
99
		if ($str === null) return false;
100
101
		$url = $this->compileValue($this->lib_e($str));
102
103
		// don't import if it ends in css
104
		if (substr_compare($url, '.css', -4, 4) === 0) return false;
105
106
		$realPath = $this->findImport($url);
107
108
		if ($realPath === null) return false;
109
110
		if ($this->importDisabled) {
111
			return array(false, "/* import disabled */");
112
		}
113
114
		if (isset($this->allParsedFiles[realpath($realPath)])) {
115
			return array(false, null);
116
		}
117
118
		$this->addParsedFile($realPath);
119
		$parser = $this->makeParser($realPath);
120
		$root = $parser->parse(file_get_contents($realPath));
121
122
		// set the parents of all the block props
123
		foreach ($root->props as $prop) {
124
			if ($prop[0] == "block") {
125
				$prop[1]->parent = $parentBlock;
126
			}
127
		}
128
129
		// copy mixins into scope, set their parents
130
		// bring blocks from import into current block
131
		// TODO: need to mark the source parser	these came from this file
132
		foreach ($root->children as $childName => $child) {
133
			if (isset($parentBlock->children[$childName])) {
134
				$parentBlock->children[$childName] = array_merge(
135
					$parentBlock->children[$childName],
136
					$child);
137
			} else {
138
				$parentBlock->children[$childName] = $child;
139
			}
140
		}
141
142
		$pi = pathinfo($realPath);
143
		$dir = $pi["dirname"];
144
145
		list($top, $bottom) = $this->sortProps($root->props, true);
146
		$this->compileImportedProps($top, $parentBlock, $out, $parser, $dir);
147
148
		return array(true, $bottom, $parser, $dir);
149
	}
150
151
	protected function compileImportedProps($props, $block, $out, $sourceParser, $importDir) {
0 ignored issues
show
Unused Code introduced by
The parameter $sourceParser is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
152
		$oldSourceParser = $this->sourceParser;
153
154
		$oldImport = $this->importDir;
155
156
		// TODO: this is because the importDir api is stupid
157
		$this->importDir = (array)$this->importDir;
0 ignored issues
show
Documentation Bug introduced by
It seems like (array) $this->importDir of type array is incompatible with the declared type string of property $importDir.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
158
		array_unshift($this->importDir, $importDir);
159
160
		foreach ($props as $prop) {
161
			$this->compileProp($prop, $block, $out);
162
		}
163
164
		$this->importDir = $oldImport;
165
		$this->sourceParser = $oldSourceParser;
166
	}
167
168
	/**
169
	 * Recursively compiles a block.
170
	 *
171
	 * A block is analogous to a CSS block in most cases. A single LESS document
172
	 * is encapsulated in a block when parsed, but it does not have parent tags
173
	 * so all of it's children appear on the root level when compiled.
174
	 *
175
	 * Blocks are made up of props and children.
176
	 *
177
	 * Props are property instructions, array tuples which describe an action
178
	 * to be taken, eg. write a property, set a variable, mixin a block.
179
	 *
180
	 * The children of a block are just all the blocks that are defined within.
181
	 * This is used to look up mixins when performing a mixin.
182
	 *
183
	 * Compiling the block involves pushing a fresh environment on the stack,
184
	 * and iterating through the props, compiling each one.
185
	 *
186
	 * See lessc::compileProp()
187
	 *
188
	 */
189
	protected function compileBlock($block) {
190
		switch ($block->type) {
191
		case "root":
192
			$this->compileRoot($block);
193
			break;
194
		case null:
195
			$this->compileCSSBlock($block);
196
			break;
197
		case "media":
198
			$this->compileMedia($block);
199
			break;
200
		case "directive":
201
			$name = "@" . $block->name;
202
			if (!empty($block->value)) {
203
				$name .= " " . $this->compileValue($this->reduce($block->value));
204
			}
205
206
			$this->compileNestedBlock($block, array($name));
207
			break;
208
		default:
209
			$this->throwError("unknown block type: $block->type\n");
210
		}
211
	}
212
213
	protected function compileCSSBlock($block) {
214
		$env = $this->pushEnv();
215
216
		$selectors = $this->compileSelectors($block->tags);
217
		$env->selectors = $this->multiplySelectors($selectors);
218
		$out = $this->makeOutputBlock(null, $env->selectors);
219
220
		$this->scope->children[] = $out;
0 ignored issues
show
Bug introduced by
The property scope does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
221
		$this->compileProps($block, $out);
222
223
		$block->scope = $env; // mixins carry scope with them!
224
		$this->popEnv();
225
	}
226
227
	protected function compileMedia($media) {
228
		$env = $this->pushEnv($media);
229
		$parentScope = $this->mediaParent($this->scope);
230
231
		$query = $this->compileMediaQuery($this->multiplyMedia($env));
232
233
		$this->scope = $this->makeOutputBlock($media->type, array($query));
234
		$parentScope->children[] = $this->scope;
235
236
		$this->compileProps($media, $this->scope);
237
238
		if (count($this->scope->lines) > 0) {
239
			$orphanSelelectors = $this->findClosestSelectors();
240
			if (!is_null($orphanSelelectors)) {
241
				$orphan = $this->makeOutputBlock(null, $orphanSelelectors);
242
				$orphan->lines = $this->scope->lines;
243
				array_unshift($this->scope->children, $orphan);
244
				$this->scope->lines = array();
245
			}
246
		}
247
248
		$this->scope = $this->scope->parent;
249
		$this->popEnv();
250
	}
251
252
	protected function mediaParent($scope) {
253
		while (!empty($scope->parent)) {
254
			if (!empty($scope->type) && $scope->type != "media") {
255
				break;
256
			}
257
			$scope = $scope->parent;
258
		}
259
260
		return $scope;
261
	}
262
263
	protected function compileNestedBlock($block, $selectors) {
264
		$this->pushEnv($block);
265
		$this->scope = $this->makeOutputBlock($block->type, $selectors);
266
		$this->scope->parent->children[] = $this->scope;
267
268
		$this->compileProps($block, $this->scope);
269
270
		$this->scope = $this->scope->parent;
271
		$this->popEnv();
272
	}
273
274
	protected function compileRoot($root) {
275
		$this->pushEnv();
276
		$this->scope = $this->makeOutputBlock($root->type);
277
		$this->compileProps($root, $this->scope);
278
		$this->popEnv();
279
	}
280
281
	protected function compileProps($block, $out) {
282
		foreach ($this->sortProps($block->props) as $prop) {
283
			$this->compileProp($prop, $block, $out);
284
		}
285
		$out->lines = $this->deduplicate($out->lines);
286
	}
287
288
	/**
289
	 * Deduplicate lines in a block. Comments are not deduplicated. If a
290
	 * duplicate rule is detected, the comments immediately preceding each
291
	 * occurence are consolidated.
292
	 */
293
	protected function deduplicate($lines) {
294
		$unique = array();
295
		$comments = array();
296
297
		foreach($lines as $line) {
298
			if (strpos($line, '/*') === 0) {
299
				$comments[] = $line;
300
				continue;
301
			}
302
			if (!in_array($line, $unique)) {
303
				$unique[] = $line;
304
			}
305
			array_splice($unique, array_search($line, $unique), 0, $comments);
306
			$comments = array();
307
		}
308
		return array_merge($unique, $comments);
309
	}
310
311
	protected function sortProps($props, $split = false) {
312
		$vars = array();
313
		$imports = array();
314
		$other = array();
315
		$stack = array();
316
317
		foreach ($props as $prop) {
318
			switch ($prop[0]) {
319
			case "comment":
320
				$stack[] = $prop;
321
				break;
322
			case "assign":
323
				$stack[] = $prop;
324
				if (isset($prop[1][0]) && $prop[1][0] == $this->vPrefix) {
325
					$vars = array_merge($vars, $stack);
326
				} else {
327
					$other = array_merge($other, $stack);
328
				}
329
				$stack = array();
330
				break;
331
			case "import":
332
				$id = self::$nextImportId++;
333
				$prop[] = $id;
334
				$stack[] = $prop;
335
				$imports = array_merge($imports, $stack);
336
				$other[] = array("import_mixin", $id);
337
				$stack = array();
338
				break;
339
			default:
340
				$stack[] = $prop;
341
				$other = array_merge($other, $stack);
342
				$stack = array();
343
				break;
344
			}
345
		}
346
		$other = array_merge($other, $stack);
347
348
		if ($split) {
349
			return array(array_merge($vars, $imports), $other);
350
		} else {
351
			return array_merge($vars, $imports, $other);
352
		}
353
	}
354
355
	protected function compileMediaQuery($queries) {
356
		$compiledQueries = array();
357
		foreach ($queries as $query) {
358
			$parts = array();
359
			foreach ($query as $q) {
360
				switch ($q[0]) {
361
				case "mediaType":
362
					$parts[] = implode(" ", array_slice($q, 1));
363
					break;
364
				case "mediaExp":
365
					if (isset($q[2])) {
366
						$parts[] = "($q[1]: " .
367
							$this->compileValue($this->reduce($q[2])) . ")";
368
					} else {
369
						$parts[] = "($q[1])";
370
					}
371
					break;
372
				case "variable":
373
					$parts[] = $this->compileValue($this->reduce($q));
374
				break;
375
				}
376
			}
377
378
			if (count($parts) > 0) {
379
				$compiledQueries[] =  implode(" and ", $parts);
380
			}
381
		}
382
383
		$out = "@media";
384
		if (!empty($parts)) {
385
			$out .= " " .
386
				implode($this->formatter->selectorSeparator, $compiledQueries);
0 ignored issues
show
Bug introduced by
The property formatter does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
387
		}
388
		return $out;
389
	}
390
391
	protected function multiplyMedia($env, $childQueries = null) {
392
		if (is_null($env) ||
393
			!empty($env->block->type) && $env->block->type != "media")
394
		{
395
			return $childQueries;
396
		}
397
398
		// plain old block, skip
399
		if (empty($env->block->type)) {
400
			return $this->multiplyMedia($env->parent, $childQueries);
401
		}
402
403
		$out = array();
404
		$queries = $env->block->queries;
405
		if (is_null($childQueries)) {
406
			$out = $queries;
407
		} else {
408
			foreach ($queries as $parent) {
409
				foreach ($childQueries as $child) {
410
					$out[] = array_merge($parent, $child);
411
				}
412
			}
413
		}
414
415
		return $this->multiplyMedia($env->parent, $out);
416
	}
417
418
	protected function expandParentSelectors(&$tag, $replace) {
419
		$parts = explode("$&$", $tag);
420
		$count = 0;
421
		foreach ($parts as &$part) {
422
			$part = str_replace($this->parentSelector, $replace, $part, $c);
423
			$count += $c;
424
		}
425
		$tag = implode($this->parentSelector, $parts);
426
		return $count;
427
	}
428
429
	protected function findClosestSelectors() {
430
		$env = $this->env;
0 ignored issues
show
Bug introduced by
The property env does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
431
		$selectors = null;
432
		while ($env !== null) {
433
			if (isset($env->selectors)) {
434
				$selectors = $env->selectors;
435
				break;
436
			}
437
			$env = $env->parent;
438
		}
439
440
		return $selectors;
441
	}
442
443
444
	// multiply $selectors against the nearest selectors in env
445
	protected function multiplySelectors($selectors) {
446
		// find parent selectors
447
448
		$parentSelectors = $this->findClosestSelectors();
449
		if (is_null($parentSelectors)) {
450
			// kill parent reference in top level selector
451
			foreach ($selectors as &$s) {
452
				$this->expandParentSelectors($s, "");
453
			}
454
455
			return $selectors;
456
		}
457
458
		$out = array();
459
		foreach ($parentSelectors as $parent) {
460
			foreach ($selectors as $child) {
461
				$count = $this->expandParentSelectors($child, $parent);
462
463
				// don't prepend the parent tag if & was used
464
				if ($count > 0) {
465
					$out[] = trim($child);
466
				} else {
467
					$out[] = trim($parent . ' ' . $child);
468
				}
469
			}
470
		}
471
472
		return $out;
473
	}
474
475
	// reduces selector expressions
476
	protected function compileSelectors($selectors) {
477
		$out = array();
478
479
		foreach ($selectors as $s) {
480
			if (is_array($s)) {
481
				list(, $value) = $s;
482
				$out[] = trim($this->compileValue($this->reduce($value)));
483
			} else {
484
				$out[] = $s;
485
			}
486
		}
487
488
		return $out;
489
	}
490
491
	protected function eq($left, $right) {
492
		return $left == $right;
493
	}
494
495
	protected function patternMatch($block, $orderedArgs, $keywordArgs) {
496
		// match the guards if it has them
497
		// any one of the groups must have all its guards pass for a match
498
		if (!empty($block->guards)) {
499
			$groupPassed = false;
500
			foreach ($block->guards as $guardGroup) {
501
				foreach ($guardGroup as $guard) {
502
					$this->pushEnv();
503
					$this->zipSetArgs($block->args, $orderedArgs, $keywordArgs);
504
505
					$negate = false;
506
					if ($guard[0] == "negate") {
507
						$guard = $guard[1];
508
						$negate = true;
509
					}
510
511
					$passed = $this->reduce($guard) == self::$TRUE;
512
					if ($negate) $passed = !$passed;
513
514
					$this->popEnv();
515
516
					if ($passed) {
517
						$groupPassed = true;
518
					} else {
519
						$groupPassed = false;
520
						break;
521
					}
522
				}
523
524
				if ($groupPassed) break;
525
			}
526
527
			if (!$groupPassed) {
528
				return false;
529
			}
530
		}
531
532
		if (empty($block->args)) {
533
			return $block->isVararg || empty($orderedArgs) && empty($keywordArgs);
534
		}
535
536
		$remainingArgs = $block->args;
537
		if ($keywordArgs) {
538
			$remainingArgs = array();
539
			foreach ($block->args as $arg) {
540
				if ($arg[0] == "arg" && isset($keywordArgs[$arg[1]])) {
541
					continue;
542
				}
543
544
				$remainingArgs[] = $arg;
545
			}
546
		}
547
548
		$i = -1; // no args
549
		// try to match by arity or by argument literal
550
		foreach ($remainingArgs as $i => $arg) {
551
			switch ($arg[0]) {
552
			case "lit":
553
				if (empty($orderedArgs[$i]) || !$this->eq($arg[1], $orderedArgs[$i])) {
554
					return false;
555
				}
556
				break;
557
			case "arg":
558
				// no arg and no default value
559
				if (!isset($orderedArgs[$i]) && !isset($arg[2])) {
560
					return false;
561
				}
562
				break;
563
			case "rest":
564
				$i--; // rest can be empty
565
				break 2;
566
			}
567
		}
568
569
		if ($block->isVararg) {
570
			return true; // not having enough is handled above
571
		} else {
572
			$numMatched = $i + 1;
573
			// greater than becuase default values always match
574
			return $numMatched >= count($orderedArgs);
575
		}
576
	}
577
578
	protected function patternMatchAll($blocks, $orderedArgs, $keywordArgs, $skip=array()) {
579
		$matches = null;
580
		foreach ($blocks as $block) {
581
			// skip seen blocks that don't have arguments
582
			if (isset($skip[$block->id]) && !isset($block->args)) {
583
				continue;
584
			}
585
586
			if ($this->patternMatch($block, $orderedArgs, $keywordArgs)) {
587
				$matches[] = $block;
588
			}
589
		}
590
591
		return $matches;
592
	}
593
594
	// attempt to find blocks matched by path and args
595
	protected function findBlocks($searchIn, $path, $orderedArgs, $keywordArgs, $seen=array()) {
596
		if ($searchIn == null) return null;
597
		if (isset($seen[$searchIn->id])) return null;
598
		$seen[$searchIn->id] = true;
599
600
		$name = $path[0];
601
602
		if (isset($searchIn->children[$name])) {
603
			$blocks = $searchIn->children[$name];
604
			if (count($path) == 1) {
605
				$matches = $this->patternMatchAll($blocks, $orderedArgs, $keywordArgs, $seen);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $matches is correct as $this->patternMatchAll($...s, $keywordArgs, $seen) (which targets lessc::patternMatchAll()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
606
				if (!empty($matches)) {
607
					// This will return all blocks that match in the closest
608
					// scope that has any matching block, like lessjs
609
					return $matches;
610
				}
611
			} else {
612
				$matches = array();
613
				foreach ($blocks as $subBlock) {
614
					$subMatches = $this->findBlocks($subBlock,
615
						array_slice($path, 1), $orderedArgs, $keywordArgs, $seen);
616
617
					if (!is_null($subMatches)) {
618
						foreach ($subMatches as $sm) {
619
							$matches[] = $sm;
620
						}
621
					}
622
				}
623
624
				return count($matches) > 0 ? $matches : null;
625
			}
626
		}
627
		if ($searchIn->parent === $searchIn) return null;
628
		return $this->findBlocks($searchIn->parent, $path, $orderedArgs, $keywordArgs, $seen);
629
	}
630
631
	// sets all argument names in $args to either the default value
632
	// or the one passed in through $values
633
	protected function zipSetArgs($args, $orderedValues, $keywordValues) {
634
		$assignedValues = array();
635
636
		$i = 0;
637
		foreach ($args as  $a) {
638
			if ($a[0] == "arg") {
639
				if (isset($keywordValues[$a[1]])) {
640
					// has keyword arg
641
					$value = $keywordValues[$a[1]];
642
				} elseif (isset($orderedValues[$i])) {
643
					// has ordered arg
644
					$value = $orderedValues[$i];
645
					$i++;
646
				} elseif (isset($a[2])) {
647
					// has default value
648
					$value = $a[2];
649
				} else {
650
					$this->throwError("Failed to assign arg " . $a[1]);
651
					$value = null; // :(
652
				}
653
654
				$value = $this->reduce($value);
655
				$this->set($a[1], $value);
656
				$assignedValues[] = $value;
657
			} else {
658
				// a lit
659
				$i++;
660
			}
661
		}
662
663
		// check for a rest
664
		$last = end($args);
665
		if ($last[0] == "rest") {
666
			$rest = array_slice($orderedValues, count($args) - 1);
667
			$this->set($last[1], $this->reduce(array("list", " ", $rest)));
668
		}
669
670
		// wow is this the only true use of PHP's + operator for arrays?
671
		$this->env->arguments = $assignedValues + $orderedValues;
672
	}
673
674
	// compile a prop and update $lines or $blocks appropriately
675
	protected function compileProp($prop, $block, $out) {
676
		// set error position context
677
		$this->sourceLoc = isset($prop[-1]) ? $prop[-1] : -1;
678
679
		switch ($prop[0]) {
680
		case 'assign':
681
			list(, $name, $value) = $prop;
682
			if ($name[0] == $this->vPrefix) {
683
				$this->set($name, $value);
684
			} else {
685
				$out->lines[] = $this->formatter->property($name,
686
						$this->compileValue($this->reduce($value)));
687
			}
688
			break;
689
		case 'block':
690
			list(, $child) = $prop;
691
			$this->compileBlock($child);
692
			break;
693
		case 'mixin':
694
			list(, $path, $args, $suffix) = $prop;
695
696
			$orderedArgs = array();
697
			$keywordArgs = array();
698
			foreach ((array)$args as $arg) {
699
				$argval = null;
0 ignored issues
show
Unused Code introduced by
$argval is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
700
				switch ($arg[0]) {
701
				case "arg":
702
					if (!isset($arg[2])) {
703
						$orderedArgs[] = $this->reduce(array("variable", $arg[1]));
704
					} else {
705
						$keywordArgs[$arg[1]] = $this->reduce($arg[2]);
706
					}
707
					break;
708
709
				case "lit":
710
					$orderedArgs[] = $this->reduce($arg[1]);
711
					break;
712
				default:
713
					$this->throwError("Unknown arg type: " . $arg[0]);
714
				}
715
			}
716
717
			$mixins = $this->findBlocks($block, $path, $orderedArgs, $keywordArgs);
718
719
			if ($mixins === null) {
720
				$this->throwError("{$prop[1][0]} is undefined");
721
			}
722
723
			foreach ($mixins as $mixin) {
724
				if ($mixin === $block && !$orderedArgs) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $orderedArgs of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
725
					continue;
726
				}
727
728
				$haveScope = false;
729
				if (isset($mixin->parent->scope)) {
730
					$haveScope = true;
731
					$mixinParentEnv = $this->pushEnv();
732
					$mixinParentEnv->storeParent = $mixin->parent->scope;
733
				}
734
735
				$haveArgs = false;
736
				if (isset($mixin->args)) {
737
					$haveArgs = true;
738
					$this->pushEnv();
739
					$this->zipSetArgs($mixin->args, $orderedArgs, $keywordArgs);
740
				}
741
742
				$oldParent = $mixin->parent;
743
				if ($mixin != $block) $mixin->parent = $block;
744
745
				foreach ($this->sortProps($mixin->props) as $subProp) {
746
					if ($suffix !== null &&
747
						$subProp[0] == "assign" &&
748
						is_string($subProp[1]) &&
749
						$subProp[1]{0} != $this->vPrefix)
750
					{
751
						$subProp[2] = array(
752
							'list', ' ',
753
							array($subProp[2], array('keyword', $suffix))
754
						);
755
					}
756
757
					$this->compileProp($subProp, $mixin, $out);
758
				}
759
760
				$mixin->parent = $oldParent;
761
762
				if ($haveArgs) $this->popEnv();
763
				if ($haveScope) $this->popEnv();
764
			}
765
766
			break;
767
		case 'raw':
768
			$out->lines[] = $prop[1];
769
			break;
770
		case "directive":
771
			list(, $name, $value) = $prop;
772
			$out->lines[] = "@$name " . $this->compileValue($this->reduce($value)).';';
773
			break;
774
		case "comment":
775
			$out->lines[] = $prop[1];
776
			break;
777
		case "import";
778
			list(, $importPath, $importId) = $prop;
779
			$importPath = $this->reduce($importPath);
780
781
			if (!isset($this->env->imports)) {
782
				$this->env->imports = array();
783
			}
784
785
			$result = $this->tryImport($importPath, $block, $out);
786
787
			$this->env->imports[$importId] = $result === false ?
788
				array(false, "@import " . $this->compileValue($importPath).";") :
789
				$result;
790
791
			break;
792
		case "import_mixin":
793
			list(,$importId) = $prop;
794
			$import = $this->env->imports[$importId];
795
			if ($import[0] === false) {
796
				if (isset($import[1])) {
797
					$out->lines[] = $import[1];
798
				}
799
			} else {
800
				list(, $bottom, $parser, $importDir) = $import;
801
				$this->compileImportedProps($bottom, $block, $out, $parser, $importDir);
802
			}
803
804
			break;
805
		default:
806
			$this->throwError("unknown op: {$prop[0]}\n");
807
		}
808
	}
809
810
811
	/**
812
	 * Compiles a primitive value into a CSS property value.
813
	 *
814
	 * Values in lessphp are typed by being wrapped in arrays, their format is
815
	 * typically:
816
	 *
817
	 *     array(type, contents [, additional_contents]*)
818
	 *
819
	 * The input is expected to be reduced. This function will not work on
820
	 * things like expressions and variables.
821
	 */
822
	protected function compileValue($value) {
823
		switch ($value[0]) {
824
		case 'list':
825
			// [1] - delimiter
826
			// [2] - array of values
827
			return implode($value[1], array_map(array($this, 'compileValue'), $value[2]));
828
		case 'raw_color':
829
			if (!empty($this->formatter->compressColors)) {
830
				return $this->compileValue($this->coerceColor($value));
831
			}
832
			return $value[1];
833
		case 'keyword':
834
			// [1] - the keyword
835
			return $value[1];
836
		case 'number':
837
			list(, $num, $unit) = $value;
838
			// [1] - the number
839
			// [2] - the unit
840
			if ($this->numberPrecision !== null) {
841
				$num = round($num, $this->numberPrecision);
842
			}
843
			return $num . $unit;
844
		case 'string':
845
			// [1] - contents of string (includes quotes)
846
			list(, $delim, $content) = $value;
847
			foreach ($content as &$part) {
848
				if (is_array($part)) {
849
					$part = $this->compileValue($part);
850
				}
851
			}
852
			return $delim . implode($content) . $delim;
853
		case 'color':
854
			// [1] - red component (either number or a %)
855
			// [2] - green component
856
			// [3] - blue component
857
			// [4] - optional alpha component
858
			list(, $r, $g, $b) = $value;
859
			$r = round($r);
860
			$g = round($g);
861
			$b = round($b);
862
863
			if (count($value) == 5 && $value[4] != 1) { // rgba
864
				return 'rgba('.$r.','.$g.','.$b.','.$value[4].')';
865
			}
866
867
			$h = sprintf("#%02x%02x%02x", $r, $g, $b);
868
869
			if (!empty($this->formatter->compressColors)) {
870
				// Converting hex color to short notation (e.g. #003399 to #039)
871
				if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) {
872
					$h = '#' . $h[1] . $h[3] . $h[5];
873
				}
874
			}
875
876
			return $h;
877
878
		case 'function':
879
			list(, $name, $args) = $value;
880
			return $name.'('.$this->compileValue($args).')';
881
		default: // assumed to be unit
882
			$this->throwError("unknown value type: $value[0]");
883
		}
884
	}
885
886
	protected function lib_pow($args) {
887
		list($base, $exp) = $this->assertArgs($args, 2, "pow");
888
		return pow($this->assertNumber($base), $this->assertNumber($exp));
889
	}
890
891
	protected function lib_pi() {
892
		return pi();
893
	}
894
895
	protected function lib_mod($args) {
896
		list($a, $b) = $this->assertArgs($args, 2, "mod");
897
		return $this->assertNumber($a) % $this->assertNumber($b);
898
	}
899
900
	protected function lib_tan($num) {
901
		return tan($this->assertNumber($num));
902
	}
903
904
	protected function lib_sin($num) {
905
		return sin($this->assertNumber($num));
906
	}
907
908
	protected function lib_cos($num) {
909
		return cos($this->assertNumber($num));
910
	}
911
912
	protected function lib_atan($num) {
913
		$num = atan($this->assertNumber($num));
914
		return array("number", $num, "rad");
915
	}
916
917
	protected function lib_asin($num) {
918
		$num = asin($this->assertNumber($num));
919
		return array("number", $num, "rad");
920
	}
921
922
	protected function lib_acos($num) {
923
		$num = acos($this->assertNumber($num));
924
		return array("number", $num, "rad");
925
	}
926
927
	protected function lib_sqrt($num) {
928
		return sqrt($this->assertNumber($num));
929
	}
930
931
	protected function lib_extract($value) {
932
		list($list, $idx) = $this->assertArgs($value, 2, "extract");
933
		$idx = $this->assertNumber($idx);
934
		// 1 indexed
935
		if ($list[0] == "list" && isset($list[2][$idx - 1])) {
936
			return $list[2][$idx - 1];
937
		}
938
	}
939
940
	protected function lib_isnumber($value) {
941
		return $this->toBool($value[0] == "number");
942
	}
943
944
	protected function lib_isstring($value) {
945
		return $this->toBool($value[0] == "string");
946
	}
947
948
	protected function lib_iscolor($value) {
949
		return $this->toBool($this->coerceColor($value));
950
	}
951
952
	protected function lib_iskeyword($value) {
953
		return $this->toBool($value[0] == "keyword");
954
	}
955
956
	protected function lib_ispixel($value) {
957
		return $this->toBool($value[0] == "number" && $value[2] == "px");
958
	}
959
960
	protected function lib_ispercentage($value) {
961
		return $this->toBool($value[0] == "number" && $value[2] == "%");
962
	}
963
964
	protected function lib_isem($value) {
965
		return $this->toBool($value[0] == "number" && $value[2] == "em");
966
	}
967
968
	protected function lib_isrem($value) {
969
		return $this->toBool($value[0] == "number" && $value[2] == "rem");
970
	}
971
972
	protected function lib_rgbahex($color) {
973
		$color = $this->coerceColor($color);
974
		if (is_null($color))
975
			$this->throwError("color expected for rgbahex");
976
977
		return sprintf("#%02x%02x%02x%02x",
978
			isset($color[4]) ? $color[4]*255 : 255,
979
			$color[1],$color[2], $color[3]);
980
	}
981
982
	protected function lib_argb($color){
983
		return $this->lib_rgbahex($color);
984
	}
985
986
	// utility func to unquote a string
987
	protected function lib_e($arg) {
988
		switch ($arg[0]) {
989
			case "list":
990
				$items = $arg[2];
991
				if (isset($items[0])) {
992
					return $this->lib_e($items[0]);
993
				}
994
				$this->throwError("unrecognised input");
995
			case "string":
996
				$arg[1] = "";
997
				return $arg;
998
			case "keyword":
999
				return $arg;
1000
			default:
1001
				return array("keyword", $this->compileValue($arg));
1002
		}
1003
	}
1004
1005
	protected function lib__sprintf($args) {
1006
		if ($args[0] != "list") return $args;
1007
		$values = $args[2];
1008
		$string = array_shift($values);
1009
		$template = $this->compileValue($this->lib_e($string));
1010
1011
		$i = 0;
1012
		if (preg_match_all('/%[dsa]/', $template, $m)) {
1013
			foreach ($m[0] as $match) {
1014
				$val = isset($values[$i]) ?
1015
					$this->reduce($values[$i]) : array('keyword', '');
1016
1017
				// lessjs compat, renders fully expanded color, not raw color
1018
				if ($color = $this->coerceColor($val)) {
1019
					$val = $color;
1020
				}
1021
1022
				$i++;
1023
				$rep = $this->compileValue($this->lib_e($val));
1024
				$template = preg_replace('/'.self::preg_quote($match).'/',
1025
					$rep, $template, 1);
1026
			}
1027
		}
1028
1029
		$d = $string[0] == "string" ? $string[1] : '"';
1030
		return array("string", $d, array($template));
1031
	}
1032
1033
	protected function lib_floor($arg) {
1034
		$value = $this->assertNumber($arg);
1035
		return array("number", floor($value), $arg[2]);
1036
	}
1037
1038
	protected function lib_ceil($arg) {
1039
		$value = $this->assertNumber($arg);
1040
		return array("number", ceil($value), $arg[2]);
1041
	}
1042
1043
	protected function lib_round($arg) {
1044
		if($arg[0] != "list") {
1045
			$value = $this->assertNumber($arg);
1046
			return array("number", round($value), $arg[2]);
1047
		} else {
1048
			$value = $this->assertNumber($arg[2][0]);
1049
			$precision = $this->assertNumber($arg[2][1]);
1050
			return array("number", round($value, $precision), $arg[2][0][2]);
1051
		}
1052
	}
1053
1054
	protected function lib_unit($arg) {
1055
		if ($arg[0] == "list") {
1056
			list($number, $newUnit) = $arg[2];
1057
			return array("number", $this->assertNumber($number),
1058
				$this->compileValue($this->lib_e($newUnit)));
1059
		} else {
1060
			return array("number", $this->assertNumber($arg), "");
1061
		}
1062
	}
1063
1064
	/**
1065
	 * Helper function to get arguments for color manipulation functions.
1066
	 * takes a list that contains a color like thing and a percentage
1067
	 */
1068
	public function colorArgs($args) {
1069
		if ($args[0] != 'list' || count($args[2]) < 2) {
1070
			return array(array('color', 0, 0, 0), 0);
1071
		}
1072
		list($color, $delta) = $args[2];
1073
		$color = $this->assertColor($color);
1074
		$delta = floatval($delta[1]);
1075
1076
		return array($color, $delta);
1077
	}
1078
1079 View Code Duplication
	protected function lib_darken($args) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1080
		list($color, $delta) = $this->colorArgs($args);
1081
1082
		$hsl = $this->toHSL($color);
1083
		$hsl[3] = $this->clamp($hsl[3] - $delta, 100);
1084
		return $this->toRGB($hsl);
1085
	}
1086
1087 View Code Duplication
	protected function lib_lighten($args) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1088
		list($color, $delta) = $this->colorArgs($args);
1089
1090
		$hsl = $this->toHSL($color);
1091
		$hsl[3] = $this->clamp($hsl[3] + $delta, 100);
1092
		return $this->toRGB($hsl);
1093
	}
1094
1095 View Code Duplication
	protected function lib_saturate($args) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1096
		list($color, $delta) = $this->colorArgs($args);
1097
1098
		$hsl = $this->toHSL($color);
1099
		$hsl[2] = $this->clamp($hsl[2] + $delta, 100);
1100
		return $this->toRGB($hsl);
1101
	}
1102
1103 View Code Duplication
	protected function lib_desaturate($args) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1104
		list($color, $delta) = $this->colorArgs($args);
1105
1106
		$hsl = $this->toHSL($color);
1107
		$hsl[2] = $this->clamp($hsl[2] - $delta, 100);
1108
		return $this->toRGB($hsl);
1109
	}
1110
1111
	protected function lib_spin($args) {
1112
		list($color, $delta) = $this->colorArgs($args);
1113
1114
		$hsl = $this->toHSL($color);
1115
1116
		$hsl[1] = $hsl[1] + $delta % 360;
1117
		if ($hsl[1] < 0) $hsl[1] += 360;
1118
1119
		return $this->toRGB($hsl);
1120
	}
1121
1122
	protected function lib_fadeout($args) {
1123
		list($color, $delta) = $this->colorArgs($args);
1124
		$color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) - $delta/100);
1125
		return $color;
1126
	}
1127
1128
	protected function lib_fadein($args) {
1129
		list($color, $delta) = $this->colorArgs($args);
1130
		$color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) + $delta/100);
1131
		return $color;
1132
	}
1133
1134
	protected function lib_hue($color) {
1135
		$hsl = $this->toHSL($this->assertColor($color));
1136
		return round($hsl[1]);
1137
	}
1138
1139
	protected function lib_saturation($color) {
1140
		$hsl = $this->toHSL($this->assertColor($color));
1141
		return round($hsl[2]);
1142
	}
1143
1144
	protected function lib_lightness($color) {
1145
		$hsl = $this->toHSL($this->assertColor($color));
1146
		return round($hsl[3]);
1147
	}
1148
1149
	// get the alpha of a color
1150
	// defaults to 1 for non-colors or colors without an alpha
1151
	protected function lib_alpha($value) {
1152
		if (!is_null($color = $this->coerceColor($value))) {
1153
			return isset($color[4]) ? $color[4] : 1;
1154
		}
1155
	}
1156
1157
	// set the alpha of the color
1158
	protected function lib_fade($args) {
1159
		list($color, $alpha) = $this->colorArgs($args);
1160
		$color[4] = $this->clamp($alpha / 100.0);
1161
		return $color;
1162
	}
1163
1164
	protected function lib_percentage($arg) {
1165
		$num = $this->assertNumber($arg);
1166
		return array("number", $num*100, "%");
1167
	}
1168
1169
	// mixes two colors by weight
1170
	// mix(@color1, @color2, [@weight: 50%]);
1171
	// http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#mix-instance_method
1172
	protected function lib_mix($args) {
1173
		if ($args[0] != "list" || count($args[2]) < 2)
1174
			$this->throwError("mix expects (color1, color2, weight)");
1175
1176
		list($first, $second) = $args[2];
1177
		$first = $this->assertColor($first);
1178
		$second = $this->assertColor($second);
1179
1180
		$first_a = $this->lib_alpha($first);
1181
		$second_a = $this->lib_alpha($second);
1182
1183
		if (isset($args[2][2])) {
1184
			$weight = $args[2][2][1] / 100.0;
1185
		} else {
1186
			$weight = 0.5;
1187
		}
1188
1189
		$w = $weight * 2 - 1;
1190
		$a = $first_a - $second_a;
1191
1192
		$w1 = (($w * $a == -1 ? $w : ($w + $a)/(1 + $w * $a)) + 1) / 2.0;
1193
		$w2 = 1.0 - $w1;
1194
1195
		$new = array('color',
1196
			$w1 * $first[1] + $w2 * $second[1],
1197
			$w1 * $first[2] + $w2 * $second[2],
1198
			$w1 * $first[3] + $w2 * $second[3],
1199
		);
1200
1201
		if ($first_a != 1.0 || $second_a != 1.0) {
1202
			$new[] = $first_a * $weight + $second_a * ($weight - 1);
1203
		}
1204
1205
		return $this->fixColor($new);
1206
	}
1207
1208
	protected function lib_contrast($args) {
1209
	    $darkColor  = array('color', 0, 0, 0);
1210
	    $lightColor = array('color', 255, 255, 255);
1211
	    $threshold  = 0.43;
1212
1213
	    if ( $args[0] == 'list' ) {
1214
	        $inputColor = ( isset($args[2][0]) ) ? $this->assertColor($args[2][0])  : $lightColor;
1215
	        $darkColor  = ( isset($args[2][1]) ) ? $this->assertColor($args[2][1])  : $darkColor;
1216
	        $lightColor = ( isset($args[2][2]) ) ? $this->assertColor($args[2][2])  : $lightColor;
1217
	        $threshold  = ( isset($args[2][3]) ) ? $this->assertNumber($args[2][3]) : $threshold;
1218
	    }
1219
	    else {
1220
	        $inputColor  = $this->assertColor($args);
1221
	    }
1222
1223
	    $inputColor = $this->coerceColor($inputColor);
1224
	    $darkColor  = $this->coerceColor($darkColor);
1225
	    $lightColor = $this->coerceColor($lightColor);
1226
1227
	    //Figure out which is actually light and dark!
1228
	    if ( $this->lib_luma($darkColor) > $this->lib_luma($lightColor) ) {
1229
	        $t  = $lightColor;
1230
	        $lightColor = $darkColor;
1231
	        $darkColor  = $t;
1232
	    }
1233
1234
	    $inputColor_alpha = $this->lib_alpha($inputColor);
1235
	    if ( ( $this->lib_luma($inputColor) * $inputColor_alpha) < $threshold) {
1236
	        return $lightColor;
1237
	    }
1238
	    return $darkColor;
1239
	}
1240
1241
	protected function lib_luma($color) {
1242
	    $color = $this->coerceColor($color);
1243
	    return (0.2126 * $color[0] / 255) + (0.7152 * $color[1] / 255) + (0.0722 * $color[2] / 255);
1244
	}
1245
1246
1247
	public function assertColor($value, $error = "expected color value") {
1248
		$color = $this->coerceColor($value);
1249
		if (is_null($color)) $this->throwError($error);
1250
		return $color;
1251
	}
1252
1253
	public function assertNumber($value, $error = "expecting number") {
1254
		if ($value[0] == "number") return $value[1];
1255
		$this->throwError($error);
1256
	}
1257
1258
	public function assertArgs($value, $expectedArgs, $name="") {
1259
		if ($expectedArgs == 1) {
1260
			return $value;
1261
		} else {
1262
			if ($value[0] !== "list" || $value[1] != ",") $this->throwError("expecting list");
1263
			$values = $value[2];
1264
			$numValues = count($values);
1265
			if ($expectedArgs != $numValues) {
1266
				if ($name) {
1267
					$name = $name . ": ";
1268
				}
1269
1270
				$this->throwError("${name}expecting $expectedArgs arguments, got $numValues");
1271
			}
1272
1273
			return $values;
1274
		}
1275
	}
1276
1277
	protected function toHSL($color) {
1278
		if ($color[0] == 'hsl') return $color;
1279
1280
		$r = $color[1] / 255;
1281
		$g = $color[2] / 255;
1282
		$b = $color[3] / 255;
1283
1284
		$min = min($r, $g, $b);
1285
		$max = max($r, $g, $b);
1286
1287
		$L = ($min + $max) / 2;
1288
		if ($min == $max) {
1289
			$S = $H = 0;
1290
		} else {
1291
			if ($L < 0.5)
1292
				$S = ($max - $min)/($max + $min);
1293
			else
1294
				$S = ($max - $min)/(2.0 - $max - $min);
1295
1296
			if ($r == $max) $H = ($g - $b)/($max - $min);
1297
			elseif ($g == $max) $H = 2.0 + ($b - $r)/($max - $min);
1298
			elseif ($b == $max) $H = 4.0 + ($r - $g)/($max - $min);
1299
1300
		}
1301
1302
		$out = array('hsl',
1303
			($H < 0 ? $H + 6 : $H)*60,
0 ignored issues
show
Bug introduced by
The variable $H does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1304
			$S*100,
1305
			$L*100,
1306
		);
1307
1308
		if (count($color) > 4) $out[] = $color[4]; // copy alpha
1309
		return $out;
1310
	}
1311
1312
	protected function toRGB_helper($comp, $temp1, $temp2) {
1313
		if ($comp < 0) $comp += 1.0;
1314
		elseif ($comp > 1) $comp -= 1.0;
1315
1316 View Code Duplication
		if (6 * $comp < 1) return $temp1 + ($temp2 - $temp1) * 6 * $comp;
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1317
		if (2 * $comp < 1) return $temp2;
1318 View Code Duplication
		if (3 * $comp < 2) return $temp1 + ($temp2 - $temp1)*((2/3) - $comp) * 6;
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1319
1320
		return $temp1;
1321
	}
1322
1323
	/**
1324
	 * Converts a hsl array into a color value in rgb.
1325
	 * Expects H to be in range of 0 to 360, S and L in 0 to 100
1326
	 */
1327
	protected function toRGB($color) {
1328
		if ($color[0] == 'color') return $color;
1329
1330
		$H = $color[1] / 360;
1331
		$S = $color[2] / 100;
1332
		$L = $color[3] / 100;
1333
1334
		if ($S == 0) {
1335
			$r = $g = $b = $L;
1336
		} else {
1337
			$temp2 = $L < 0.5 ?
1338
				$L*(1.0 + $S) :
1339
				$L + $S - $L * $S;
1340
1341
			$temp1 = 2.0 * $L - $temp2;
1342
1343
			$r = $this->toRGB_helper($H + 1/3, $temp1, $temp2);
1344
			$g = $this->toRGB_helper($H, $temp1, $temp2);
1345
			$b = $this->toRGB_helper($H - 1/3, $temp1, $temp2);
1346
		}
1347
1348
		// $out = array('color', round($r*255), round($g*255), round($b*255));
1349
		$out = array('color', $r*255, $g*255, $b*255);
1350
		if (count($color) > 4) $out[] = $color[4]; // copy alpha
1351
		return $out;
1352
	}
1353
1354
	protected function clamp($v, $max = 1, $min = 0) {
1355
		return min($max, max($min, $v));
1356
	}
1357
1358
	/**
1359
	 * Convert the rgb, rgba, hsl color literals of function type
1360
	 * as returned by the parser into values of color type.
1361
	 */
1362
	protected function funcToColor($func) {
1363
		$fname = $func[1];
1364
		if ($func[2][0] != 'list') return false; // need a list of arguments
1365
		$rawComponents = $func[2][2];
1366
1367
		if ($fname == 'hsl' || $fname == 'hsla') {
1368
			$hsl = array('hsl');
1369
			$i = 0;
1370
			foreach ($rawComponents as $c) {
1371
				$val = $this->reduce($c);
1372
				$val = isset($val[1]) ? floatval($val[1]) : 0;
1373
1374
				if ($i == 0) $clamp = 360;
1375
				elseif ($i < 3) $clamp = 100;
1376
				else $clamp = 1;
1377
1378
				$hsl[] = $this->clamp($val, $clamp);
1379
				$i++;
1380
			}
1381
1382
			while (count($hsl) < 4) $hsl[] = 0;
1383
			return $this->toRGB($hsl);
1384
1385
		} elseif ($fname == 'rgb' || $fname == 'rgba') {
1386
			$components = array();
1387
			$i = 1;
1388
			foreach	($rawComponents as $c) {
1389
				$c = $this->reduce($c);
1390
				if ($i < 4) {
1391 View Code Duplication
					if ($c[0] == "number" && $c[2] == "%") {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1392
						$components[] = 255 * ($c[1] / 100);
1393
					} else {
1394
						$components[] = floatval($c[1]);
1395
					}
1396 View Code Duplication
				} elseif ($i == 4) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1397
					if ($c[0] == "number" && $c[2] == "%") {
1398
						$components[] = 1.0 * ($c[1] / 100);
1399
					} else {
1400
						$components[] = floatval($c[1]);
1401
					}
1402
				} else break;
1403
1404
				$i++;
1405
			}
1406
			while (count($components) < 3) $components[] = 0;
1407
			array_unshift($components, 'color');
1408
			return $this->fixColor($components);
1409
		}
1410
1411
		return false;
1412
	}
1413
1414
	protected function reduce($value, $forExpression = false) {
1415
		switch ($value[0]) {
1416
		case "interpolate":
1417
			$reduced = $this->reduce($value[1]);
1418
			$var = $this->compileValue($reduced);
1419
			$res = $this->reduce(array("variable", $this->vPrefix . $var));
1420
1421
			if ($res[0] == "raw_color") {
1422
				$res = $this->coerceColor($res);
1423
			}
1424
1425
			if (empty($value[2])) $res = $this->lib_e($res);
1426
1427
			return $res;
1428
		case "variable":
1429
			$key = $value[1];
1430
			if (is_array($key)) {
1431
				$key = $this->reduce($key);
1432
				$key = $this->vPrefix . $this->compileValue($this->lib_e($key));
1433
			}
1434
1435
			$seen =& $this->env->seenNames;
1436
1437
			if (!empty($seen[$key])) {
1438
				$this->throwError("infinite loop detected: $key");
1439
			}
1440
1441
			$seen[$key] = true;
1442
			$out = $this->reduce($this->get($key));
1443
			$seen[$key] = false;
1444
			return $out;
1445
		case "list":
1446
			foreach ($value[2] as &$item) {
1447
				$item = $this->reduce($item, $forExpression);
1448
			}
1449
			return $value;
1450
		case "expression":
1451
			return $this->evaluate($value);
1452
		case "string":
1453
			foreach ($value[2] as &$part) {
1454
				if (is_array($part)) {
1455
					$strip = $part[0] == "variable";
1456
					$part = $this->reduce($part);
1457
					if ($strip) $part = $this->lib_e($part);
1458
				}
1459
			}
1460
			return $value;
1461
		case "escape":
1462
			list(,$inner) = $value;
1463
			return $this->lib_e($this->reduce($inner));
1464
		case "function":
1465
			$color = $this->funcToColor($value);
1466
			if ($color) return $color;
1467
1468
			list(, $name, $args) = $value;
1469
			if ($name == "%") $name = "_sprintf";
1470
			$f = isset($this->libFunctions[$name]) ?
1471
				$this->libFunctions[$name] : array($this, 'lib_'.$name);
1472
1473
			if (is_callable($f)) {
1474
				if ($args[0] == 'list')
1475
					$args = self::compressList($args[2], $args[1]);
1476
1477
				$ret = call_user_func($f, $this->reduce($args, true), $this);
1478
1479
				if (is_null($ret)) {
1480
					return array("string", "", array(
1481
						$name, "(", $args, ")"
1482
					));
1483
				}
1484
1485
				// convert to a typed value if the result is a php primitive
1486
				if (is_numeric($ret)) $ret = array('number', $ret, "");
1487
				elseif (!is_array($ret)) $ret = array('keyword', $ret);
1488
1489
				return $ret;
1490
			}
1491
1492
			// plain function, reduce args
1493
			$value[2] = $this->reduce($value[2]);
1494
			return $value;
1495
		case "unary":
1496
			list(, $op, $exp) = $value;
1497
			$exp = $this->reduce($exp);
1498
1499
			if ($exp[0] == "number") {
1500
				switch ($op) {
1501
				case "+":
1502
					return $exp;
1503
				case "-":
1504
					$exp[1] *= -1;
1505
					return $exp;
1506
				}
1507
			}
1508
			return array("string", "", array($op, $exp));
1509
		}
1510
1511
		if ($forExpression) {
1512
			switch ($value[0]) {
1513
			case "keyword":
1514
				if ($color = $this->coerceColor($value)) {
1515
					return $color;
1516
				}
1517
				break;
1518
			case "raw_color":
1519
				return $this->coerceColor($value);
1520
			}
1521
		}
1522
1523
		return $value;
1524
	}
1525
1526
1527
	// coerce a value for use in color operation
1528
	protected function coerceColor($value) {
1529
		switch($value[0]) {
1530
			case 'color': return $value;
1531
			case 'raw_color':
1532
				$c = array("color", 0, 0, 0);
1533
				$colorStr = substr($value[1], 1);
1534
				$num = hexdec($colorStr);
1535
				$width = strlen($colorStr) == 3 ? 16 : 256;
1536
1537
				for ($i = 3; $i > 0; $i--) { // 3 2 1
1538
					$t = $num % $width;
1539
					$num /= $width;
1540
1541
					$c[$i] = $t * (256/$width) + $t * floor(16/$width);
1542
				}
1543
1544
				return $c;
1545
			case 'keyword':
1546
				$name = $value[1];
1547
				if (isset(self::$cssColors[$name])) {
1548
					$rgba = explode(',', self::$cssColors[$name]);
1549
1550
					if(isset($rgba[3]))
1551
						return array('color', $rgba[0], $rgba[1], $rgba[2], $rgba[3]);
1552
1553
					return array('color', $rgba[0], $rgba[1], $rgba[2]);
1554
				}
1555
				return null;
1556
		}
1557
	}
1558
1559
	// make something string like into a string
1560
	protected function coerceString($value) {
1561
		switch ($value[0]) {
1562
		case "string":
1563
			return $value;
1564
		case "keyword":
1565
			return array("string", "", array($value[1]));
1566
		}
1567
		return null;
1568
	}
1569
1570
	// turn list of length 1 into value type
1571
	protected function flattenList($value) {
1572
		if ($value[0] == "list" && count($value[2]) == 1) {
1573
			return $this->flattenList($value[2][0]);
1574
		}
1575
		return $value;
1576
	}
1577
1578
	public function toBool($a) {
1579
		if ($a) return self::$TRUE;
1580
		else return self::$FALSE;
1581
	}
1582
1583
	// evaluate an expression
1584
	protected function evaluate($exp) {
1585
		list(, $op, $left, $right, $whiteBefore, $whiteAfter) = $exp;
1586
1587
		$left = $this->reduce($left, true);
1588
		$right = $this->reduce($right, true);
1589
1590
		if ($leftColor = $this->coerceColor($left)) {
1591
			$left = $leftColor;
1592
		}
1593
1594
		if ($rightColor = $this->coerceColor($right)) {
1595
			$right = $rightColor;
1596
		}
1597
1598
		$ltype = $left[0];
1599
		$rtype = $right[0];
1600
1601
		// operators that work on all types
1602
		if ($op == "and") {
1603
			return $this->toBool($left == self::$TRUE && $right == self::$TRUE);
1604
		}
1605
1606
		if ($op == "=") {
1607
			return $this->toBool($this->eq($left, $right) );
1608
		}
1609
1610
		if ($op == "+" && !is_null($str = $this->stringConcatenate($left, $right))) {
1611
			return $str;
1612
		}
1613
1614
		// type based operators
1615
		$fname = "op_${ltype}_${rtype}";
1616
		if (is_callable(array($this, $fname))) {
1617
			$out = $this->$fname($op, $left, $right);
1618
			if (!is_null($out)) return $out;
1619
		}
1620
1621
		// make the expression look it did before being parsed
1622
		$paddedOp = $op;
1623
		if ($whiteBefore) $paddedOp = " " . $paddedOp;
1624
		if ($whiteAfter) $paddedOp .= " ";
1625
1626
		return array("string", "", array($left, $paddedOp, $right));
1627
	}
1628
1629
	protected function stringConcatenate($left, $right) {
1630
		if ($strLeft = $this->coerceString($left)) {
1631
			if ($right[0] == "string") {
1632
				$right[1] = "";
1633
			}
1634
			$strLeft[2][] = $right;
1635
			return $strLeft;
1636
		}
1637
1638
		if ($strRight = $this->coerceString($right)) {
1639
			array_unshift($strRight[2], $left);
1640
			return $strRight;
1641
		}
1642
	}
1643
1644
1645
	// make sure a color's components don't go out of bounds
1646
	protected function fixColor($c) {
1647
		foreach (range(1, 3) as $i) {
1648
			if ($c[$i] < 0) $c[$i] = 0;
1649
			if ($c[$i] > 255) $c[$i] = 255;
1650
		}
1651
1652
		return $c;
1653
	}
1654
1655
	protected function op_number_color($op, $lft, $rgt) {
1656
		if ($op == '+' || $op == '*') {
1657
			return $this->op_color_number($op, $rgt, $lft);
1658
		}
1659
	}
1660
1661
	protected function op_color_number($op, $lft, $rgt) {
1662
		if ($rgt[0] == '%') $rgt[1] /= 100;
1663
1664
		return $this->op_color_color($op, $lft,
1665
			array_fill(1, count($lft) - 1, $rgt[1]));
1666
	}
1667
1668
	protected function op_color_color($op, $left, $right) {
1669
		$out = array('color');
1670
		$max = count($left) > count($right) ? count($left) : count($right);
1671
		foreach (range(1, $max - 1) as $i) {
1672
			$lval = isset($left[$i]) ? $left[$i] : 0;
1673
			$rval = isset($right[$i]) ? $right[$i] : 0;
1674
			switch ($op) {
1675
			case '+':
1676
				$out[] = $lval + $rval;
1677
				break;
1678
			case '-':
1679
				$out[] = $lval - $rval;
1680
				break;
1681
			case '*':
1682
				$out[] = $lval * $rval;
1683
				break;
1684
			case '%':
1685
				$out[] = $lval % $rval;
1686
				break;
1687
			case '/':
1688
				if ($rval == 0) $this->throwError("evaluate error: can't divide by zero");
1689
				$out[] = $lval / $rval;
1690
				break;
1691
			default:
1692
				$this->throwError('evaluate error: color op number failed on op '.$op);
1693
			}
1694
		}
1695
		return $this->fixColor($out);
1696
	}
1697
1698 View Code Duplication
	function lib_red($color){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1699
		$color = $this->coerceColor($color);
1700
		if (is_null($color)) {
1701
			$this->throwError('color expected for red()');
1702
		}
1703
1704
		return $color[1];
1705
	}
1706
1707 View Code Duplication
	function lib_green($color){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1708
		$color = $this->coerceColor($color);
1709
		if (is_null($color)) {
1710
			$this->throwError('color expected for green()');
1711
		}
1712
1713
		return $color[2];
1714
	}
1715
1716 View Code Duplication
	function lib_blue($color){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1717
		$color = $this->coerceColor($color);
1718
		if (is_null($color)) {
1719
			$this->throwError('color expected for blue()');
1720
		}
1721
1722
		return $color[3];
1723
	}
1724
1725
1726
	// operator on two numbers
1727
	protected function op_number_number($op, $left, $right) {
1728
		$unit = empty($left[2]) ? $right[2] : $left[2];
1729
1730
		$value = 0;
1731
		switch ($op) {
1732
		case '+':
1733
			$value = $left[1] + $right[1];
1734
			break;
1735
		case '*':
1736
			$value = $left[1] * $right[1];
1737
			break;
1738
		case '-':
1739
			$value = $left[1] - $right[1];
1740
			break;
1741
		case '%':
1742
			$value = $left[1] % $right[1];
1743
			break;
1744
		case '/':
1745
			if ($right[1] == 0) $this->throwError('parse error: divide by zero');
1746
			$value = $left[1] / $right[1];
1747
			break;
1748
		case '<':
1749
			return $this->toBool($left[1] < $right[1]);
1750
		case '>':
1751
			return $this->toBool($left[1] > $right[1]);
1752
		case '>=':
1753
			return $this->toBool($left[1] >= $right[1]);
1754
		case '=<':
1755
			return $this->toBool($left[1] <= $right[1]);
1756
		default:
1757
			$this->throwError('parse error: unknown number operator: '.$op);
1758
		}
1759
1760
		return array("number", $value, $unit);
1761
	}
1762
1763
1764
	/* environment functions */
1765
1766
	protected function makeOutputBlock($type, $selectors = null) {
1767
		$b = new stdclass;
1768
		$b->lines = array();
1769
		$b->children = array();
1770
		$b->selectors = $selectors;
1771
		$b->type = $type;
1772
		$b->parent = $this->scope;
1773
		return $b;
1774
	}
1775
1776
	// the state of execution
1777
	protected function pushEnv($block = null) {
1778
		$e = new stdclass;
1779
		$e->parent = $this->env;
1780
		$e->store = array();
1781
		$e->block = $block;
1782
1783
		$this->env = $e;
1784
		return $e;
1785
	}
1786
1787
	// pop something off the stack
1788
	protected function popEnv() {
1789
		$old = $this->env;
1790
		$this->env = $this->env->parent;
1791
		return $old;
1792
	}
1793
1794
	// set something in the current env
1795
	protected function set($name, $value) {
1796
		$this->env->store[$name] = $value;
1797
	}
1798
1799
1800
	// get the highest occurrence entry for a name
1801
	protected function get($name) {
1802
		$current = $this->env;
1803
1804
		$isArguments = $name == $this->vPrefix . 'arguments';
1805
		while ($current) {
1806
			if ($isArguments && isset($current->arguments)) {
1807
				return array('list', ' ', $current->arguments);
1808
			}
1809
1810
			if (isset($current->store[$name]))
1811
				return $current->store[$name];
1812
			else {
1813
				$current = isset($current->storeParent) ?
1814
					$current->storeParent : $current->parent;
1815
			}
1816
		}
1817
1818
		$this->throwError("variable $name is undefined");
1819
	}
1820
1821
	// inject array of unparsed strings into environment as variables
1822
	protected function injectVariables($args) {
1823
		$this->pushEnv();
1824
		$parser = new lessc_parser($this, __METHOD__);
1825
		foreach ($args as $name => $strValue) {
1826
			if ($name{0} != '@') $name = '@'.$name;
1827
			$parser->count = 0;
0 ignored issues
show
Bug introduced by
The property count does not seem to exist in lessc_parser.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1828
			$parser->buffer = (string)$strValue;
0 ignored issues
show
Bug introduced by
The property buffer does not seem to exist in lessc_parser.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1829
			if (!$parser->propertyValue($value)) {
1830
				throw new Exception("failed to parse passed in variable $name: $strValue");
1831
			}
1832
1833
			$this->set($name, $value);
1834
		}
1835
	}
1836
1837
	/**
1838
	 * Initialize any static state, can initialize parser for a file
1839
	 * $opts isn't used yet
1840
	 */
1841
	public function __construct($fname = null) {
1842
		if ($fname !== null) {
1843
			// used for deprecated parse method
1844
			$this->_parseFile = $fname;
0 ignored issues
show
Bug introduced by
The property _parseFile does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
1845
		}
1846
	}
1847
1848
	public function compile($string, $name = null) {
1849
		$locale = setlocale(LC_NUMERIC, 0);
1850
		setlocale(LC_NUMERIC, "C");
1851
1852
		$this->parser = $this->makeParser($name);
0 ignored issues
show
Bug introduced by
The property parser does not seem to exist. Did you mean sourceParser?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1853
		$root = $this->parser->parse($string);
0 ignored issues
show
Bug introduced by
The property parser does not seem to exist. Did you mean sourceParser?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1854
1855
		$this->env = null;
1856
		$this->scope = null;
1857
1858
		$this->formatter = $this->newFormatter();
1859
1860
		if (!empty($this->registeredVars)) {
1861
			$this->injectVariables($this->registeredVars);
1862
		}
1863
1864
		$this->sourceParser = $this->parser; // used for error messages
0 ignored issues
show
Bug introduced by
The property parser does not seem to exist. Did you mean sourceParser?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1865
		$this->compileBlock($root);
1866
1867
		ob_start();
1868
		$this->formatter->block($this->scope);
1869
		$out = ob_get_clean();
1870
		setlocale(LC_NUMERIC, $locale);
1871
		return $out;
1872
	}
1873
1874
	public function compileFile($fname, $outFname = null) {
1875
		if (!is_readable($fname)) {
1876
			throw new Exception('load error: failed to find '.$fname);
1877
		}
1878
1879
		$pi = pathinfo($fname);
1880
1881
		$oldImport = $this->importDir;
1882
1883
		$this->importDir = (array)$this->importDir;
0 ignored issues
show
Documentation Bug introduced by
It seems like (array) $this->importDir of type array is incompatible with the declared type string of property $importDir.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1884
		$this->importDir[] = $pi['dirname'].'/';
1885
1886
		$this->addParsedFile($fname);
1887
1888
		$out = $this->compile(file_get_contents($fname), $fname);
1889
1890
		$this->importDir = $oldImport;
1891
1892
		if ($outFname !== null) {
1893
			return file_put_contents($outFname, $out);
1894
		}
1895
1896
		return $out;
1897
	}
1898
1899
	// compile only if changed input has changed or output doesn't exist
1900
	public function checkedCompile($in, $out) {
1901
		if (!is_file($out) || filemtime($in) > filemtime($out)) {
1902
			$this->compileFile($in, $out);
1903
			return true;
1904
		}
1905
		return false;
1906
	}
1907
1908
	/**
1909
	 * Execute lessphp on a .less file or a lessphp cache structure
1910
	 *
1911
	 * The lessphp cache structure contains information about a specific
1912
	 * less file having been parsed. It can be used as a hint for future
1913
	 * calls to determine whether or not a rebuild is required.
1914
	 *
1915
	 * The cache structure contains two important keys that may be used
1916
	 * externally:
1917
	 *
1918
	 * compiled: The final compiled CSS
1919
	 * updated: The time (in seconds) the CSS was last compiled
1920
	 *
1921
	 * The cache structure is a plain-ol' PHP associative array and can
1922
	 * be serialized and unserialized without a hitch.
1923
	 *
1924
	 * @param mixed $in Input
1925
	 * @param bool $force Force rebuild?
1926
	 * @return array lessphp cache structure
1927
	 */
1928
	public function cachedCompile($in, $force = false) {
1929
		// assume no root
1930
		$root = null;
1931
1932
		if (is_string($in)) {
1933
			$root = $in;
1934
		} elseif (is_array($in) and isset($in['root'])) {
1935
			if ($force or ! isset($in['files'])) {
1936
				// If we are forcing a recompile or if for some reason the
1937
				// structure does not contain any file information we should
1938
				// specify the root to trigger a rebuild.
1939
				$root = $in['root'];
1940
			} elseif (isset($in['files']) and is_array($in['files'])) {
1941
				foreach ($in['files'] as $fname => $ftime ) {
1942
					if (!file_exists($fname) or filemtime($fname) > $ftime) {
1943
						// One of the files we knew about previously has changed
1944
						// so we should look at our incoming root again.
1945
						$root = $in['root'];
1946
						break;
1947
					}
1948
				}
1949
			}
1950
		} else {
1951
			// TODO: Throw an exception? We got neither a string nor something
1952
			// that looks like a compatible lessphp cache structure.
1953
			return null;
1954
		}
1955
1956
		if ($root !== null) {
1957
			// If we have a root value which means we should rebuild.
1958
			$out = array();
1959
			$out['root'] = $root;
1960
			$out['compiled'] = $this->compileFile($root);
1961
			$out['files'] = $this->allParsedFiles();
1962
			$out['updated'] = time();
1963
			return $out;
1964
		} else {
1965
			// No changes, pass back the structure
1966
			// we were given initially.
1967
			return $in;
1968
		}
1969
1970
	}
1971
1972
	// parse and compile buffer
1973
	// This is deprecated
1974
	public function parse($str = null, $initialVariables = null) {
1975
		if (is_array($str)) {
1976
			$initialVariables = $str;
1977
			$str = null;
1978
		}
1979
1980
		$oldVars = $this->registeredVars;
1981
		if ($initialVariables !== null) {
1982
			$this->setVariables($initialVariables);
1983
		}
1984
1985
		if ($str == null) {
1986
			if (empty($this->_parseFile)) {
1987
				throw new exception("nothing to parse");
1988
			}
1989
1990
			$out = $this->compileFile($this->_parseFile);
1991
		} else {
1992
			$out = $this->compile($str);
1993
		}
1994
1995
		$this->registeredVars = $oldVars;
1996
		return $out;
1997
	}
1998
1999
	protected function makeParser($name) {
2000
		$parser = new lessc_parser($this, $name);
2001
		$parser->writeComments = $this->preserveComments;
0 ignored issues
show
Bug introduced by
The property writeComments does not seem to exist in lessc_parser.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
2002
2003
		return $parser;
2004
	}
2005
2006
	public function setFormatter($name) {
2007
		$this->formatterName = $name;
0 ignored issues
show
Bug introduced by
The property formatterName does not seem to exist. Did you mean formatter?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
2008
	}
2009
2010
	protected function newFormatter() {
2011
		$className = "lessc_formatter_lessjs";
2012
		if (!empty($this->formatterName)) {
0 ignored issues
show
Bug introduced by
The property formatterName does not seem to exist. Did you mean formatter?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
2013
			if (!is_string($this->formatterName))
0 ignored issues
show
Bug introduced by
The property formatterName does not seem to exist. Did you mean formatter?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
2014
				return $this->formatterName;
0 ignored issues
show
Bug introduced by
The property formatterName does not seem to exist. Did you mean formatter?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
2015
			$className = "lessc_formatter_$this->formatterName";
0 ignored issues
show
Bug introduced by
The property formatterName does not seem to exist. Did you mean formatter?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
2016
		}
2017
2018
		return new $className;
2019
	}
2020
2021
	public function setPreserveComments($preserve) {
2022
		$this->preserveComments = $preserve;
2023
	}
2024
2025
	public function registerFunction($name, $func) {
2026
		$this->libFunctions[$name] = $func;
2027
	}
2028
2029
	public function unregisterFunction($name) {
2030
		unset($this->libFunctions[$name]);
2031
	}
2032
2033
	public function setVariables($variables) {
2034
		$this->registeredVars = array_merge($this->registeredVars, $variables);
2035
	}
2036
2037
	public function unsetVariable($name) {
2038
		unset($this->registeredVars[$name]);
2039
	}
2040
2041
	public function setImportDir($dirs) {
2042
		$this->importDir = (array)$dirs;
0 ignored issues
show
Documentation Bug introduced by
It seems like (array) $dirs of type array is incompatible with the declared type string of property $importDir.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
2043
	}
2044
2045
	public function addImportDir($dir) {
2046
		$this->importDir = (array)$this->importDir;
0 ignored issues
show
Documentation Bug introduced by
It seems like (array) $this->importDir of type array is incompatible with the declared type string of property $importDir.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
2047
		$this->importDir[] = $dir;
2048
	}
2049
2050
	public function allParsedFiles() {
2051
		return $this->allParsedFiles;
2052
	}
2053
2054
	public function addParsedFile($file) {
2055
		$this->allParsedFiles[realpath($file)] = filemtime($file);
2056
	}
2057
2058
	/**
2059
	 * Uses the current value of $this->count to show line and line number
2060
	 */
2061
	public function throwError($msg = null) {
2062
		if ($this->sourceLoc >= 0) {
2063
			$this->sourceParser->throwError($msg, $this->sourceLoc);
2064
		}
2065
		throw new exception($msg);
2066
	}
2067
2068
	// compile file $in to file $out if $in is newer than $out
2069
	// returns true when it compiles, false otherwise
2070
	public static function ccompile($in, $out, $less = null) {
2071
		if ($less === null) {
2072
			$less = new self;
2073
		}
2074
		return $less->checkedCompile($in, $out);
2075
	}
2076
2077
	public static function cexecute($in, $force = false, $less = null) {
2078
		if ($less === null) {
2079
			$less = new self;
2080
		}
2081
		return $less->cachedCompile($in, $force);
2082
	}
2083
2084
	static protected $cssColors = array(
2085
		'aliceblue' => '240,248,255',
2086
		'antiquewhite' => '250,235,215',
2087
		'aqua' => '0,255,255',
2088
		'aquamarine' => '127,255,212',
2089
		'azure' => '240,255,255',
2090
		'beige' => '245,245,220',
2091
		'bisque' => '255,228,196',
2092
		'black' => '0,0,0',
2093
		'blanchedalmond' => '255,235,205',
2094
		'blue' => '0,0,255',
2095
		'blueviolet' => '138,43,226',
2096
		'brown' => '165,42,42',
2097
		'burlywood' => '222,184,135',
2098
		'cadetblue' => '95,158,160',
2099
		'chartreuse' => '127,255,0',
2100
		'chocolate' => '210,105,30',
2101
		'coral' => '255,127,80',
2102
		'cornflowerblue' => '100,149,237',
2103
		'cornsilk' => '255,248,220',
2104
		'crimson' => '220,20,60',
2105
		'cyan' => '0,255,255',
2106
		'darkblue' => '0,0,139',
2107
		'darkcyan' => '0,139,139',
2108
		'darkgoldenrod' => '184,134,11',
2109
		'darkgray' => '169,169,169',
2110
		'darkgreen' => '0,100,0',
2111
		'darkgrey' => '169,169,169',
2112
		'darkkhaki' => '189,183,107',
2113
		'darkmagenta' => '139,0,139',
2114
		'darkolivegreen' => '85,107,47',
2115
		'darkorange' => '255,140,0',
2116
		'darkorchid' => '153,50,204',
2117
		'darkred' => '139,0,0',
2118
		'darksalmon' => '233,150,122',
2119
		'darkseagreen' => '143,188,143',
2120
		'darkslateblue' => '72,61,139',
2121
		'darkslategray' => '47,79,79',
2122
		'darkslategrey' => '47,79,79',
2123
		'darkturquoise' => '0,206,209',
2124
		'darkviolet' => '148,0,211',
2125
		'deeppink' => '255,20,147',
2126
		'deepskyblue' => '0,191,255',
2127
		'dimgray' => '105,105,105',
2128
		'dimgrey' => '105,105,105',
2129
		'dodgerblue' => '30,144,255',
2130
		'firebrick' => '178,34,34',
2131
		'floralwhite' => '255,250,240',
2132
		'forestgreen' => '34,139,34',
2133
		'fuchsia' => '255,0,255',
2134
		'gainsboro' => '220,220,220',
2135
		'ghostwhite' => '248,248,255',
2136
		'gold' => '255,215,0',
2137
		'goldenrod' => '218,165,32',
2138
		'gray' => '128,128,128',
2139
		'green' => '0,128,0',
2140
		'greenyellow' => '173,255,47',
2141
		'grey' => '128,128,128',
2142
		'honeydew' => '240,255,240',
2143
		'hotpink' => '255,105,180',
2144
		'indianred' => '205,92,92',
2145
		'indigo' => '75,0,130',
2146
		'ivory' => '255,255,240',
2147
		'khaki' => '240,230,140',
2148
		'lavender' => '230,230,250',
2149
		'lavenderblush' => '255,240,245',
2150
		'lawngreen' => '124,252,0',
2151
		'lemonchiffon' => '255,250,205',
2152
		'lightblue' => '173,216,230',
2153
		'lightcoral' => '240,128,128',
2154
		'lightcyan' => '224,255,255',
2155
		'lightgoldenrodyellow' => '250,250,210',
2156
		'lightgray' => '211,211,211',
2157
		'lightgreen' => '144,238,144',
2158
		'lightgrey' => '211,211,211',
2159
		'lightpink' => '255,182,193',
2160
		'lightsalmon' => '255,160,122',
2161
		'lightseagreen' => '32,178,170',
2162
		'lightskyblue' => '135,206,250',
2163
		'lightslategray' => '119,136,153',
2164
		'lightslategrey' => '119,136,153',
2165
		'lightsteelblue' => '176,196,222',
2166
		'lightyellow' => '255,255,224',
2167
		'lime' => '0,255,0',
2168
		'limegreen' => '50,205,50',
2169
		'linen' => '250,240,230',
2170
		'magenta' => '255,0,255',
2171
		'maroon' => '128,0,0',
2172
		'mediumaquamarine' => '102,205,170',
2173
		'mediumblue' => '0,0,205',
2174
		'mediumorchid' => '186,85,211',
2175
		'mediumpurple' => '147,112,219',
2176
		'mediumseagreen' => '60,179,113',
2177
		'mediumslateblue' => '123,104,238',
2178
		'mediumspringgreen' => '0,250,154',
2179
		'mediumturquoise' => '72,209,204',
2180
		'mediumvioletred' => '199,21,133',
2181
		'midnightblue' => '25,25,112',
2182
		'mintcream' => '245,255,250',
2183
		'mistyrose' => '255,228,225',
2184
		'moccasin' => '255,228,181',
2185
		'navajowhite' => '255,222,173',
2186
		'navy' => '0,0,128',
2187
		'oldlace' => '253,245,230',
2188
		'olive' => '128,128,0',
2189
		'olivedrab' => '107,142,35',
2190
		'orange' => '255,165,0',
2191
		'orangered' => '255,69,0',
2192
		'orchid' => '218,112,214',
2193
		'palegoldenrod' => '238,232,170',
2194
		'palegreen' => '152,251,152',
2195
		'paleturquoise' => '175,238,238',
2196
		'palevioletred' => '219,112,147',
2197
		'papayawhip' => '255,239,213',
2198
		'peachpuff' => '255,218,185',
2199
		'peru' => '205,133,63',
2200
		'pink' => '255,192,203',
2201
		'plum' => '221,160,221',
2202
		'powderblue' => '176,224,230',
2203
		'purple' => '128,0,128',
2204
		'red' => '255,0,0',
2205
		'rosybrown' => '188,143,143',
2206
		'royalblue' => '65,105,225',
2207
		'saddlebrown' => '139,69,19',
2208
		'salmon' => '250,128,114',
2209
		'sandybrown' => '244,164,96',
2210
		'seagreen' => '46,139,87',
2211
		'seashell' => '255,245,238',
2212
		'sienna' => '160,82,45',
2213
		'silver' => '192,192,192',
2214
		'skyblue' => '135,206,235',
2215
		'slateblue' => '106,90,205',
2216
		'slategray' => '112,128,144',
2217
		'slategrey' => '112,128,144',
2218
		'snow' => '255,250,250',
2219
		'springgreen' => '0,255,127',
2220
		'steelblue' => '70,130,180',
2221
		'tan' => '210,180,140',
2222
		'teal' => '0,128,128',
2223
		'thistle' => '216,191,216',
2224
		'tomato' => '255,99,71',
2225
		'transparent' => '0,0,0,0',
2226
		'turquoise' => '64,224,208',
2227
		'violet' => '238,130,238',
2228
		'wheat' => '245,222,179',
2229
		'white' => '255,255,255',
2230
		'whitesmoke' => '245,245,245',
2231
		'yellow' => '255,255,0',
2232
		'yellowgreen' => '154,205,50'
2233
	);
2234
}
2235
2236
// responsible for taking a string of LESS code and converting it into a
2237
// syntax tree
2238
class lessc_parser {
2239
	static protected $nextBlockId = 0; // used to uniquely identify blocks
2240
2241
	static protected $precedence = array(
2242
		'=<' => 0,
2243
		'>=' => 0,
2244
		'=' => 0,
2245
		'<' => 0,
2246
		'>' => 0,
2247
2248
		'+' => 1,
2249
		'-' => 1,
2250
		'*' => 2,
2251
		'/' => 2,
2252
		'%' => 2,
2253
	);
2254
2255
	static protected $whitePattern;
2256
	static protected $commentMulti;
2257
2258
	static protected $commentSingle = "//";
2259
	static protected $commentMultiLeft = "/*";
2260
	static protected $commentMultiRight = "*/";
2261
2262
	// regex string to match any of the operators
2263
	static protected $operatorString;
2264
2265
	// these properties will supress division unless it's inside parenthases
2266
	static protected $supressDivisionProps =
2267
		array('/border-radius$/i', '/^font$/i');
2268
2269
	protected $blockDirectives = array("font-face", "keyframes", "page", "-moz-document", "viewport", "-moz-viewport", "-o-viewport", "-ms-viewport");
2270
	protected $lineDirectives = array("charset");
2271
2272
	/**
2273
	 * if we are in parens we can be more liberal with whitespace around
2274
	 * operators because it must evaluate to a single value and thus is less
2275
	 * ambiguous.
2276
	 *
2277
	 * Consider:
2278
	 *     property1: 10 -5; // is two numbers, 10 and -5
2279
	 *     property2: (10 -5); // should evaluate to 5
2280
	 */
2281
	protected $inParens = false;
2282
2283
	// caches preg escaped literals
2284
	static protected $literalCache = array();
2285
2286
	public function __construct($lessc, $sourceName = null) {
2287
		$this->eatWhiteDefault = true;
0 ignored issues
show
Bug introduced by
The property eatWhiteDefault does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
2288
		// reference to less needed for vPrefix, mPrefix, and parentSelector
2289
		$this->lessc = $lessc;
0 ignored issues
show
Bug introduced by
The property lessc does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
2290
2291
		$this->sourceName = $sourceName; // name used for error messages
0 ignored issues
show
Bug introduced by
The property sourceName does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
2292
2293
		$this->writeComments = false;
0 ignored issues
show
Bug introduced by
The property writeComments does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
2294
2295
		if (!self::$operatorString) {
2296
			self::$operatorString =
2297
				'('.implode('|', array_map(array('lessc', 'preg_quote'),
2298
					array_keys(self::$precedence))).')';
2299
2300
			$commentSingle = lessc::preg_quote(self::$commentSingle);
2301
			$commentMultiLeft = lessc::preg_quote(self::$commentMultiLeft);
2302
			$commentMultiRight = lessc::preg_quote(self::$commentMultiRight);
2303
2304
			self::$commentMulti = $commentMultiLeft.'.*?'.$commentMultiRight;
2305
			self::$whitePattern = '/'.$commentSingle.'[^\n]*\s*|('.self::$commentMulti.')\s*|\s+/Ais';
2306
		}
2307
	}
2308
2309
	public function parse($buffer) {
2310
		$this->count = 0;
0 ignored issues
show
Bug introduced by
The property count does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
2311
		$this->line = 1;
0 ignored issues
show
Bug introduced by
The property line does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
2312
2313
		$this->env = null; // block stack
0 ignored issues
show
Bug introduced by
The property env does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
2314
		$this->buffer = $this->writeComments ? $buffer : $this->removeComments($buffer);
0 ignored issues
show
Bug introduced by
The property buffer does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
2315
		$this->pushSpecialBlock("root");
2316
		$this->eatWhiteDefault = true;
2317
		$this->seenComments = array();
0 ignored issues
show
Bug introduced by
The property seenComments does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
2318
2319
		// trim whitespace on head
2320
		// if (preg_match('/^\s+/', $this->buffer, $m)) {
2321
		// 	$this->line += substr_count($m[0], "\n");
2322
		// 	$this->buffer = ltrim($this->buffer);
2323
		// }
2324
		$this->whitespace();
2325
2326
		// parse the entire file
2327
		while (false !== $this->parseChunk());
2328
2329
		if ($this->count != strlen($this->buffer))
2330
			$this->throwError();
2331
2332
		// TODO report where the block was opened
2333
		if ( !property_exists($this->env, 'parent') || !is_null($this->env->parent) )
2334
			throw new exception('parse error: unclosed block');
2335
2336
		return $this->env;
2337
	}
2338
2339
	/**
2340
	 * Parse a single chunk off the head of the buffer and append it to the
2341
	 * current parse environment.
2342
	 * Returns false when the buffer is empty, or when there is an error.
2343
	 *
2344
	 * This function is called repeatedly until the entire document is
2345
	 * parsed.
2346
	 *
2347
	 * This parser is most similar to a recursive descent parser. Single
2348
	 * functions represent discrete grammatical rules for the language, and
2349
	 * they are able to capture the text that represents those rules.
2350
	 *
2351
	 * Consider the function lessc::keyword(). (all parse functions are
2352
	 * structured the same)
2353
	 *
2354
	 * The function takes a single reference argument. When calling the
2355
	 * function it will attempt to match a keyword on the head of the buffer.
2356
	 * If it is successful, it will place the keyword in the referenced
2357
	 * argument, advance the position in the buffer, and return true. If it
2358
	 * fails then it won't advance the buffer and it will return false.
2359
	 *
2360
	 * All of these parse functions are powered by lessc::match(), which behaves
2361
	 * the same way, but takes a literal regular expression. Sometimes it is
2362
	 * more convenient to use match instead of creating a new function.
2363
	 *
2364
	 * Because of the format of the functions, to parse an entire string of
2365
	 * grammatical rules, you can chain them together using &&.
2366
	 *
2367
	 * But, if some of the rules in the chain succeed before one fails, then
2368
	 * the buffer position will be left at an invalid state. In order to
2369
	 * avoid this, lessc::seek() is used to remember and set buffer positions.
2370
	 *
2371
	 * Before parsing a chain, use $s = $this->seek() to remember the current
2372
	 * position into $s. Then if a chain fails, use $this->seek($s) to
2373
	 * go back where we started.
2374
	 */
2375
	protected function parseChunk() {
2376
		if (empty($this->buffer)) return false;
2377
		$s = $this->seek();
2378
2379
		if ($this->whitespace()) {
2380
			return true;
2381
		}
2382
2383
		// setting a property
2384 View Code Duplication
		if ($this->keyword($key) && $this->assign() &&
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2385
			$this->propertyValue($value, $key) && $this->end())
2386
		{
2387
			$this->append(array('assign', $key, $value), $s);
2388
			return true;
2389
		} else {
2390
			$this->seek($s);
2391
		}
2392
2393
2394
		// look for special css blocks
2395
		if ($this->literal('@', false)) {
2396
			$this->count--;
2397
2398
			// media
2399
			if ($this->literal('@media')) {
2400
				if (($this->mediaQueryList($mediaQueries) || true)
2401
					&& $this->literal('{'))
2402
				{
2403
					$media = $this->pushSpecialBlock("media");
2404
					$media->queries = is_null($mediaQueries) ? array() : $mediaQueries;
2405
					return true;
2406
				} else {
2407
					$this->seek($s);
2408
					return false;
2409
				}
2410
			}
2411
2412
			if ($this->literal("@", false) && $this->keyword($dirName)) {
2413
				if ($this->isDirective($dirName, $this->blockDirectives)) {
2414
					if (($this->openString("{", $dirValue, null, array(";")) || true) &&
2415
						$this->literal("{"))
2416
					{
2417
						$dir = $this->pushSpecialBlock("directive");
2418
						$dir->name = $dirName;
2419
						if (isset($dirValue)) $dir->value = $dirValue;
2420
						return true;
2421
					}
2422
				} elseif ($this->isDirective($dirName, $this->lineDirectives)) {
2423
					if ($this->propertyValue($dirValue) && $this->end()) {
2424
						$this->append(array("directive", $dirName, $dirValue));
2425
						return true;
2426
					}
2427
				}
2428
			}
2429
2430
			$this->seek($s);
2431
		}
2432
2433
		// setting a variable
2434 View Code Duplication
		if ($this->variable($var) && $this->assign() &&
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2435
			$this->propertyValue($value) && $this->end())
2436
		{
2437
			$this->append(array('assign', $var, $value), $s);
2438
			return true;
2439
		} else {
2440
			$this->seek($s);
2441
		}
2442
2443
		if ($this->import($importValue)) {
2444
			$this->append($importValue, $s);
2445
			return true;
2446
		}
2447
2448
		// opening parametric mixin
2449
		if ($this->tag($tag, true) && $this->argumentDef($args, $isVararg) &&
2450
			($this->guards($guards) || true) &&
2451
			$this->literal('{'))
2452
		{
2453
			$block = $this->pushBlock($this->fixTags(array($tag)));
2454
			$block->args = $args;
2455
			$block->isVararg = $isVararg;
2456
			if (!empty($guards)) $block->guards = $guards;
2457
			return true;
2458
		} else {
2459
			$this->seek($s);
2460
		}
2461
2462
		// opening a simple block
2463
		if ($this->tags($tags) && $this->literal('{', false)) {
2464
			$tags = $this->fixTags($tags);
2465
			$this->pushBlock($tags);
2466
			return true;
2467
		} else {
2468
			$this->seek($s);
2469
		}
2470
2471
		// closing a block
2472
		if ($this->literal('}', false)) {
2473
			try {
2474
				$block = $this->pop();
2475
			} catch (exception $e) {
2476
				$this->seek($s);
2477
				$this->throwError($e->getMessage());
2478
			}
2479
2480
			$hidden = false;
2481
			if (is_null($block->type)) {
2482
				$hidden = true;
2483
				if (!isset($block->args)) {
2484
					foreach ($block->tags as $tag) {
2485
						if (!is_string($tag) || $tag{0} != $this->lessc->mPrefix) {
2486
							$hidden = false;
2487
							break;
2488
						}
2489
					}
2490
				}
2491
2492
				foreach ($block->tags as $tag) {
2493
					if (is_string($tag)) {
2494
						$this->env->children[$tag][] = $block;
2495
					}
2496
				}
2497
			}
2498
2499
			if (!$hidden) {
2500
				$this->append(array('block', $block), $s);
2501
			}
2502
2503
			// this is done here so comments aren't bundled into he block that
2504
			// was just closed
2505
			$this->whitespace();
2506
			return true;
2507
		}
2508
2509
		// mixin
2510
		if ($this->mixinTags($tags) &&
2511
			($this->argumentDef($argv, $isVararg) || true) &&
2512
			($this->keyword($suffix) || true) && $this->end())
2513
		{
2514
			$tags = $this->fixTags($tags);
2515
			$this->append(array('mixin', $tags, $argv, $suffix), $s);
2516
			return true;
2517
		} else {
2518
			$this->seek($s);
2519
		}
2520
2521
		// spare ;
2522
		if ($this->literal(';')) return true;
2523
2524
		return false; // got nothing, throw error
2525
	}
2526
2527
	protected function isDirective($dirname, $directives) {
2528
		// TODO: cache pattern in parser
2529
		$pattern = implode("|",
2530
			array_map(array("lessc", "preg_quote"), $directives));
2531
		$pattern = '/^(-[a-z-]+-)?(' . $pattern . ')$/i';
2532
2533
		return preg_match($pattern, $dirname);
2534
	}
2535
2536
	protected function fixTags($tags) {
2537
		// move @ tags out of variable namespace
2538
		foreach ($tags as &$tag) {
2539
			if ($tag{0} == $this->lessc->vPrefix)
2540
				$tag[0] = $this->lessc->mPrefix;
2541
		}
2542
		return $tags;
2543
	}
2544
2545
	// a list of expressions
2546
	protected function expressionList(&$exps) {
2547
		$values = array();
2548
2549
		while ($this->expression($exp)) {
2550
			$values[] = $exp;
2551
		}
2552
2553
		if (count($values) == 0) return false;
2554
2555
		$exps = lessc::compressList($values, ' ');
2556
		return true;
2557
	}
2558
2559
	/**
2560
	 * Attempt to consume an expression.
2561
	 * @link http://en.wikipedia.org/wiki/Operator-precedence_parser#Pseudo-code
2562
	 */
2563
	protected function expression(&$out) {
2564
		if ($this->value($lhs)) {
2565
			$out = $this->expHelper($lhs, 0);
2566
2567
			// look for / shorthand
2568
			if (!empty($this->env->supressedDivision)) {
2569
				unset($this->env->supressedDivision);
2570
				$s = $this->seek();
2571
				if ($this->literal("/") && $this->value($rhs)) {
2572
					$out = array("list", "",
2573
						array($out, array("keyword", "/"), $rhs));
2574
				} else {
2575
					$this->seek($s);
2576
				}
2577
			}
2578
2579
			return true;
2580
		}
2581
		return false;
2582
	}
2583
2584
	/**
2585
	 * recursively parse infix equation with $lhs at precedence $minP
2586
	 */
2587
	protected function expHelper($lhs, $minP) {
2588
		$this->inExp = true;
0 ignored issues
show
Bug introduced by
The property inExp does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
2589
		$ss = $this->seek();
2590
2591
		while (true) {
2592
			$whiteBefore = isset($this->buffer[$this->count - 1]) &&
2593
				ctype_space($this->buffer[$this->count - 1]);
2594
2595
			// If there is whitespace before the operator, then we require
2596
			// whitespace after the operator for it to be an expression
2597
			$needWhite = $whiteBefore && !$this->inParens;
2598
2599
			if ($this->match(self::$operatorString.($needWhite ? '\s' : ''), $m) && self::$precedence[$m[1]] >= $minP) {
2600
				if (!$this->inParens && isset($this->env->currentProperty) && $m[1] == "/" && empty($this->env->supressedDivision)) {
2601
					foreach (self::$supressDivisionProps as $pattern) {
2602
						if (preg_match($pattern, $this->env->currentProperty)) {
2603
							$this->env->supressedDivision = true;
2604
							break 2;
2605
						}
2606
					}
2607
				}
2608
2609
2610
				$whiteAfter = isset($this->buffer[$this->count - 1]) &&
2611
					ctype_space($this->buffer[$this->count - 1]);
2612
2613
				if (!$this->value($rhs)) break;
2614
2615
				// peek for next operator to see what to do with rhs
2616
				if ($this->peek(self::$operatorString, $next) && self::$precedence[$next[1]] > self::$precedence[$m[1]]) {
2617
					$rhs = $this->expHelper($rhs, self::$precedence[$next[1]]);
2618
				}
2619
2620
				$lhs = array('expression', $m[1], $lhs, $rhs, $whiteBefore, $whiteAfter);
2621
				$ss = $this->seek();
2622
2623
				continue;
2624
			}
2625
2626
			break;
2627
		}
2628
2629
		$this->seek($ss);
2630
2631
		return $lhs;
2632
	}
2633
2634
	// consume a list of values for a property
2635
	public function propertyValue(&$value, $keyName = null) {
2636
		$values = array();
2637
2638
		if ($keyName !== null) $this->env->currentProperty = $keyName;
2639
2640
		$s = null;
2641
		while ($this->expressionList($v)) {
2642
			$values[] = $v;
2643
			$s = $this->seek();
2644
			if (!$this->literal(',')) break;
2645
		}
2646
2647
		if ($s) $this->seek($s);
2648
2649
		if ($keyName !== null) unset($this->env->currentProperty);
2650
2651
		if (count($values) == 0) return false;
2652
2653
		$value = lessc::compressList($values, ', ');
2654
		return true;
2655
	}
2656
2657
	protected function parenValue(&$out) {
2658
		$s = $this->seek();
2659
2660
		// speed shortcut
2661 View Code Duplication
		if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "(") {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2662
			return false;
2663
		}
2664
2665
		$inParens = $this->inParens;
2666
		if ($this->literal("(") &&
2667
			($this->inParens = true) && $this->expression($exp) &&
2668
			$this->literal(")"))
2669
		{
2670
			$out = $exp;
2671
			$this->inParens = $inParens;
2672
			return true;
2673
		} else {
2674
			$this->inParens = $inParens;
2675
			$this->seek($s);
2676
		}
2677
2678
		return false;
2679
	}
2680
2681
	// a single value
2682
	protected function value(&$value) {
2683
		$s = $this->seek();
2684
2685
		// speed shortcut
2686
		if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "-") {
2687
			// negation
2688
			if ($this->literal("-", false) &&
2689
				(($this->variable($inner) && $inner = array("variable", $inner)) ||
2690
				$this->unit($inner) ||
2691
				$this->parenValue($inner)))
2692
			{
2693
				$value = array("unary", "-", $inner);
2694
				return true;
2695
			} else {
2696
				$this->seek($s);
2697
			}
2698
		}
2699
2700
		if ($this->parenValue($value)) return true;
2701
		if ($this->unit($value)) return true;
2702
		if ($this->color($value)) return true;
2703
		if ($this->func($value)) return true;
2704
		if ($this->string($value)) return true;
2705
2706
		if ($this->keyword($word)) {
2707
			$value = array('keyword', $word);
2708
			return true;
2709
		}
2710
2711
		// try a variable
2712
		if ($this->variable($var)) {
2713
			$value = array('variable', $var);
2714
			return true;
2715
		}
2716
2717
		// unquote string (should this work on any type?
2718
		if ($this->literal("~") && $this->string($str)) {
2719
			$value = array("escape", $str);
2720
			return true;
2721
		} else {
2722
			$this->seek($s);
2723
		}
2724
2725
		// css hack: \0
2726
		if ($this->literal('\\') && $this->match('([0-9]+)', $m)) {
2727
			$value = array('keyword', '\\'.$m[1]);
2728
			return true;
2729
		} else {
2730
			$this->seek($s);
2731
		}
2732
2733
		return false;
2734
	}
2735
2736
	// an import statement
2737
	protected function import(&$out) {
2738
		if (!$this->literal('@import')) return false;
2739
2740
		// @import "something.css" media;
2741
		// @import url("something.css") media;
2742
		// @import url(something.css) media;
2743
2744
		if ($this->propertyValue($value)) {
2745
			$out = array("import", $value);
2746
			return true;
2747
		}
2748
	}
2749
2750
	protected function mediaQueryList(&$out) {
2751
		if ($this->genericList($list, "mediaQuery", ",", false)) {
2752
			$out = $list[2];
2753
			return true;
2754
		}
2755
		return false;
2756
	}
2757
2758
	protected function mediaQuery(&$out) {
2759
		$s = $this->seek();
2760
2761
		$expressions = null;
2762
		$parts = array();
2763
2764
		if (($this->literal("only") && ($only = true) || $this->literal("not") && ($not = true) || true) && $this->keyword($mediaType)) {
2765
			$prop = array("mediaType");
2766
			if (isset($only)) $prop[] = "only";
2767
			if (isset($not)) $prop[] = "not";
2768
			$prop[] = $mediaType;
2769
			$parts[] = $prop;
2770
		} else {
2771
			$this->seek($s);
2772
		}
2773
2774
2775
		if (!empty($mediaType) && !$this->literal("and")) {
2776
			// ~
2777
		} else {
2778
			$this->genericList($expressions, "mediaExpression", "and", false);
2779
			if (is_array($expressions)) $parts = array_merge($parts, $expressions[2]);
2780
		}
2781
2782
		if (count($parts) == 0) {
2783
			$this->seek($s);
2784
			return false;
2785
		}
2786
2787
		$out = $parts;
2788
		return true;
2789
	}
2790
2791
	protected function mediaExpression(&$out) {
2792
		$s = $this->seek();
2793
		$value = null;
2794
		if ($this->literal("(") &&
2795
			$this->keyword($feature) &&
2796
			($this->literal(":") && $this->expression($value) || true) &&
2797
			$this->literal(")"))
2798
		{
2799
			$out = array("mediaExp", $feature);
2800
			if ($value) $out[] = $value;
2801
			return true;
2802
		} elseif ($this->variable($variable)) {
2803
			$out = array('variable', $variable);
2804
			return true;
2805
		}
2806
2807
		$this->seek($s);
2808
		return false;
2809
	}
2810
2811
	// an unbounded string stopped by $end
2812
	protected function openString($end, &$out, $nestingOpen=null, $rejectStrs = null) {
2813
		$oldWhite = $this->eatWhiteDefault;
2814
		$this->eatWhiteDefault = false;
2815
2816
		$stop = array("'", '"', "@{", $end);
2817
		$stop = array_map(array("lessc", "preg_quote"), $stop);
2818
		// $stop[] = self::$commentMulti;
2819
2820
		if (!is_null($rejectStrs)) {
2821
			$stop = array_merge($stop, $rejectStrs);
2822
		}
2823
2824
		$patt = '(.*?)('.implode("|", $stop).')';
2825
2826
		$nestingLevel = 0;
2827
2828
		$content = array();
2829
		while ($this->match($patt, $m, false)) {
2830
			if (!empty($m[1])) {
2831
				$content[] = $m[1];
2832
				if ($nestingOpen) {
2833
					$nestingLevel += substr_count($m[1], $nestingOpen);
2834
				}
2835
			}
2836
2837
			$tok = $m[2];
2838
2839
			$this->count-= strlen($tok);
2840
			if ($tok == $end) {
2841
				if ($nestingLevel == 0) {
2842
					break;
2843
				} else {
2844
					$nestingLevel--;
2845
				}
2846
			}
2847
2848
			if (($tok == "'" || $tok == '"') && $this->string($str)) {
2849
				$content[] = $str;
2850
				continue;
2851
			}
2852
2853
			if ($tok == "@{" && $this->interpolation($inter)) {
2854
				$content[] = $inter;
2855
				continue;
2856
			}
2857
2858
			if (!empty($rejectStrs) && in_array($tok, $rejectStrs)) {
2859
				break;
2860
			}
2861
2862
			$content[] = $tok;
2863
			$this->count+= strlen($tok);
2864
		}
2865
2866
		$this->eatWhiteDefault = $oldWhite;
2867
2868
		if (count($content) == 0) return false;
2869
2870
		// trim the end
2871
		if (is_string(end($content))) {
2872
			$content[count($content) - 1] = rtrim(end($content));
2873
		}
2874
2875
		$out = array("string", "", $content);
2876
		return true;
2877
	}
2878
2879
	protected function string(&$out) {
2880
		$s = $this->seek();
2881
		if ($this->literal('"', false)) {
2882
			$delim = '"';
2883
		} elseif ($this->literal("'", false)) {
2884
			$delim = "'";
2885
		} else {
2886
			return false;
2887
		}
2888
2889
		$content = array();
2890
2891
		// look for either ending delim , escape, or string interpolation
2892
		$patt = '([^\n]*?)(@\{|\\\\|' .
2893
			lessc::preg_quote($delim).')';
2894
2895
		$oldWhite = $this->eatWhiteDefault;
2896
		$this->eatWhiteDefault = false;
2897
2898
		while ($this->match($patt, $m, false)) {
2899
			$content[] = $m[1];
2900
			if ($m[2] == "@{") {
2901
				$this->count -= strlen($m[2]);
2902
				if ($this->interpolation($inter, false)) {
0 ignored issues
show
Unused Code introduced by
The call to lessc_parser::interpolation() has too many arguments starting with false.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
2903
					$content[] = $inter;
2904
				} else {
2905
					$this->count += strlen($m[2]);
2906
					$content[] = "@{"; // ignore it
2907
				}
2908
			} elseif ($m[2] == '\\') {
2909
				$content[] = $m[2];
2910
				if ($this->literal($delim, false)) {
2911
					$content[] = $delim;
2912
				}
2913
			} else {
2914
				$this->count -= strlen($delim);
2915
				break; // delim
2916
			}
2917
		}
2918
2919
		$this->eatWhiteDefault = $oldWhite;
2920
2921
		if ($this->literal($delim)) {
2922
			$out = array("string", $delim, $content);
2923
			return true;
2924
		}
2925
2926
		$this->seek($s);
2927
		return false;
2928
	}
2929
2930
	protected function interpolation(&$out) {
2931
		$oldWhite = $this->eatWhiteDefault;
2932
		$this->eatWhiteDefault = true;
2933
2934
		$s = $this->seek();
2935
		if ($this->literal("@{") &&
2936
			$this->openString("}", $interp, null, array("'", '"', ";")) &&
2937
			$this->literal("}", false))
2938
		{
2939
			$out = array("interpolate", $interp);
2940
			$this->eatWhiteDefault = $oldWhite;
2941
			if ($this->eatWhiteDefault) $this->whitespace();
2942
			return true;
2943
		}
2944
2945
		$this->eatWhiteDefault = $oldWhite;
2946
		$this->seek($s);
2947
		return false;
2948
	}
2949
2950
	protected function unit(&$unit) {
2951
		// speed shortcut
2952
		if (isset($this->buffer[$this->count])) {
2953
			$char = $this->buffer[$this->count];
2954
			if (!ctype_digit($char) && $char != ".") return false;
2955
		}
2956
2957
		if ($this->match('([0-9]+(?:\.[0-9]*)?|\.[0-9]+)([%a-zA-Z]+)?', $m)) {
2958
			$unit = array("number", $m[1], empty($m[2]) ? "" : $m[2]);
2959
			return true;
2960
		}
2961
		return false;
2962
	}
2963
2964
	// a # color
2965
	protected function color(&$out) {
2966
		if ($this->match('(#(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3}))', $m)) {
2967
			if (strlen($m[1]) > 7) {
2968
				$out = array("string", "", array($m[1]));
2969
			} else {
2970
				$out = array("raw_color", $m[1]);
2971
			}
2972
			return true;
2973
		}
2974
2975
		return false;
2976
	}
2977
2978
	// consume an argument definition list surrounded by ()
2979
	// each argument is a variable name with optional value
2980
	// or at the end a ... or a variable named followed by ...
2981
	// arguments are separated by , unless a ; is in the list, then ; is the
2982
	// delimiter.
2983
	protected function argumentDef(&$args, &$isVararg) {
2984
		$s = $this->seek();
2985
		if (!$this->literal('(')) return false;
2986
2987
		$values = array();
2988
		$delim = ",";
2989
		$method = "expressionList";
2990
2991
		$isVararg = false;
2992
		while (true) {
2993
			if ($this->literal("...")) {
2994
				$isVararg = true;
2995
				break;
2996
			}
2997
2998
			if ($this->$method($value)) {
0 ignored issues
show
Bug introduced by
The variable $value does not exist. Did you mean $values?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
2999
				if ($value[0] == "variable") {
0 ignored issues
show
Bug introduced by
The variable $value does not exist. Did you mean $values?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
3000
					$arg = array("arg", $value[1]);
0 ignored issues
show
Bug introduced by
The variable $value does not exist. Did you mean $values?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
3001
					$ss = $this->seek();
3002
3003
					if ($this->assign() && $this->$method($rhs)) {
3004
						$arg[] = $rhs;
0 ignored issues
show
Bug introduced by
The variable $rhs does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
3005
					} else {
3006
						$this->seek($ss);
3007
						if ($this->literal("...")) {
3008
							$arg[0] = "rest";
3009
							$isVararg = true;
3010
						}
3011
					}
3012
3013
					$values[] = $arg;
3014
					if ($isVararg) break;
3015
					continue;
3016
				} else {
3017
					$values[] = array("lit", $value);
0 ignored issues
show
Bug introduced by
The variable $value does not exist. Did you mean $values?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
3018
				}
3019
			}
3020
3021
3022
			if (!$this->literal($delim)) {
3023
				if ($delim == "," && $this->literal(";")) {
3024
					// found new delim, convert existing args
3025
					$delim = ";";
3026
					$method = "propertyValue";
3027
3028
					// transform arg list
3029
					if (isset($values[1])) { // 2 items
3030
						$newList = array();
3031
						foreach ($values as $i => $arg) {
3032
							switch($arg[0]) {
3033
							case "arg":
3034
								if ($i) {
3035
									$this->throwError("Cannot mix ; and , as delimiter types");
3036
								}
3037
								$newList[] = $arg[2];
3038
								break;
3039
							case "lit":
3040
								$newList[] = $arg[1];
3041
								break;
3042
							case "rest":
3043
								$this->throwError("Unexpected rest before semicolon");
3044
							}
3045
						}
3046
3047
						$newList = array("list", ", ", $newList);
3048
3049
						switch ($values[0][0]) {
3050
						case "arg":
3051
							$newArg = array("arg", $values[0][1], $newList);
3052
							break;
3053
						case "lit":
3054
							$newArg = array("lit", $newList);
3055
							break;
3056
						}
3057
3058
					} elseif ($values) { // 1 item
0 ignored issues
show
Bug Best Practice introduced by
The expression $values of type null[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
3059
						$newArg = $values[0];
3060
					}
3061
3062
					if ($newArg) {
3063
						$values = array($newArg);
0 ignored issues
show
Bug introduced by
The variable $newArg does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
3064
					}
3065
				} else {
3066
					break;
3067
				}
3068
			}
3069
		}
3070
3071
		if (!$this->literal(')')) {
3072
			$this->seek($s);
3073
			return false;
3074
		}
3075
3076
		$args = $values;
3077
3078
		return true;
3079
	}
3080
3081
	// consume a list of tags
3082
	// this accepts a hanging delimiter
3083 View Code Duplication
	protected function tags(&$tags, $simple = false, $delim = ',') {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3084
		$tags = array();
3085
		while ($this->tag($tt, $simple)) {
3086
			$tags[] = $tt;
3087
			if (!$this->literal($delim)) break;
3088
		}
3089
		if (count($tags) == 0) return false;
3090
3091
		return true;
3092
	}
3093
3094
	// list of tags of specifying mixin path
3095
	// optionally separated by > (lazy, accepts extra >)
3096 View Code Duplication
	protected function mixinTags(&$tags) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3097
		$tags = array();
3098
		while ($this->tag($tt, true)) {
3099
			$tags[] = $tt;
3100
			$this->literal(">");
3101
		}
3102
3103
		if (count($tags) == 0) return false;
3104
3105
		return true;
3106
	}
3107
3108
	// a bracketed value (contained within in a tag definition)
3109
	protected function tagBracket(&$parts, &$hasExpression) {
3110
		// speed shortcut
3111 View Code Duplication
		if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "[") {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3112
			return false;
3113
		}
3114
3115
		$s = $this->seek();
3116
3117
		$hasInterpolation = false;
3118
3119
		if ($this->literal("[", false)) {
3120
			$attrParts = array("[");
3121
			// keyword, string, operator
3122
			while (true) {
3123
				if ($this->literal("]", false)) {
3124
					$this->count--;
3125
					break; // get out early
3126
				}
3127
3128
				if ($this->match('\s+', $m)) {
3129
					$attrParts[] = " ";
3130
					continue;
3131
				}
3132
				if ($this->string($str)) {
3133
					// escape parent selector, (yuck)
3134
					foreach ($str[2] as &$chunk) {
3135
						$chunk = str_replace($this->lessc->parentSelector, "$&$", $chunk);
3136
					}
3137
3138
					$attrParts[] = $str;
3139
					$hasInterpolation = true;
3140
					continue;
3141
				}
3142
3143
				if ($this->keyword($word)) {
3144
					$attrParts[] = $word;
3145
					continue;
3146
				}
3147
3148
				if ($this->interpolation($inter, false)) {
0 ignored issues
show
Unused Code introduced by
The call to lessc_parser::interpolation() has too many arguments starting with false.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
3149
					$attrParts[] = $inter;
3150
					$hasInterpolation = true;
3151
					continue;
3152
				}
3153
3154
				// operator, handles attr namespace too
3155
				if ($this->match('[|-~\$\*\^=]+', $m)) {
3156
					$attrParts[] = $m[0];
3157
					continue;
3158
				}
3159
3160
				break;
3161
			}
3162
3163
			if ($this->literal("]", false)) {
3164
				$attrParts[] = "]";
3165
				foreach ($attrParts as $part) {
3166
					$parts[] = $part;
3167
				}
3168
				$hasExpression = $hasExpression || $hasInterpolation;
3169
				return true;
3170
			}
3171
			$this->seek($s);
3172
		}
3173
3174
		$this->seek($s);
3175
		return false;
3176
	}
3177
3178
	// a space separated list of selectors
3179
	protected function tag(&$tag, $simple = false) {
3180
		if ($simple)
3181
			$chars = '^@,:;{}\][>\(\) "\'';
3182
		else
3183
			$chars = '^@,;{}["\'';
3184
3185
		$s = $this->seek();
3186
3187
		$hasExpression = false;
3188
		$parts = array();
3189
		while ($this->tagBracket($parts, $hasExpression));
3190
3191
		$oldWhite = $this->eatWhiteDefault;
3192
		$this->eatWhiteDefault = false;
3193
3194
		while (true) {
3195
			if ($this->match('(['.$chars.'0-9]['.$chars.']*)', $m)) {
3196
				$parts[] = $m[1];
3197
				if ($simple) break;
3198
3199
				while ($this->tagBracket($parts, $hasExpression));
3200
				continue;
3201
			}
3202
3203
			if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "@") {
3204
				if ($this->interpolation($interp)) {
3205
					$hasExpression = true;
3206
					$interp[2] = true; // don't unescape
3207
					$parts[] = $interp;
3208
					continue;
3209
				}
3210
3211
				if ($this->literal("@")) {
3212
					$parts[] = "@";
3213
					continue;
3214
				}
3215
			}
3216
3217
			if ($this->unit($unit)) { // for keyframes
3218
				$parts[] = $unit[1];
3219
				$parts[] = $unit[2];
3220
				continue;
3221
			}
3222
3223
			break;
3224
		}
3225
3226
		$this->eatWhiteDefault = $oldWhite;
3227
		if (!$parts) {
3228
			$this->seek($s);
3229
			return false;
3230
		}
3231
3232
		if ($hasExpression) {
3233
			$tag = array("exp", array("string", "", $parts));
3234
		} else {
3235
			$tag = trim(implode($parts));
3236
		}
3237
3238
		$this->whitespace();
3239
		return true;
3240
	}
3241
3242
	// a css function
3243
	protected function func(&$func) {
3244
		$s = $this->seek();
3245
3246
		if ($this->match('(%|[\w\-_][\w\-_:\.]+|[\w_])', $m) && $this->literal('(')) {
3247
			$fname = $m[1];
3248
3249
			$sPreArgs = $this->seek();
3250
3251
			$args = array();
3252
			while (true) {
3253
				$ss = $this->seek();
3254
				// this ugly nonsense is for ie filter properties
3255
				if ($this->keyword($name) && $this->literal('=') && $this->expressionList($value)) {
3256
					$args[] = array("string", "", array($name, "=", $value));
3257
				} else {
3258
					$this->seek($ss);
3259
					if ($this->expressionList($value)) {
3260
						$args[] = $value;
3261
					}
3262
				}
3263
3264
				if (!$this->literal(',')) break;
3265
			}
3266
			$args = array('list', ',', $args);
3267
3268
			if ($this->literal(')')) {
3269
				$func = array('function', $fname, $args);
3270
				return true;
3271
			} elseif ($fname == 'url') {
3272
				// couldn't parse and in url? treat as string
3273
				$this->seek($sPreArgs);
3274
				if ($this->openString(")", $string) && $this->literal(")")) {
3275
					$func = array('function', $fname, $string);
3276
					return true;
3277
				}
3278
			}
3279
		}
3280
3281
		$this->seek($s);
3282
		return false;
3283
	}
3284
3285
	// consume a less variable
3286
	protected function variable(&$name) {
3287
		$s = $this->seek();
3288
		if ($this->literal($this->lessc->vPrefix, false) &&
3289
			($this->variable($sub) || $this->keyword($name)))
3290
		{
3291
			if (!empty($sub)) {
3292
				$name = array('variable', $sub);
3293
			} else {
3294
				$name = $this->lessc->vPrefix.$name;
3295
			}
3296
			return true;
3297
		}
3298
3299
		$name = null;
3300
		$this->seek($s);
3301
		return false;
3302
	}
3303
3304
	/**
3305
	 * Consume an assignment operator
3306
	 * Can optionally take a name that will be set to the current property name
3307
	 */
3308
	protected function assign($name = null) {
3309
		if ($name) $this->currentProperty = $name;
0 ignored issues
show
Bug introduced by
The property currentProperty does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
3310
		return $this->literal(':') || $this->literal('=');
3311
	}
3312
3313
	// consume a keyword
3314
	protected function keyword(&$word) {
3315
		if ($this->match('([\w_\-\*!"][\w\-_"]*)', $m)) {
3316
			$word = $m[1];
3317
			return true;
3318
		}
3319
		return false;
3320
	}
3321
3322
	// consume an end of statement delimiter
3323
	protected function end() {
3324
		if ($this->literal(';', false)) {
3325
			return true;
3326
		} elseif ($this->count == strlen($this->buffer) || $this->buffer[$this->count] == '}') {
3327
			// if there is end of file or a closing block next then we don't need a ;
3328
			return true;
3329
		}
3330
		return false;
3331
	}
3332
3333
	protected function guards(&$guards) {
3334
		$s = $this->seek();
3335
3336
		if (!$this->literal("when")) {
3337
			$this->seek($s);
3338
			return false;
3339
		}
3340
3341
		$guards = array();
3342
3343
		while ($this->guardGroup($g)) {
3344
			$guards[] = $g;
3345
			if (!$this->literal(",")) break;
3346
		}
3347
3348
		if (count($guards) == 0) {
3349
			$guards = null;
3350
			$this->seek($s);
3351
			return false;
3352
		}
3353
3354
		return true;
3355
	}
3356
3357
	// a bunch of guards that are and'd together
3358
	// TODO rename to guardGroup
3359
	protected function guardGroup(&$guardGroup) {
3360
		$s = $this->seek();
3361
		$guardGroup = array();
3362
		while ($this->guard($guard)) {
3363
			$guardGroup[] = $guard;
3364
			if (!$this->literal("and")) break;
3365
		}
3366
3367
		if (count($guardGroup) == 0) {
3368
			$guardGroup = null;
3369
			$this->seek($s);
3370
			return false;
3371
		}
3372
3373
		return true;
3374
	}
3375
3376
	protected function guard(&$guard) {
3377
		$s = $this->seek();
3378
		$negate = $this->literal("not");
3379
3380
		if ($this->literal("(") && $this->expression($exp) && $this->literal(")")) {
3381
			$guard = $exp;
3382
			if ($negate) $guard = array("negate", $guard);
3383
			return true;
3384
		}
3385
3386
		$this->seek($s);
3387
		return false;
3388
	}
3389
3390
	/* raw parsing functions */
3391
3392
	protected function literal($what, $eatWhitespace = null) {
3393
		if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault;
3394
3395
		// shortcut on single letter
3396
		if (!isset($what[1]) && isset($this->buffer[$this->count])) {
3397
			if ($this->buffer[$this->count] == $what) {
3398
				if (!$eatWhitespace) {
3399
					$this->count++;
3400
					return true;
3401
				}
3402
				// goes below...
3403
			} else {
3404
				return false;
3405
			}
3406
		}
3407
3408
		if (!isset(self::$literalCache[$what])) {
3409
			self::$literalCache[$what] = lessc::preg_quote($what);
3410
		}
3411
3412
		return $this->match(self::$literalCache[$what], $m, $eatWhitespace);
3413
	}
3414
3415
	protected function genericList(&$out, $parseItem, $delim="", $flatten=true) {
3416
		$s = $this->seek();
3417
		$items = array();
3418
		while ($this->$parseItem($value)) {
0 ignored issues
show
Bug introduced by
The variable $value does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
3419
			$items[] = $value;
3420
			if ($delim) {
3421
				if (!$this->literal($delim)) break;
3422
			}
3423
		}
3424
3425
		if (count($items) == 0) {
3426
			$this->seek($s);
3427
			return false;
3428
		}
3429
3430
		if ($flatten && count($items) == 1) {
3431
			$out = $items[0];
3432
		} else {
3433
			$out = array("list", $delim, $items);
3434
		}
3435
3436
		return true;
3437
	}
3438
3439
3440
	// advance counter to next occurrence of $what
3441
	// $until - don't include $what in advance
3442
	// $allowNewline, if string, will be used as valid char set
3443
	protected function to($what, &$out, $until = false, $allowNewline = false) {
3444
		if (is_string($allowNewline)) {
3445
			$validChars = $allowNewline;
3446
		} else {
3447
			$validChars = $allowNewline ? "." : "[^\n]";
3448
		}
3449
		if (!$this->match('('.$validChars.'*?)'.lessc::preg_quote($what), $m, !$until)) return false;
3450
		if ($until) $this->count -= strlen($what); // give back $what
3451
		$out = $m[1];
3452
		return true;
3453
	}
3454
3455
	// try to match something on head of buffer
3456
	protected function match($regex, &$out, $eatWhitespace = null) {
3457
		if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault;
3458
3459
		$r = '/'.$regex.($eatWhitespace && !$this->writeComments ? '\s*' : '').'/Ais';
3460
		if (preg_match($r, $this->buffer, $out, null, $this->count)) {
3461
			$this->count += strlen($out[0]);
3462
			if ($eatWhitespace && $this->writeComments) $this->whitespace();
3463
			return true;
3464
		}
3465
		return false;
3466
	}
3467
3468
	// match some whitespace
3469
	protected function whitespace() {
3470
		if ($this->writeComments) {
3471
			$gotWhite = false;
3472
			while (preg_match(self::$whitePattern, $this->buffer, $m, null, $this->count)) {
3473
				if (isset($m[1]) && empty($this->seenComments[$this->count])) {
3474
					$this->append(array("comment", $m[1]));
3475
					$this->seenComments[$this->count] = true;
3476
				}
3477
				$this->count += strlen($m[0]);
3478
				$gotWhite = true;
3479
			}
3480
			return $gotWhite;
3481
		} else {
3482
			$this->match("", $m);
3483
			return strlen($m[0]) > 0;
3484
		}
3485
	}
3486
3487
	// match something without consuming it
3488
	protected function peek($regex, &$out = null, $from=null) {
3489
		if (is_null($from)) $from = $this->count;
3490
		$r = '/'.$regex.'/Ais';
3491
		$result = preg_match($r, $this->buffer, $out, null, $from);
3492
3493
		return $result;
3494
	}
3495
3496
	// seek to a spot in the buffer or return where we are on no argument
3497
	protected function seek($where = null) {
3498
		if ($where === null) return $this->count;
3499
		else $this->count = $where;
3500
		return true;
3501
	}
3502
3503
	/* misc functions */
3504
3505
	public function throwError($msg = "parse error", $count = null) {
3506
		$count = is_null($count) ? $this->count : $count;
3507
3508
		$line = $this->line +
3509
			substr_count(substr($this->buffer, 0, $count), "\n");
3510
3511
		if (!empty($this->sourceName)) {
3512
			$loc = "$this->sourceName on line $line";
3513
		} else {
3514
			$loc = "line: $line";
3515
		}
3516
3517
		// TODO this depends on $this->count
3518
		if ($this->peek("(.*?)(\n|$)", $m, $count)) {
3519
			throw new exception("$msg: failed at `$m[1]` $loc");
3520
		} else {
3521
			throw new exception("$msg: $loc");
3522
		}
3523
	}
3524
3525
	protected function pushBlock($selectors=null, $type=null) {
3526
		$b = new stdclass;
3527
		$b->parent = $this->env;
3528
3529
		$b->type = $type;
3530
		$b->id = self::$nextBlockId++;
3531
3532
		$b->isVararg = false; // TODO: kill me from here
3533
		$b->tags = $selectors;
3534
3535
		$b->props = array();
3536
		$b->children = array();
3537
3538
		$this->env = $b;
3539
		return $b;
3540
	}
3541
3542
	// push a block that doesn't multiply tags
3543
	protected function pushSpecialBlock($type) {
3544
		return $this->pushBlock(null, $type);
3545
	}
3546
3547
	// append a property to the current block
3548
	protected function append($prop, $pos = null) {
3549
		if ($pos !== null) $prop[-1] = $pos;
3550
		$this->env->props[] = $prop;
3551
	}
3552
3553
	// pop something off the stack
3554
	protected function pop() {
3555
		$old = $this->env;
3556
		$this->env = $this->env->parent;
3557
		return $old;
3558
	}
3559
3560
	// remove comments from $text
3561
	// todo: make it work for all functions, not just url
3562
	protected function removeComments($text) {
3563
		$look = array(
3564
			'url(', '//', '/*', '"', "'"
3565
		);
3566
3567
		$out = '';
3568
		$min = null;
3569
		while (true) {
3570
			// find the next item
3571
			foreach ($look as $token) {
3572
				$pos = strpos($text, $token);
3573
				if ($pos !== false) {
3574
					if (!isset($min) || $pos < $min[1]) $min = array($token, $pos);
3575
				}
3576
			}
3577
3578
			if (is_null($min)) break;
3579
3580
			$count = $min[1];
3581
			$skip = 0;
3582
			$newlines = 0;
3583
			switch ($min[0]) {
3584 View Code Duplication
			case 'url(':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3585
				if (preg_match('/url\(.*?\)/', $text, $m, 0, $count))
3586
					$count += strlen($m[0]) - strlen($min[0]);
3587
				break;
3588
			case '"':
3589
			case "'":
3590
				if (preg_match('/'.$min[0].'.*?(?<!\\\\)'.$min[0].'/', $text, $m, 0, $count))
3591
					$count += strlen($m[0]) - 1;
3592
				break;
3593
			case '//':
3594
				$skip = strpos($text, "\n", $count);
3595
				if ($skip === false) $skip = strlen($text) - $count;
3596
				else $skip -= $count;
3597
				break;
3598 View Code Duplication
			case '/*':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3599
				if (preg_match('/\/\*.*?\*\//s', $text, $m, 0, $count)) {
3600
					$skip = strlen($m[0]);
3601
					$newlines = substr_count($m[0], "\n");
3602
				}
3603
				break;
3604
			}
3605
3606
			if ($skip == 0) $count += strlen($min[0]);
3607
3608
			$out .= substr($text, 0, $count).str_repeat("\n", $newlines);
3609
			$text = substr($text, $count + $skip);
3610
3611
			$min = null;
3612
		}
3613
3614
		return $out.$text;
3615
	}
3616
3617
}
3618
3619
class lessc_formatter_classic {
3620
	public $indentChar = "  ";
3621
3622
	public $break = "\n";
3623
	public $open = " {";
3624
	public $close = "}";
3625
	public $selectorSeparator = ", ";
3626
	public $assignSeparator = ":";
3627
3628
	public $openSingle = " { ";
3629
	public $closeSingle = " }";
3630
3631
	public $disableSingle = false;
3632
	public $breakSelectors = false;
3633
3634
	public $compressColors = false;
3635
3636
	public function __construct() {
3637
		$this->indentLevel = 0;
0 ignored issues
show
Bug introduced by
The property indentLevel does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
3638
	}
3639
3640
	public function indentStr($n = 0) {
3641
		return str_repeat($this->indentChar, max($this->indentLevel + $n, 0));
3642
	}
3643
3644
	public function property($name, $value) {
3645
		return $name . $this->assignSeparator . $value . ";";
3646
	}
3647
3648
	protected function isEmpty($block) {
3649
		if (empty($block->lines)) {
3650
			foreach ($block->children as $child) {
3651
				if (!$this->isEmpty($child)) return false;
3652
			}
3653
3654
			return true;
3655
		}
3656
		return false;
3657
	}
3658
3659
	public function block($block) {
3660
		if ($this->isEmpty($block)) return;
3661
3662
		$inner = $pre = $this->indentStr();
3663
3664
		$isSingle = !$this->disableSingle &&
3665
			is_null($block->type) && count($block->lines) == 1;
3666
3667
		if (!empty($block->selectors)) {
3668
			$this->indentLevel++;
3669
3670
			if ($this->breakSelectors) {
3671
				$selectorSeparator = $this->selectorSeparator . $this->break . $pre;
3672
			} else {
3673
				$selectorSeparator = $this->selectorSeparator;
3674
			}
3675
3676
			echo $pre .
3677
				implode($selectorSeparator, $block->selectors);
3678
			if ($isSingle) {
3679
				echo $this->openSingle;
3680
				$inner = "";
3681
			} else {
3682
				echo $this->open . $this->break;
3683
				$inner = $this->indentStr();
3684
			}
3685
3686
		}
3687
3688
		if (!empty($block->lines)) {
3689
			$glue = $this->break.$inner;
3690
			echo $inner . implode($glue, $block->lines);
3691
			if (!$isSingle && !empty($block->children)) {
3692
				echo $this->break;
3693
			}
3694
		}
3695
3696
		foreach ($block->children as $child) {
3697
			$this->block($child);
3698
		}
3699
3700
		if (!empty($block->selectors)) {
3701
			if (!$isSingle && empty($block->children)) echo $this->break;
3702
3703
			if ($isSingle) {
3704
				echo $this->closeSingle . $this->break;
3705
			} else {
3706
				echo $pre . $this->close . $this->break;
3707
			}
3708
3709
			$this->indentLevel--;
3710
		}
3711
	}
3712
}
3713
3714
class lessc_formatter_compressed extends lessc_formatter_classic {
3715
	public $disableSingle = true;
3716
	public $open = "{";
3717
	public $selectorSeparator = ",";
3718
	public $assignSeparator = ":";
3719
	public $break = "";
3720
	public $compressColors = true;
3721
3722
	public function indentStr($n = 0) {
3723
		return "";
3724
	}
3725
}
3726
3727
class lessc_formatter_lessjs extends lessc_formatter_classic {
3728
	public $disableSingle = true;
3729
	public $breakSelectors = true;
3730
	public $assignSeparator = ": ";
3731
	public $selectorSeparator = ",";
3732
}
3733