Completed
Push — branch-4.0 ( fc0d6f...bf547c )
by
unknown
08:22
created

lessc::multiplySelectors()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 29
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 15
nc 6
nop 1
dl 0
loc 29
rs 8.439
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * lessphp v0.5.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.5.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) {
0 ignored issues
show
Coding Style introduced by
As per PSR2, the static declaration should come after the visibility declaration.
Loading history...
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) {
0 ignored issues
show
Coding Style introduced by
As per PSR2, the static declaration should come after the visibility declaration.
Loading history...
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
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
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
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
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 View Code Duplication
		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 View Code Duplication
	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 View Code Duplication
	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($imports, $vars), $other);
350
		} else {
351
			return array_merge($imports, $vars, $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 View Code Duplication
	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 View Code Duplication
		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";
0 ignored issues
show
Coding Style introduced by
CASE statements must be defined using a colon

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
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
	public 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 View Code Duplication
			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 View Code Duplication
			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
	/**
987
	 * Given an url, decide whether to output a regular link or the base64-encoded contents of the file
988
	 *
989
	 * @param  array  $value either an argument list (two strings) or a single string
990
	 * @return string        formatted url(), either as a link or base64-encoded
991
	 */
992
	protected function lib_data_uri($value) {
993
		$mime = ($value[0] === 'list') ? $value[2][0][2] : null;
994
		$url = ($value[0] === 'list') ? $value[2][1][2][0] : $value[2][0];
995
996
		$fullpath = $this->findImport($url);
997
998
		if($fullpath && ($fsize = filesize($fullpath)) !== false) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fullpath of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
999
			// IE8 can't handle data uris larger than 32KB
1000
			if($fsize/1024 < 32) {
1001 View Code Duplication
				if(is_null($mime)) {
1002
					if(class_exists('finfo')) { // php 5.3+
1003
						$finfo = new finfo(FILEINFO_MIME);
1004
						$mime = explode('; ', $finfo->file($fullpath));
1005
						$mime = $mime[0];
1006
					} elseif(function_exists('mime_content_type')) { // PHP 5.2
1007
						$mime = mime_content_type($fullpath);
1008
					}
1009
				}
1010
1011
				if(!is_null($mime)) // fallback if the mime type is still unknown
1012
					$url = sprintf('data:%s;base64,%s', $mime, base64_encode(file_get_contents($fullpath)));
1013
			}
1014
		}
1015
1016
		return 'url("'.$url.'")';
1017
	}
1018
1019
	// utility func to unquote a string
1020
	protected function lib_e($arg) {
1021
		switch ($arg[0]) {
1022
			case "list":
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
1023
				$items = $arg[2];
1024
				if (isset($items[0])) {
1025
					return $this->lib_e($items[0]);
1026
				}
1027
				$this->throwError("unrecognised input");
1028
			case "string":
1029
				$arg[1] = "";
1030
				return $arg;
1031
			case "keyword":
1032
				return $arg;
1033
			default:
1034
				return array("keyword", $this->compileValue($arg));
1035
		}
1036
	}
1037
1038
	protected function lib__sprintf($args) {
1039
		if ($args[0] != "list") return $args;
1040
		$values = $args[2];
1041
		$string = array_shift($values);
1042
		$template = $this->compileValue($this->lib_e($string));
1043
1044
		$i = 0;
1045
		if (preg_match_all('/%[dsa]/', $template, $m)) {
1046
			foreach ($m[0] as $match) {
1047
				$val = isset($values[$i]) ?
1048
					$this->reduce($values[$i]) : array('keyword', '');
1049
1050
				// lessjs compat, renders fully expanded color, not raw color
1051
				if ($color = $this->coerceColor($val)) {
1052
					$val = $color;
1053
				}
1054
1055
				$i++;
1056
				$rep = $this->compileValue($this->lib_e($val));
1057
				$template = preg_replace('/'.self::preg_quote($match).'/',
1058
					$rep, $template, 1);
1059
			}
1060
		}
1061
1062
		$d = $string[0] == "string" ? $string[1] : '"';
1063
		return array("string", $d, array($template));
1064
	}
1065
1066
	protected function lib_floor($arg) {
1067
		$value = $this->assertNumber($arg);
1068
		return array("number", floor($value), $arg[2]);
1069
	}
1070
1071
	protected function lib_ceil($arg) {
1072
		$value = $this->assertNumber($arg);
1073
		return array("number", ceil($value), $arg[2]);
1074
	}
1075
1076
	protected function lib_round($arg) {
1077
		if($arg[0] != "list") {
1078
			$value = $this->assertNumber($arg);
1079
			return array("number", round($value), $arg[2]);
1080
		} else {
1081
			$value = $this->assertNumber($arg[2][0]);
1082
			$precision = $this->assertNumber($arg[2][1]);
1083
			return array("number", round($value, $precision), $arg[2][0][2]);
1084
		}
1085
	}
1086
1087
	protected function lib_unit($arg) {
1088
		if ($arg[0] == "list") {
1089
			list($number, $newUnit) = $arg[2];
1090
			return array("number", $this->assertNumber($number),
1091
				$this->compileValue($this->lib_e($newUnit)));
1092
		} else {
1093
			return array("number", $this->assertNumber($arg), "");
1094
		}
1095
	}
1096
1097
	/**
1098
	 * Helper function to get arguments for color manipulation functions.
1099
	 * takes a list that contains a color like thing and a percentage
1100
	 */
1101
	public function colorArgs($args) {
1102
		if ($args[0] != 'list' || count($args[2]) < 2) {
1103
			return array(array('color', 0, 0, 0), 0);
1104
		}
1105
		list($color, $delta) = $args[2];
1106
		$color = $this->assertColor($color);
1107
		$delta = floatval($delta[1]);
1108
1109
		return array($color, $delta);
1110
	}
1111
1112 View Code Duplication
	protected function lib_darken($args) {
1113
		list($color, $delta) = $this->colorArgs($args);
1114
1115
		$hsl = $this->toHSL($color);
1116
		$hsl[3] = $this->clamp($hsl[3] - $delta, 100);
1117
		return $this->toRGB($hsl);
1118
	}
1119
1120 View Code Duplication
	protected function lib_lighten($args) {
1121
		list($color, $delta) = $this->colorArgs($args);
1122
1123
		$hsl = $this->toHSL($color);
1124
		$hsl[3] = $this->clamp($hsl[3] + $delta, 100);
1125
		return $this->toRGB($hsl);
1126
	}
1127
1128 View Code Duplication
	protected function lib_saturate($args) {
1129
		list($color, $delta) = $this->colorArgs($args);
1130
1131
		$hsl = $this->toHSL($color);
1132
		$hsl[2] = $this->clamp($hsl[2] + $delta, 100);
1133
		return $this->toRGB($hsl);
1134
	}
1135
1136 View Code Duplication
	protected function lib_desaturate($args) {
1137
		list($color, $delta) = $this->colorArgs($args);
1138
1139
		$hsl = $this->toHSL($color);
1140
		$hsl[2] = $this->clamp($hsl[2] - $delta, 100);
1141
		return $this->toRGB($hsl);
1142
	}
1143
1144
	protected function lib_spin($args) {
1145
		list($color, $delta) = $this->colorArgs($args);
1146
1147
		$hsl = $this->toHSL($color);
1148
1149
		$hsl[1] = $hsl[1] + $delta % 360;
1150
		if ($hsl[1] < 0) $hsl[1] += 360;
1151
1152
		return $this->toRGB($hsl);
1153
	}
1154
1155
	protected function lib_fadeout($args) {
1156
		list($color, $delta) = $this->colorArgs($args);
1157
		$color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) - $delta/100);
1158
		return $color;
1159
	}
1160
1161
	protected function lib_fadein($args) {
1162
		list($color, $delta) = $this->colorArgs($args);
1163
		$color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) + $delta/100);
1164
		return $color;
1165
	}
1166
1167
	protected function lib_hue($color) {
1168
		$hsl = $this->toHSL($this->assertColor($color));
1169
		return round($hsl[1]);
1170
	}
1171
1172
	protected function lib_saturation($color) {
1173
		$hsl = $this->toHSL($this->assertColor($color));
1174
		return round($hsl[2]);
1175
	}
1176
1177
	protected function lib_lightness($color) {
1178
		$hsl = $this->toHSL($this->assertColor($color));
1179
		return round($hsl[3]);
1180
	}
1181
1182
	// get the alpha of a color
1183
	// defaults to 1 for non-colors or colors without an alpha
1184
	protected function lib_alpha($value) {
1185
		if (!is_null($color = $this->coerceColor($value))) {
1186
			return isset($color[4]) ? $color[4] : 1;
1187
		}
1188
	}
1189
1190
	// set the alpha of the color
1191
	protected function lib_fade($args) {
1192
		list($color, $alpha) = $this->colorArgs($args);
1193
		$color[4] = $this->clamp($alpha / 100.0);
1194
		return $color;
1195
	}
1196
1197
	protected function lib_percentage($arg) {
1198
		$num = $this->assertNumber($arg);
1199
		return array("number", $num*100, "%");
1200
	}
1201
1202
	// mixes two colors by weight
1203
	// mix(@color1, @color2, [@weight: 50%]);
1204
	// http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#mix-instance_method
1205
	protected function lib_mix($args) {
1206
		if ($args[0] != "list" || count($args[2]) < 2)
1207
			$this->throwError("mix expects (color1, color2, weight)");
1208
1209
		list($first, $second) = $args[2];
1210
		$first = $this->assertColor($first);
1211
		$second = $this->assertColor($second);
1212
1213
		$first_a = $this->lib_alpha($first);
1214
		$second_a = $this->lib_alpha($second);
1215
1216
		if (isset($args[2][2])) {
1217
			$weight = $args[2][2][1] / 100.0;
1218
		} else {
1219
			$weight = 0.5;
1220
		}
1221
1222
		$w = $weight * 2 - 1;
1223
		$a = $first_a - $second_a;
1224
1225
		$w1 = (($w * $a == -1 ? $w : ($w + $a)/(1 + $w * $a)) + 1) / 2.0;
1226
		$w2 = 1.0 - $w1;
1227
1228
		$new = array('color',
1229
			$w1 * $first[1] + $w2 * $second[1],
1230
			$w1 * $first[2] + $w2 * $second[2],
1231
			$w1 * $first[3] + $w2 * $second[3],
1232
		);
1233
1234
		if ($first_a != 1.0 || $second_a != 1.0) {
1235
			$new[] = $first_a * $weight + $second_a * ($weight - 1);
1236
		}
1237
1238
		return $this->fixColor($new);
1239
	}
1240
1241
	protected function lib_contrast($args) {
1242
	    $darkColor  = array('color', 0, 0, 0);
1243
	    $lightColor = array('color', 255, 255, 255);
1244
	    $threshold  = 0.43;
1245
1246
	    if ( $args[0] == 'list' ) {
1247
	        $inputColor = ( isset($args[2][0]) ) ? $this->assertColor($args[2][0])  : $lightColor;
1248
	        $darkColor  = ( isset($args[2][1]) ) ? $this->assertColor($args[2][1])  : $darkColor;
1249
	        $lightColor = ( isset($args[2][2]) ) ? $this->assertColor($args[2][2])  : $lightColor;
1250
	        $threshold  = ( isset($args[2][3]) ) ? $this->assertNumber($args[2][3]) : $threshold;
1251
	    }
1252
	    else {
1253
	        $inputColor  = $this->assertColor($args);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 2 spaces

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

$a = "a";
$ab = "ab";
$abc = "abc";

will have no issues, while

$a   = "a";
$ab  = "ab";
$abc = "abc";

will report issues in lines 1 and 2.

Loading history...
1254
	    }
1255
1256
	    $inputColor = $this->coerceColor($inputColor);
1257
	    $darkColor  = $this->coerceColor($darkColor);
1258
	    $lightColor = $this->coerceColor($lightColor);
1259
1260
	    //Figure out which is actually light and dark!
1261
	    if ( $this->lib_luma($darkColor) > $this->lib_luma($lightColor) ) {
1262
	        $t  = $lightColor;
1263
	        $lightColor = $darkColor;
1264
	        $darkColor  = $t;
1265
	    }
1266
1267
	    $inputColor_alpha = $this->lib_alpha($inputColor);
1268
	    if ( ( $this->lib_luma($inputColor) * $inputColor_alpha) < $threshold) {
1269
	        return $lightColor;
1270
	    }
1271
	    return $darkColor;
1272
	}
1273
1274
	protected function lib_luma($color) {
1275
	    $color = $this->coerceColor($color);
1276
	    return (0.2126 * $color[0] / 255) + (0.7152 * $color[1] / 255) + (0.0722 * $color[2] / 255);
1277
	}
1278
1279
1280
	public function assertColor($value, $error = "expected color value") {
1281
		$color = $this->coerceColor($value);
1282
		if (is_null($color)) $this->throwError($error);
1283
		return $color;
1284
	}
1285
1286
	public function assertNumber($value, $error = "expecting number") {
1287
		if ($value[0] == "number") return $value[1];
1288
		$this->throwError($error);
1289
	}
1290
1291
	public function assertArgs($value, $expectedArgs, $name="") {
1292
		if ($expectedArgs == 1) {
1293
			return $value;
1294
		} else {
1295
			if ($value[0] !== "list" || $value[1] != ",") $this->throwError("expecting list");
1296
			$values = $value[2];
1297
			$numValues = count($values);
1298
			if ($expectedArgs != $numValues) {
1299
				if ($name) {
1300
					$name = $name . ": ";
1301
				}
1302
1303
				$this->throwError("${name}expecting $expectedArgs arguments, got $numValues");
1304
			}
1305
1306
			return $values;
1307
		}
1308
	}
1309
1310
	protected function toHSL($color) {
1311
		if ($color[0] == 'hsl') return $color;
1312
1313
		$r = $color[1] / 255;
1314
		$g = $color[2] / 255;
1315
		$b = $color[3] / 255;
1316
1317
		$min = min($r, $g, $b);
1318
		$max = max($r, $g, $b);
1319
1320
		$L = ($min + $max) / 2;
1321
		if ($min == $max) {
1322
			$S = $H = 0;
1323
		} else {
1324
			if ($L < 0.5)
1325
				$S = ($max - $min)/($max + $min);
1326
			else
1327
				$S = ($max - $min)/(2.0 - $max - $min);
1328
1329
			if ($r == $max) $H = ($g - $b)/($max - $min);
1330
			elseif ($g == $max) $H = 2.0 + ($b - $r)/($max - $min);
1331
			elseif ($b == $max) $H = 4.0 + ($r - $g)/($max - $min);
1332
1333
		}
1334
1335
		$out = array('hsl',
1336
			($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...
1337
			$S*100,
1338
			$L*100,
1339
		);
1340
1341
		if (count($color) > 4) $out[] = $color[4]; // copy alpha
1342
		return $out;
1343
	}
1344
1345
	protected function toRGB_helper($comp, $temp1, $temp2) {
1346
		if ($comp < 0) $comp += 1.0;
1347
		elseif ($comp > 1) $comp -= 1.0;
1348
1349 View Code Duplication
		if (6 * $comp < 1) return $temp1 + ($temp2 - $temp1) * 6 * $comp;
1350
		if (2 * $comp < 1) return $temp2;
1351 View Code Duplication
		if (3 * $comp < 2) return $temp1 + ($temp2 - $temp1)*((2/3) - $comp) * 6;
1352
1353
		return $temp1;
1354
	}
1355
1356
	/**
1357
	 * Converts a hsl array into a color value in rgb.
1358
	 * Expects H to be in range of 0 to 360, S and L in 0 to 100
1359
	 */
1360
	protected function toRGB($color) {
1361
		if ($color[0] == 'color') return $color;
1362
1363
		$H = $color[1] / 360;
1364
		$S = $color[2] / 100;
1365
		$L = $color[3] / 100;
1366
1367
		if ($S == 0) {
1368
			$r = $g = $b = $L;
1369
		} else {
1370
			$temp2 = $L < 0.5 ?
1371
				$L*(1.0 + $S) :
1372
				$L + $S - $L * $S;
1373
1374
			$temp1 = 2.0 * $L - $temp2;
1375
1376
			$r = $this->toRGB_helper($H + 1/3, $temp1, $temp2);
1377
			$g = $this->toRGB_helper($H, $temp1, $temp2);
1378
			$b = $this->toRGB_helper($H - 1/3, $temp1, $temp2);
1379
		}
1380
1381
		// $out = array('color', round($r*255), round($g*255), round($b*255));
1382
		$out = array('color', $r*255, $g*255, $b*255);
1383
		if (count($color) > 4) $out[] = $color[4]; // copy alpha
1384
		return $out;
1385
	}
1386
1387
	protected function clamp($v, $max = 1, $min = 0) {
1388
		return min($max, max($min, $v));
1389
	}
1390
1391
	/**
1392
	 * Convert the rgb, rgba, hsl color literals of function type
1393
	 * as returned by the parser into values of color type.
1394
	 */
1395
	protected function funcToColor($func) {
1396
		$fname = $func[1];
1397
		if ($func[2][0] != 'list') return false; // need a list of arguments
1398
		$rawComponents = $func[2][2];
1399
1400
		if ($fname == 'hsl' || $fname == 'hsla') {
1401
			$hsl = array('hsl');
1402
			$i = 0;
1403
			foreach ($rawComponents as $c) {
1404
				$val = $this->reduce($c);
1405
				$val = isset($val[1]) ? floatval($val[1]) : 0;
1406
1407
				if ($i == 0) $clamp = 360;
1408
				elseif ($i < 3) $clamp = 100;
1409
				else $clamp = 1;
1410
1411
				$hsl[] = $this->clamp($val, $clamp);
1412
				$i++;
1413
			}
1414
1415
			while (count($hsl) < 4) $hsl[] = 0;
1416
			return $this->toRGB($hsl);
1417
1418
		} elseif ($fname == 'rgb' || $fname == 'rgba') {
1419
			$components = array();
1420
			$i = 1;
1421
			foreach	($rawComponents as $c) {
1422
				$c = $this->reduce($c);
1423
				if ($i < 4) {
1424 View Code Duplication
					if ($c[0] == "number" && $c[2] == "%") {
1425
						$components[] = 255 * ($c[1] / 100);
1426
					} else {
1427
						$components[] = floatval($c[1]);
1428
					}
1429 View Code Duplication
				} elseif ($i == 4) {
1430
					if ($c[0] == "number" && $c[2] == "%") {
1431
						$components[] = 1.0 * ($c[1] / 100);
1432
					} else {
1433
						$components[] = floatval($c[1]);
1434
					}
1435
				} else break;
1436
1437
				$i++;
1438
			}
1439
			while (count($components) < 3) $components[] = 0;
1440
			array_unshift($components, 'color');
1441
			return $this->fixColor($components);
1442
		}
1443
1444
		return false;
1445
	}
1446
1447
	protected function reduce($value, $forExpression = false) {
1448
		switch ($value[0]) {
1449
		case "interpolate":
1450
			$reduced = $this->reduce($value[1]);
1451
			$var = $this->compileValue($reduced);
1452
			$res = $this->reduce(array("variable", $this->vPrefix . $var));
1453
1454
			if ($res[0] == "raw_color") {
1455
				$res = $this->coerceColor($res);
1456
			}
1457
1458
			if (empty($value[2])) $res = $this->lib_e($res);
1459
1460
			return $res;
1461
		case "variable":
1462
			$key = $value[1];
1463
			if (is_array($key)) {
1464
				$key = $this->reduce($key);
1465
				$key = $this->vPrefix . $this->compileValue($this->lib_e($key));
1466
			}
1467
1468
			$seen =& $this->env->seenNames;
1469
1470
			if (!empty($seen[$key])) {
1471
				$this->throwError("infinite loop detected: $key");
1472
			}
1473
1474
			$seen[$key] = true;
1475
			$out = $this->reduce($this->get($key));
1476
			$seen[$key] = false;
1477
			return $out;
1478
		case "list":
1479
			foreach ($value[2] as &$item) {
1480
				$item = $this->reduce($item, $forExpression);
1481
			}
1482
			return $value;
1483
		case "expression":
1484
			return $this->evaluate($value);
1485
		case "string":
1486
			foreach ($value[2] as &$part) {
1487
				if (is_array($part)) {
1488
					$strip = $part[0] == "variable";
1489
					$part = $this->reduce($part);
1490
					if ($strip) $part = $this->lib_e($part);
1491
				}
1492
			}
1493
			return $value;
1494
		case "escape":
1495
			list(,$inner) = $value;
1496
			return $this->lib_e($this->reduce($inner));
1497
		case "function":
1498
			$color = $this->funcToColor($value);
1499
			if ($color) return $color;
1500
1501
			list(, $name, $args) = $value;
1502
			if ($name == "%") $name = "_sprintf";
1503
1504
			$f = isset($this->libFunctions[$name]) ?
1505
				$this->libFunctions[$name] : array($this, 'lib_'.str_replace('-', '_', $name));
1506
1507
			if (is_callable($f)) {
1508
				if ($args[0] == 'list')
1509
					$args = self::compressList($args[2], $args[1]);
1510
1511
				$ret = call_user_func($f, $this->reduce($args, true), $this);
1512
1513
				if (is_null($ret)) {
1514
					return array("string", "", array(
1515
						$name, "(", $args, ")"
1516
					));
1517
				}
1518
1519
				// convert to a typed value if the result is a php primitive
1520
				if (is_numeric($ret)) $ret = array('number', $ret, "");
1521
				elseif (!is_array($ret)) $ret = array('keyword', $ret);
1522
1523
				return $ret;
1524
			}
1525
1526
			// plain function, reduce args
1527
			$value[2] = $this->reduce($value[2]);
1528
			return $value;
1529
		case "unary":
1530
			list(, $op, $exp) = $value;
1531
			$exp = $this->reduce($exp);
1532
1533 View Code Duplication
			if ($exp[0] == "number") {
1534
				switch ($op) {
1535
				case "+":
1536
					return $exp;
1537
				case "-":
1538
					$exp[1] *= -1;
1539
					return $exp;
1540
				}
1541
			}
1542
			return array("string", "", array($op, $exp));
1543
		}
1544
1545
		if ($forExpression) {
1546
			switch ($value[0]) {
1547
			case "keyword":
1548
				if ($color = $this->coerceColor($value)) {
1549
					return $color;
1550
				}
1551
				break;
1552
			case "raw_color":
1553
				return $this->coerceColor($value);
1554
			}
1555
		}
1556
1557
		return $value;
1558
	}
1559
1560
1561
	// coerce a value for use in color operation
1562
	protected function coerceColor($value) {
1563
		switch($value[0]) {
1564
			case 'color': return $value;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1565
			case 'raw_color':
1566
				$c = array("color", 0, 0, 0);
1567
				$colorStr = substr($value[1], 1);
1568
				$num = hexdec($colorStr);
1569
				$width = strlen($colorStr) == 3 ? 16 : 256;
1570
1571
				for ($i = 3; $i > 0; $i--) { // 3 2 1
1572
					$t = $num % $width;
1573
					$num /= $width;
1574
1575
					$c[$i] = $t * (256/$width) + $t * floor(16/$width);
1576
				}
1577
1578
				return $c;
1579
			case 'keyword':
1580
				$name = $value[1];
1581
				if (isset(self::$cssColors[$name])) {
1582
					$rgba = explode(',', self::$cssColors[$name]);
1583
1584
					if(isset($rgba[3]))
1585
						return array('color', $rgba[0], $rgba[1], $rgba[2], $rgba[3]);
1586
1587
					return array('color', $rgba[0], $rgba[1], $rgba[2]);
1588
				}
1589
				return null;
1590
		}
1591
	}
1592
1593
	// make something string like into a string
1594 View Code Duplication
	protected function coerceString($value) {
1595
		switch ($value[0]) {
1596
		case "string":
1597
			return $value;
1598
		case "keyword":
1599
			return array("string", "", array($value[1]));
1600
		}
1601
		return null;
1602
	}
1603
1604
	// turn list of length 1 into value type
1605
	protected function flattenList($value) {
1606 View Code Duplication
		if ($value[0] == "list" && count($value[2]) == 1) {
1607
			return $this->flattenList($value[2][0]);
1608
		}
1609
		return $value;
1610
	}
1611
1612
	public function toBool($a) {
1613
		if ($a) return self::$TRUE;
1614
		else return self::$FALSE;
1615
	}
1616
1617
	// evaluate an expression
1618
	protected function evaluate($exp) {
1619
		list(, $op, $left, $right, $whiteBefore, $whiteAfter) = $exp;
1620
1621
		$left = $this->reduce($left, true);
1622
		$right = $this->reduce($right, true);
1623
1624
		if ($leftColor = $this->coerceColor($left)) {
1625
			$left = $leftColor;
1626
		}
1627
1628
		if ($rightColor = $this->coerceColor($right)) {
1629
			$right = $rightColor;
1630
		}
1631
1632
		$ltype = $left[0];
1633
		$rtype = $right[0];
1634
1635
		// operators that work on all types
1636
		if ($op == "and") {
1637
			return $this->toBool($left == self::$TRUE && $right == self::$TRUE);
1638
		}
1639
1640
		if ($op == "=") {
1641
			return $this->toBool($this->eq($left, $right) );
1642
		}
1643
1644
		if ($op == "+" && !is_null($str = $this->stringConcatenate($left, $right))) {
1645
			return $str;
1646
		}
1647
1648
		// type based operators
1649
		$fname = "op_${ltype}_${rtype}";
1650
		if (is_callable(array($this, $fname))) {
1651
			$out = $this->$fname($op, $left, $right);
1652
			if (!is_null($out)) return $out;
1653
		}
1654
1655
		// make the expression look it did before being parsed
1656
		$paddedOp = $op;
1657
		if ($whiteBefore) $paddedOp = " " . $paddedOp;
1658
		if ($whiteAfter) $paddedOp .= " ";
1659
1660
		return array("string", "", array($left, $paddedOp, $right));
1661
	}
1662
1663
	protected function stringConcatenate($left, $right) {
1664 View Code Duplication
		if ($strLeft = $this->coerceString($left)) {
1665
			if ($right[0] == "string") {
1666
				$right[1] = "";
1667
			}
1668
			$strLeft[2][] = $right;
1669
			return $strLeft;
1670
		}
1671
1672
		if ($strRight = $this->coerceString($right)) {
1673
			array_unshift($strRight[2], $left);
1674
			return $strRight;
1675
		}
1676
	}
1677
1678
1679
	// make sure a color's components don't go out of bounds
1680 View Code Duplication
	protected function fixColor($c) {
1681
		foreach (range(1, 3) as $i) {
1682
			if ($c[$i] < 0) $c[$i] = 0;
1683
			if ($c[$i] > 255) $c[$i] = 255;
1684
		}
1685
1686
		return $c;
1687
	}
1688
1689
	protected function op_number_color($op, $lft, $rgt) {
1690
		if ($op == '+' || $op == '*') {
1691
			return $this->op_color_number($op, $rgt, $lft);
1692
		}
1693
	}
1694
1695
	protected function op_color_number($op, $lft, $rgt) {
1696
		if ($rgt[0] == '%') $rgt[1] /= 100;
1697
1698
		return $this->op_color_color($op, $lft,
1699
			array_fill(1, count($lft) - 1, $rgt[1]));
1700
	}
1701
1702
	protected function op_color_color($op, $left, $right) {
1703
		$out = array('color');
1704
		$max = count($left) > count($right) ? count($left) : count($right);
1705
		foreach (range(1, $max - 1) as $i) {
1706
			$lval = isset($left[$i]) ? $left[$i] : 0;
1707
			$rval = isset($right[$i]) ? $right[$i] : 0;
1708
			switch ($op) {
1709
			case '+':
1710
				$out[] = $lval + $rval;
1711
				break;
1712
			case '-':
1713
				$out[] = $lval - $rval;
1714
				break;
1715
			case '*':
1716
				$out[] = $lval * $rval;
1717
				break;
1718
			case '%':
1719
				$out[] = $lval % $rval;
1720
				break;
1721 View Code Duplication
			case '/':
1722
				if ($rval == 0) $this->throwError("evaluate error: can't divide by zero");
1723
				$out[] = $lval / $rval;
1724
				break;
1725
			default:
1726
				$this->throwError('evaluate error: color op number failed on op '.$op);
1727
			}
1728
		}
1729
		return $this->fixColor($out);
1730
	}
1731
1732 View Code Duplication
	function lib_red($color){
1733
		$color = $this->coerceColor($color);
1734
		if (is_null($color)) {
1735
			$this->throwError('color expected for red()');
1736
		}
1737
1738
		return $color[1];
1739
	}
1740
1741 View Code Duplication
	function lib_green($color){
1742
		$color = $this->coerceColor($color);
1743
		if (is_null($color)) {
1744
			$this->throwError('color expected for green()');
1745
		}
1746
1747
		return $color[2];
1748
	}
1749
1750 View Code Duplication
	function lib_blue($color){
1751
		$color = $this->coerceColor($color);
1752
		if (is_null($color)) {
1753
			$this->throwError('color expected for blue()');
1754
		}
1755
1756
		return $color[3];
1757
	}
1758
1759
1760
	// operator on two numbers
1761
	protected function op_number_number($op, $left, $right) {
1762
		$unit = empty($left[2]) ? $right[2] : $left[2];
1763
1764
		$value = 0;
1765
		switch ($op) {
1766
		case '+':
1767
			$value = $left[1] + $right[1];
1768
			break;
1769
		case '*':
1770
			$value = $left[1] * $right[1];
1771
			break;
1772
		case '-':
1773
			$value = $left[1] - $right[1];
1774
			break;
1775
		case '%':
1776
			$value = $left[1] % $right[1];
1777
			break;
1778
		case '/':
1779
			if ($right[1] == 0) $this->throwError('parse error: divide by zero');
1780
			$value = $left[1] / $right[1];
1781
			break;
1782
		case '<':
1783
			return $this->toBool($left[1] < $right[1]);
1784
		case '>':
1785
			return $this->toBool($left[1] > $right[1]);
1786
		case '>=':
1787
			return $this->toBool($left[1] >= $right[1]);
1788
		case '=<':
1789
			return $this->toBool($left[1] <= $right[1]);
1790
		default:
1791
			$this->throwError('parse error: unknown number operator: '.$op);
1792
		}
1793
1794
		return array("number", $value, $unit);
1795
	}
1796
1797
1798
	/* environment functions */
1799
1800
	protected function makeOutputBlock($type, $selectors = null) {
1801
		$b = new stdclass;
1802
		$b->lines = array();
1803
		$b->children = array();
1804
		$b->selectors = $selectors;
1805
		$b->type = $type;
1806
		$b->parent = $this->scope;
1807
		return $b;
1808
	}
1809
1810
	// the state of execution
1811
	protected function pushEnv($block = null) {
1812
		$e = new stdclass;
1813
		$e->parent = $this->env;
1814
		$e->store = array();
1815
		$e->block = $block;
1816
1817
		$this->env = $e;
1818
		return $e;
1819
	}
1820
1821
	// pop something off the stack
1822
	protected function popEnv() {
1823
		$old = $this->env;
1824
		$this->env = $this->env->parent;
1825
		return $old;
1826
	}
1827
1828
	// set something in the current env
1829
	protected function set($name, $value) {
1830
		$this->env->store[$name] = $value;
1831
	}
1832
1833
1834
	// get the highest occurrence entry for a name
1835
	protected function get($name) {
1836
		$current = $this->env;
1837
1838
		$isArguments = $name == $this->vPrefix . 'arguments';
1839
		while ($current) {
1840
			if ($isArguments && isset($current->arguments)) {
1841
				return array('list', ' ', $current->arguments);
1842
			}
1843
1844 View Code Duplication
			if (isset($current->store[$name]))
1845
				return $current->store[$name];
1846
			else {
1847
				$current = isset($current->storeParent) ?
1848
					$current->storeParent : $current->parent;
1849
			}
1850
		}
1851
1852
		$this->throwError("variable $name is undefined");
1853
	}
1854
1855
	// inject array of unparsed strings into environment as variables
1856
	protected function injectVariables($args) {
1857
		$this->pushEnv();
1858
		$parser = new lessc_parser($this, __METHOD__);
1859
		foreach ($args as $name => $strValue) {
1860
			if ($name{0} != '@') $name = '@'.$name;
1861
			$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...
1862
			$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...
1863
			if (!$parser->propertyValue($value)) {
1864
				throw new Exception("failed to parse passed in variable $name: $strValue");
1865
			}
1866
1867
			$this->set($name, $value);
1868
		}
1869
	}
1870
1871
	/**
1872
	 * Initialize any static state, can initialize parser for a file
1873
	 * $opts isn't used yet
1874
	 */
1875
	public function __construct($fname = null) {
1876
		if ($fname !== null) {
1877
			// used for deprecated parse method
1878
			$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...
1879
		}
1880
	}
1881
1882
	public function compile($string, $name = null) {
1883
		$locale = setlocale(LC_NUMERIC, 0);
1884
		setlocale(LC_NUMERIC, "C");
1885
1886
		$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...
1887
		$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...
1888
1889
		$this->env = null;
1890
		$this->scope = null;
1891
1892
		$this->formatter = $this->newFormatter();
1893
1894
		if (!empty($this->registeredVars)) {
1895
			$this->injectVariables($this->registeredVars);
1896
		}
1897
1898
		$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...
1899
		$this->compileBlock($root);
1900
1901
		ob_start();
1902
		$this->formatter->block($this->scope);
1903
		$out = ob_get_clean();
1904
		setlocale(LC_NUMERIC, $locale);
1905
		return $out;
1906
	}
1907
1908
	public function compileFile($fname, $outFname = null) {
1909
		if (!is_readable($fname)) {
1910
			throw new Exception('load error: failed to find '.$fname);
1911
		}
1912
1913
		$pi = pathinfo($fname);
1914
1915
		$oldImport = $this->importDir;
1916
1917
		$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...
1918
		$this->importDir[] = $pi['dirname'].'/';
1919
1920
		$this->addParsedFile($fname);
1921
1922
		$out = $this->compile(file_get_contents($fname), $fname);
1923
1924
		$this->importDir = $oldImport;
1925
1926
		if ($outFname !== null) {
1927
			return file_put_contents($outFname, $out);
1928
		}
1929
1930
		return $out;
1931
	}
1932
1933
	// compile only if changed input has changed or output doesn't exist
1934
	public function checkedCompile($in, $out) {
1935
		if (!is_file($out) || filemtime($in) > filemtime($out)) {
1936
			$this->compileFile($in, $out);
1937
			return true;
1938
		}
1939
		return false;
1940
	}
1941
1942
	/**
1943
	 * Execute lessphp on a .less file or a lessphp cache structure
1944
	 *
1945
	 * The lessphp cache structure contains information about a specific
1946
	 * less file having been parsed. It can be used as a hint for future
1947
	 * calls to determine whether or not a rebuild is required.
1948
	 *
1949
	 * The cache structure contains two important keys that may be used
1950
	 * externally:
1951
	 *
1952
	 * compiled: The final compiled CSS
1953
	 * updated: The time (in seconds) the CSS was last compiled
1954
	 *
1955
	 * The cache structure is a plain-ol' PHP associative array and can
1956
	 * be serialized and unserialized without a hitch.
1957
	 *
1958
	 * @param mixed $in Input
1959
	 * @param bool $force Force rebuild?
1960
	 * @return array lessphp cache structure
1961
	 */
1962
	public function cachedCompile($in, $force = false) {
1963
		// assume no root
1964
		$root = null;
1965
1966
		if (is_string($in)) {
1967
			$root = $in;
1968
		} elseif (is_array($in) and isset($in['root'])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1969
			if ($force or ! isset($in['files'])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1970
				// If we are forcing a recompile or if for some reason the
1971
				// structure does not contain any file information we should
1972
				// specify the root to trigger a rebuild.
1973
				$root = $in['root'];
1974
			} elseif (isset($in['files']) and is_array($in['files'])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1975
				foreach ($in['files'] as $fname => $ftime ) {
1976
					if (!file_exists($fname) or filemtime($fname) > $ftime) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1977
						// One of the files we knew about previously has changed
1978
						// so we should look at our incoming root again.
1979
						$root = $in['root'];
1980
						break;
1981
					}
1982
				}
1983
			}
1984
		} else {
1985
			// TODO: Throw an exception? We got neither a string nor something
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
1986
			// that looks like a compatible lessphp cache structure.
1987
			return null;
1988
		}
1989
1990
		if ($root !== null) {
1991
			// If we have a root value which means we should rebuild.
1992
			$out = array();
1993
			$out['root'] = $root;
1994
			$out['compiled'] = $this->compileFile($root);
1995
			$out['files'] = $this->allParsedFiles();
1996
			$out['updated'] = time();
1997
			return $out;
1998
		} else {
1999
			// No changes, pass back the structure
2000
			// we were given initially.
2001
			return $in;
2002
		}
2003
2004
	}
2005
2006
	// parse and compile buffer
2007
	// This is deprecated
2008
	public function parse($str = null, $initialVariables = null) {
2009
		if (is_array($str)) {
2010
			$initialVariables = $str;
2011
			$str = null;
2012
		}
2013
2014
		$oldVars = $this->registeredVars;
2015
		if ($initialVariables !== null) {
2016
			$this->setVariables($initialVariables);
2017
		}
2018
2019
		if ($str == null) {
2020
			if (empty($this->_parseFile)) {
2021
				throw new exception("nothing to parse");
2022
			}
2023
2024
			$out = $this->compileFile($this->_parseFile);
2025
		} else {
2026
			$out = $this->compile($str);
2027
		}
2028
2029
		$this->registeredVars = $oldVars;
2030
		return $out;
2031
	}
2032
2033
	protected function makeParser($name) {
2034
		$parser = new lessc_parser($this, $name);
2035
		$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...
2036
2037
		return $parser;
2038
	}
2039
2040
	public function setFormatter($name) {
2041
		$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...
2042
	}
2043
2044
	protected function newFormatter() {
2045
		$className = "lessc_formatter_lessjs";
2046
		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...
2047
			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...
2048
				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...
2049
			$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...
2050
		}
2051
2052
		return new $className;
2053
	}
2054
2055
	public function setPreserveComments($preserve) {
2056
		$this->preserveComments = $preserve;
2057
	}
2058
2059
	public function registerFunction($name, $func) {
2060
		$this->libFunctions[$name] = $func;
2061
	}
2062
2063
	public function unregisterFunction($name) {
2064
		unset($this->libFunctions[$name]);
2065
	}
2066
2067
	public function setVariables($variables) {
2068
		$this->registeredVars = array_merge($this->registeredVars, $variables);
2069
	}
2070
2071
	public function unsetVariable($name) {
2072
		unset($this->registeredVars[$name]);
2073
	}
2074
2075
	public function setImportDir($dirs) {
2076
		$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...
2077
	}
2078
2079
	public function addImportDir($dir) {
2080
		$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...
2081
		$this->importDir[] = $dir;
2082
	}
2083
2084
	public function allParsedFiles() {
2085
		return $this->allParsedFiles;
2086
	}
2087
2088
	public function addParsedFile($file) {
2089
		$this->allParsedFiles[realpath($file)] = filemtime($file);
2090
	}
2091
2092
	/**
2093
	 * Uses the current value of $this->count to show line and line number
2094
	 */
2095
	public function throwError($msg = null) {
2096
		if ($this->sourceLoc >= 0) {
2097
			$this->sourceParser->throwError($msg, $this->sourceLoc);
2098
		}
2099
		throw new exception($msg);
2100
	}
2101
2102
	// compile file $in to file $out if $in is newer than $out
2103
	// returns true when it compiles, false otherwise
2104
	public static function ccompile($in, $out, $less = null) {
2105
		if ($less === null) {
2106
			$less = new self;
2107
		}
2108
		return $less->checkedCompile($in, $out);
2109
	}
2110
2111
	public static function cexecute($in, $force = false, $less = null) {
2112
		if ($less === null) {
2113
			$less = new self;
2114
		}
2115
		return $less->cachedCompile($in, $force);
2116
	}
2117
2118
	static protected $cssColors = array(
2119
		'aliceblue' => '240,248,255',
2120
		'antiquewhite' => '250,235,215',
2121
		'aqua' => '0,255,255',
2122
		'aquamarine' => '127,255,212',
2123
		'azure' => '240,255,255',
2124
		'beige' => '245,245,220',
2125
		'bisque' => '255,228,196',
2126
		'black' => '0,0,0',
2127
		'blanchedalmond' => '255,235,205',
2128
		'blue' => '0,0,255',
2129
		'blueviolet' => '138,43,226',
2130
		'brown' => '165,42,42',
2131
		'burlywood' => '222,184,135',
2132
		'cadetblue' => '95,158,160',
2133
		'chartreuse' => '127,255,0',
2134
		'chocolate' => '210,105,30',
2135
		'coral' => '255,127,80',
2136
		'cornflowerblue' => '100,149,237',
2137
		'cornsilk' => '255,248,220',
2138
		'crimson' => '220,20,60',
2139
		'cyan' => '0,255,255',
2140
		'darkblue' => '0,0,139',
2141
		'darkcyan' => '0,139,139',
2142
		'darkgoldenrod' => '184,134,11',
2143
		'darkgray' => '169,169,169',
2144
		'darkgreen' => '0,100,0',
2145
		'darkgrey' => '169,169,169',
2146
		'darkkhaki' => '189,183,107',
2147
		'darkmagenta' => '139,0,139',
2148
		'darkolivegreen' => '85,107,47',
2149
		'darkorange' => '255,140,0',
2150
		'darkorchid' => '153,50,204',
2151
		'darkred' => '139,0,0',
2152
		'darksalmon' => '233,150,122',
2153
		'darkseagreen' => '143,188,143',
2154
		'darkslateblue' => '72,61,139',
2155
		'darkslategray' => '47,79,79',
2156
		'darkslategrey' => '47,79,79',
2157
		'darkturquoise' => '0,206,209',
2158
		'darkviolet' => '148,0,211',
2159
		'deeppink' => '255,20,147',
2160
		'deepskyblue' => '0,191,255',
2161
		'dimgray' => '105,105,105',
2162
		'dimgrey' => '105,105,105',
2163
		'dodgerblue' => '30,144,255',
2164
		'firebrick' => '178,34,34',
2165
		'floralwhite' => '255,250,240',
2166
		'forestgreen' => '34,139,34',
2167
		'fuchsia' => '255,0,255',
2168
		'gainsboro' => '220,220,220',
2169
		'ghostwhite' => '248,248,255',
2170
		'gold' => '255,215,0',
2171
		'goldenrod' => '218,165,32',
2172
		'gray' => '128,128,128',
2173
		'green' => '0,128,0',
2174
		'greenyellow' => '173,255,47',
2175
		'grey' => '128,128,128',
2176
		'honeydew' => '240,255,240',
2177
		'hotpink' => '255,105,180',
2178
		'indianred' => '205,92,92',
2179
		'indigo' => '75,0,130',
2180
		'ivory' => '255,255,240',
2181
		'khaki' => '240,230,140',
2182
		'lavender' => '230,230,250',
2183
		'lavenderblush' => '255,240,245',
2184
		'lawngreen' => '124,252,0',
2185
		'lemonchiffon' => '255,250,205',
2186
		'lightblue' => '173,216,230',
2187
		'lightcoral' => '240,128,128',
2188
		'lightcyan' => '224,255,255',
2189
		'lightgoldenrodyellow' => '250,250,210',
2190
		'lightgray' => '211,211,211',
2191
		'lightgreen' => '144,238,144',
2192
		'lightgrey' => '211,211,211',
2193
		'lightpink' => '255,182,193',
2194
		'lightsalmon' => '255,160,122',
2195
		'lightseagreen' => '32,178,170',
2196
		'lightskyblue' => '135,206,250',
2197
		'lightslategray' => '119,136,153',
2198
		'lightslategrey' => '119,136,153',
2199
		'lightsteelblue' => '176,196,222',
2200
		'lightyellow' => '255,255,224',
2201
		'lime' => '0,255,0',
2202
		'limegreen' => '50,205,50',
2203
		'linen' => '250,240,230',
2204
		'magenta' => '255,0,255',
2205
		'maroon' => '128,0,0',
2206
		'mediumaquamarine' => '102,205,170',
2207
		'mediumblue' => '0,0,205',
2208
		'mediumorchid' => '186,85,211',
2209
		'mediumpurple' => '147,112,219',
2210
		'mediumseagreen' => '60,179,113',
2211
		'mediumslateblue' => '123,104,238',
2212
		'mediumspringgreen' => '0,250,154',
2213
		'mediumturquoise' => '72,209,204',
2214
		'mediumvioletred' => '199,21,133',
2215
		'midnightblue' => '25,25,112',
2216
		'mintcream' => '245,255,250',
2217
		'mistyrose' => '255,228,225',
2218
		'moccasin' => '255,228,181',
2219
		'navajowhite' => '255,222,173',
2220
		'navy' => '0,0,128',
2221
		'oldlace' => '253,245,230',
2222
		'olive' => '128,128,0',
2223
		'olivedrab' => '107,142,35',
2224
		'orange' => '255,165,0',
2225
		'orangered' => '255,69,0',
2226
		'orchid' => '218,112,214',
2227
		'palegoldenrod' => '238,232,170',
2228
		'palegreen' => '152,251,152',
2229
		'paleturquoise' => '175,238,238',
2230
		'palevioletred' => '219,112,147',
2231
		'papayawhip' => '255,239,213',
2232
		'peachpuff' => '255,218,185',
2233
		'peru' => '205,133,63',
2234
		'pink' => '255,192,203',
2235
		'plum' => '221,160,221',
2236
		'powderblue' => '176,224,230',
2237
		'purple' => '128,0,128',
2238
		'red' => '255,0,0',
2239
		'rosybrown' => '188,143,143',
2240
		'royalblue' => '65,105,225',
2241
		'saddlebrown' => '139,69,19',
2242
		'salmon' => '250,128,114',
2243
		'sandybrown' => '244,164,96',
2244
		'seagreen' => '46,139,87',
2245
		'seashell' => '255,245,238',
2246
		'sienna' => '160,82,45',
2247
		'silver' => '192,192,192',
2248
		'skyblue' => '135,206,235',
2249
		'slateblue' => '106,90,205',
2250
		'slategray' => '112,128,144',
2251
		'slategrey' => '112,128,144',
2252
		'snow' => '255,250,250',
2253
		'springgreen' => '0,255,127',
2254
		'steelblue' => '70,130,180',
2255
		'tan' => '210,180,140',
2256
		'teal' => '0,128,128',
2257
		'thistle' => '216,191,216',
2258
		'tomato' => '255,99,71',
2259
		'transparent' => '0,0,0,0',
2260
		'turquoise' => '64,224,208',
2261
		'violet' => '238,130,238',
2262
		'wheat' => '245,222,179',
2263
		'white' => '255,255,255',
2264
		'whitesmoke' => '245,245,245',
2265
		'yellow' => '255,255,0',
2266
		'yellowgreen' => '154,205,50'
2267
	);
2268
}
2269
2270
// responsible for taking a string of LESS code and converting it into a
2271
// syntax tree
2272
class lessc_parser {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
2273
	static protected $nextBlockId = 0; // used to uniquely identify blocks
2274
2275
	static protected $precedence = array(
2276
		'=<' => 0,
2277
		'>=' => 0,
2278
		'=' => 0,
2279
		'<' => 0,
2280
		'>' => 0,
2281
2282
		'+' => 1,
2283
		'-' => 1,
2284
		'*' => 2,
2285
		'/' => 2,
2286
		'%' => 2,
2287
	);
2288
2289
	static protected $whitePattern;
2290
	static protected $commentMulti;
2291
2292
	static protected $commentSingle = "//";
2293
	static protected $commentMultiLeft = "/*";
2294
	static protected $commentMultiRight = "*/";
2295
2296
	// regex string to match any of the operators
2297
	static protected $operatorString;
2298
2299
	// these properties will supress division unless it's inside parenthases
2300
	static protected $supressDivisionProps =
2301
		array('/border-radius$/i', '/^font$/i');
2302
2303
	protected $blockDirectives = array("font-face", "keyframes", "page", "-moz-document", "viewport", "-moz-viewport", "-o-viewport", "-ms-viewport");
2304
	protected $lineDirectives = array("charset");
2305
2306
	/**
2307
	 * if we are in parens we can be more liberal with whitespace around
2308
	 * operators because it must evaluate to a single value and thus is less
2309
	 * ambiguous.
2310
	 *
2311
	 * Consider:
2312
	 *     property1: 10 -5; // is two numbers, 10 and -5
2313
	 *     property2: (10 -5); // should evaluate to 5
2314
	 */
2315
	protected $inParens = false;
2316
2317
	// caches preg escaped literals
2318
	static protected $literalCache = array();
2319
2320
	public function __construct($lessc, $sourceName = null) {
2321
		$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...
2322
		// reference to less needed for vPrefix, mPrefix, and parentSelector
2323
		$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...
2324
2325
		$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...
2326
2327
		$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...
2328
2329
		if (!self::$operatorString) {
2330
			self::$operatorString =
2331
				'('.implode('|', array_map(array('lessc', 'preg_quote'),
2332
					array_keys(self::$precedence))).')';
2333
2334
			$commentSingle = lessc::preg_quote(self::$commentSingle);
2335
			$commentMultiLeft = lessc::preg_quote(self::$commentMultiLeft);
2336
			$commentMultiRight = lessc::preg_quote(self::$commentMultiRight);
2337
2338
			self::$commentMulti = $commentMultiLeft.'.*?'.$commentMultiRight;
2339
			self::$whitePattern = '/'.$commentSingle.'[^\n]*\s*|('.self::$commentMulti.')\s*|\s+/Ais';
2340
		}
2341
	}
2342
2343
	public function parse($buffer) {
2344
		$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...
2345
		$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...
2346
2347
		$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...
2348
		$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...
2349
		$this->pushSpecialBlock("root");
2350
		$this->eatWhiteDefault = true;
2351
		$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...
2352
2353
		// trim whitespace on head
2354
		// if (preg_match('/^\s+/', $this->buffer, $m)) {
2355
		// 	$this->line += substr_count($m[0], "\n");
2356
		// 	$this->buffer = ltrim($this->buffer);
2357
		// }
2358
		$this->whitespace();
2359
2360
		// parse the entire file
2361
		while (false !== $this->parseChunk());
2362
2363
		if ($this->count != strlen($this->buffer))
2364
			$this->throwError();
2365
2366
		// TODO report where the block was opened
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
2367
		if ( !property_exists($this->env, 'parent') || !is_null($this->env->parent) )
2368
			throw new exception('parse error: unclosed block');
2369
2370
		return $this->env;
2371
	}
2372
2373
	/**
2374
	 * Parse a single chunk off the head of the buffer and append it to the
2375
	 * current parse environment.
2376
	 * Returns false when the buffer is empty, or when there is an error.
2377
	 *
2378
	 * This function is called repeatedly until the entire document is
2379
	 * parsed.
2380
	 *
2381
	 * This parser is most similar to a recursive descent parser. Single
2382
	 * functions represent discrete grammatical rules for the language, and
2383
	 * they are able to capture the text that represents those rules.
2384
	 *
2385
	 * Consider the function lessc::keyword(). (all parse functions are
2386
	 * structured the same)
2387
	 *
2388
	 * The function takes a single reference argument. When calling the
2389
	 * function it will attempt to match a keyword on the head of the buffer.
2390
	 * If it is successful, it will place the keyword in the referenced
2391
	 * argument, advance the position in the buffer, and return true. If it
2392
	 * fails then it won't advance the buffer and it will return false.
2393
	 *
2394
	 * All of these parse functions are powered by lessc::match(), which behaves
2395
	 * the same way, but takes a literal regular expression. Sometimes it is
2396
	 * more convenient to use match instead of creating a new function.
2397
	 *
2398
	 * Because of the format of the functions, to parse an entire string of
2399
	 * grammatical rules, you can chain them together using &&.
2400
	 *
2401
	 * But, if some of the rules in the chain succeed before one fails, then
2402
	 * the buffer position will be left at an invalid state. In order to
2403
	 * avoid this, lessc::seek() is used to remember and set buffer positions.
2404
	 *
2405
	 * Before parsing a chain, use $s = $this->seek() to remember the current
2406
	 * position into $s. Then if a chain fails, use $this->seek($s) to
2407
	 * go back where we started.
2408
	 */
2409
	protected function parseChunk() {
2410
		if (empty($this->buffer)) return false;
2411
		$s = $this->seek();
2412
2413
		if ($this->whitespace()) {
2414
			return true;
2415
		}
2416
2417
		// setting a property
2418
		if ($this->keyword($key) && $this->assign() &&
2419
			$this->propertyValue($value, $key) && $this->end())
2420
		{
2421
			$this->append(array('assign', $key, $value), $s);
2422
			return true;
2423
		} else {
2424
			$this->seek($s);
2425
		}
2426
2427
2428
		// look for special css blocks
2429
		if ($this->literal('@', false)) {
2430
			$this->count--;
2431
2432
			// media
2433
			if ($this->literal('@media')) {
2434
				if (($this->mediaQueryList($mediaQueries) || true)
2435
					&& $this->literal('{'))
2436
				{
2437
					$media = $this->pushSpecialBlock("media");
2438
					$media->queries = is_null($mediaQueries) ? array() : $mediaQueries;
2439
					return true;
2440
				} else {
2441
					$this->seek($s);
2442
					return false;
2443
				}
2444
			}
2445
2446
			if ($this->literal("@", false) && $this->keyword($dirName)) {
2447
				if ($this->isDirective($dirName, $this->blockDirectives)) {
2448
					if (($this->openString("{", $dirValue, null, array(";")) || true) &&
2449
						$this->literal("{"))
2450
					{
2451
						$dir = $this->pushSpecialBlock("directive");
2452
						$dir->name = $dirName;
2453
						if (isset($dirValue)) $dir->value = $dirValue;
2454
						return true;
2455
					}
2456
				} elseif ($this->isDirective($dirName, $this->lineDirectives)) {
2457
					if ($this->propertyValue($dirValue) && $this->end()) {
2458
						$this->append(array("directive", $dirName, $dirValue));
2459
						return true;
2460
					}
2461
				}
2462
			}
2463
2464
			$this->seek($s);
2465
		}
2466
2467
		// setting a variable
2468 View Code Duplication
		if ($this->variable($var) && $this->assign() &&
2469
			$this->propertyValue($value) && $this->end())
2470
		{
2471
			$this->append(array('assign', $var, $value), $s);
2472
			return true;
2473
		} else {
2474
			$this->seek($s);
2475
		}
2476
2477
		if ($this->import($importValue)) {
2478
			$this->append($importValue, $s);
2479
			return true;
2480
		}
2481
2482
		// opening parametric mixin
2483
		if ($this->tag($tag, true) && $this->argumentDef($args, $isVararg) &&
2484
			($this->guards($guards) || true) &&
2485
			$this->literal('{'))
2486
		{
2487
			$block = $this->pushBlock($this->fixTags(array($tag)));
2488
			$block->args = $args;
2489
			$block->isVararg = $isVararg;
2490
			if (!empty($guards)) $block->guards = $guards;
2491
			return true;
2492
		} else {
2493
			$this->seek($s);
2494
		}
2495
2496
		// opening a simple block
2497 View Code Duplication
		if ($this->tags($tags) && $this->literal('{', false)) {
2498
			$tags = $this->fixTags($tags);
2499
			$this->pushBlock($tags);
2500
			return true;
2501
		} else {
2502
			$this->seek($s);
2503
		}
2504
2505
		// closing a block
2506
		if ($this->literal('}', false)) {
2507
			try {
2508
				$block = $this->pop();
2509
			} catch (exception $e) {
2510
				$this->seek($s);
2511
				$this->throwError($e->getMessage());
2512
			}
2513
2514
			$hidden = false;
2515
			if (is_null($block->type)) {
2516
				$hidden = true;
2517
				if (!isset($block->args)) {
2518
					foreach ($block->tags as $tag) {
2519
						if (!is_string($tag) || $tag{0} != $this->lessc->mPrefix) {
2520
							$hidden = false;
2521
							break;
2522
						}
2523
					}
2524
				}
2525
2526
				foreach ($block->tags as $tag) {
2527
					if (is_string($tag)) {
2528
						$this->env->children[$tag][] = $block;
2529
					}
2530
				}
2531
			}
2532
2533
			if (!$hidden) {
2534
				$this->append(array('block', $block), $s);
2535
			}
2536
2537
			// this is done here so comments aren't bundled into he block that
2538
			// was just closed
2539
			$this->whitespace();
2540
			return true;
2541
		}
2542
2543
		// mixin
2544
		if ($this->mixinTags($tags) &&
2545
			($this->argumentDef($argv, $isVararg) || true) &&
2546
			($this->keyword($suffix) || true) && $this->end())
2547
		{
2548
			$tags = $this->fixTags($tags);
2549
			$this->append(array('mixin', $tags, $argv, $suffix), $s);
2550
			return true;
2551
		} else {
2552
			$this->seek($s);
2553
		}
2554
2555
		// spare ;
2556
		if ($this->literal(';')) return true;
2557
2558
		return false; // got nothing, throw error
2559
	}
2560
2561
	protected function isDirective($dirname, $directives) {
2562
		// TODO: cache pattern in parser
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
2563
		$pattern = implode("|",
2564
			array_map(array("lessc", "preg_quote"), $directives));
2565
		$pattern = '/^(-[a-z-]+-)?(' . $pattern . ')$/i';
2566
2567
		return preg_match($pattern, $dirname);
2568
	}
2569
2570
	protected function fixTags($tags) {
2571
		// move @ tags out of variable namespace
2572
		foreach ($tags as &$tag) {
2573
			if ($tag{0} == $this->lessc->vPrefix)
2574
				$tag[0] = $this->lessc->mPrefix;
2575
		}
2576
		return $tags;
2577
	}
2578
2579
	// a list of expressions
2580
	protected function expressionList(&$exps) {
2581
		$values = array();
2582
2583
		while ($this->expression($exp)) {
2584
			$values[] = $exp;
2585
		}
2586
2587
		if (count($values) == 0) return false;
2588
2589
		$exps = lessc::compressList($values, ' ');
2590
		return true;
2591
	}
2592
2593
	/**
2594
	 * Attempt to consume an expression.
2595
	 * @link http://en.wikipedia.org/wiki/Operator-precedence_parser#Pseudo-code
2596
	 */
2597
	protected function expression(&$out) {
2598
		if ($this->value($lhs)) {
2599
			$out = $this->expHelper($lhs, 0);
2600
2601
			// look for / shorthand
2602
			if (!empty($this->env->supressedDivision)) {
2603
				unset($this->env->supressedDivision);
2604
				$s = $this->seek();
2605
				if ($this->literal("/") && $this->value($rhs)) {
2606
					$out = array("list", "",
2607
						array($out, array("keyword", "/"), $rhs));
2608
				} else {
2609
					$this->seek($s);
2610
				}
2611
			}
2612
2613
			return true;
2614
		}
2615
		return false;
2616
	}
2617
2618
	/**
2619
	 * recursively parse infix equation with $lhs at precedence $minP
2620
	 */
2621
	protected function expHelper($lhs, $minP) {
2622
		$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...
2623
		$ss = $this->seek();
2624
2625
		while (true) {
2626
			$whiteBefore = isset($this->buffer[$this->count - 1]) &&
2627
				ctype_space($this->buffer[$this->count - 1]);
2628
2629
			// If there is whitespace before the operator, then we require
2630
			// whitespace after the operator for it to be an expression
2631
			$needWhite = $whiteBefore && !$this->inParens;
2632
2633
			if ($this->match(self::$operatorString.($needWhite ? '\s' : ''), $m) && self::$precedence[$m[1]] >= $minP) {
2634
				if (!$this->inParens && isset($this->env->currentProperty) && $m[1] == "/" && empty($this->env->supressedDivision)) {
2635
					foreach (self::$supressDivisionProps as $pattern) {
2636
						if (preg_match($pattern, $this->env->currentProperty)) {
2637
							$this->env->supressedDivision = true;
2638
							break 2;
2639
						}
2640
					}
2641
				}
2642
2643
2644
				$whiteAfter = isset($this->buffer[$this->count - 1]) &&
2645
					ctype_space($this->buffer[$this->count - 1]);
2646
2647
				if (!$this->value($rhs)) break;
2648
2649
				// peek for next operator to see what to do with rhs
2650
				if ($this->peek(self::$operatorString, $next) && self::$precedence[$next[1]] > self::$precedence[$m[1]]) {
2651
					$rhs = $this->expHelper($rhs, self::$precedence[$next[1]]);
2652
				}
2653
2654
				$lhs = array('expression', $m[1], $lhs, $rhs, $whiteBefore, $whiteAfter);
2655
				$ss = $this->seek();
2656
2657
				continue;
2658
			}
2659
2660
			break;
2661
		}
2662
2663
		$this->seek($ss);
2664
2665
		return $lhs;
2666
	}
2667
2668
	// consume a list of values for a property
2669
	public function propertyValue(&$value, $keyName = null) {
2670
		$values = array();
2671
2672
		if ($keyName !== null) $this->env->currentProperty = $keyName;
2673
2674
		$s = null;
2675
		while ($this->expressionList($v)) {
2676
			$values[] = $v;
2677
			$s = $this->seek();
2678
			if (!$this->literal(',')) break;
2679
		}
2680
2681
		if ($s) $this->seek($s);
2682
2683
		if ($keyName !== null) unset($this->env->currentProperty);
2684
2685
		if (count($values) == 0) return false;
2686
2687
		$value = lessc::compressList($values, ', ');
2688
		return true;
2689
	}
2690
2691
	protected function parenValue(&$out) {
2692
		$s = $this->seek();
2693
2694
		// speed shortcut
2695 View Code Duplication
		if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "(") {
2696
			return false;
2697
		}
2698
2699
		$inParens = $this->inParens;
2700 View Code Duplication
		if ($this->literal("(") &&
2701
			($this->inParens = true) && $this->expression($exp) &&
2702
			$this->literal(")"))
2703
		{
2704
			$out = $exp;
2705
			$this->inParens = $inParens;
2706
			return true;
2707
		} else {
2708
			$this->inParens = $inParens;
2709
			$this->seek($s);
2710
		}
2711
2712
		return false;
2713
	}
2714
2715
	// a single value
2716
	protected function value(&$value) {
2717
		$s = $this->seek();
2718
2719
		// speed shortcut
2720
		if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "-") {
2721
			// negation
2722
			if ($this->literal("-", false) &&
2723
				(($this->variable($inner) && $inner = array("variable", $inner)) ||
2724
				$this->unit($inner) ||
2725
				$this->parenValue($inner)))
2726
			{
2727
				$value = array("unary", "-", $inner);
2728
				return true;
2729
			} else {
2730
				$this->seek($s);
2731
			}
2732
		}
2733
2734
		if ($this->parenValue($value)) return true;
2735
		if ($this->unit($value)) return true;
2736
		if ($this->color($value)) return true;
2737
		if ($this->func($value)) return true;
2738
		if ($this->string($value)) return true;
2739
2740
		if ($this->keyword($word)) {
2741
			$value = array('keyword', $word);
2742
			return true;
2743
		}
2744
2745
		// try a variable
2746
		if ($this->variable($var)) {
2747
			$value = array('variable', $var);
2748
			return true;
2749
		}
2750
2751
		// unquote string (should this work on any type?
2752
		if ($this->literal("~") && $this->string($str)) {
2753
			$value = array("escape", $str);
2754
			return true;
2755
		} else {
2756
			$this->seek($s);
2757
		}
2758
2759
		// css hack: \0
2760
		if ($this->literal('\\') && $this->match('([0-9]+)', $m)) {
2761
			$value = array('keyword', '\\'.$m[1]);
2762
			return true;
2763
		} else {
2764
			$this->seek($s);
2765
		}
2766
2767
		return false;
2768
	}
2769
2770
	// an import statement
2771
	protected function import(&$out) {
2772
		if (!$this->literal('@import')) return false;
2773
2774
		// @import "something.css" media;
2775
		// @import url("something.css") media;
2776
		// @import url(something.css) media;
2777
2778
		if ($this->propertyValue($value)) {
2779
			$out = array("import", $value);
2780
			return true;
2781
		}
2782
	}
2783
2784
	protected function mediaQueryList(&$out) {
2785
		if ($this->genericList($list, "mediaQuery", ",", false)) {
2786
			$out = $list[2];
2787
			return true;
2788
		}
2789
		return false;
2790
	}
2791
2792
	protected function mediaQuery(&$out) {
2793
		$s = $this->seek();
2794
2795
		$expressions = null;
2796
		$parts = array();
2797
2798
		if (($this->literal("only") && ($only = true) || $this->literal("not") && ($not = true) || true) && $this->keyword($mediaType)) {
2799
			$prop = array("mediaType");
2800
			if (isset($only)) $prop[] = "only";
2801
			if (isset($not)) $prop[] = "not";
2802
			$prop[] = $mediaType;
2803
			$parts[] = $prop;
2804
		} else {
2805
			$this->seek($s);
2806
		}
2807
2808
2809 View Code Duplication
		if (!empty($mediaType) && !$this->literal("and")) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
2810
			// ~
2811
		} else {
2812
			$this->genericList($expressions, "mediaExpression", "and", false);
2813
			if (is_array($expressions)) $parts = array_merge($parts, $expressions[2]);
2814
		}
2815
2816
		if (count($parts) == 0) {
2817
			$this->seek($s);
2818
			return false;
2819
		}
2820
2821
		$out = $parts;
2822
		return true;
2823
	}
2824
2825
	protected function mediaExpression(&$out) {
2826
		$s = $this->seek();
2827
		$value = null;
2828
		if ($this->literal("(") &&
2829
			$this->keyword($feature) &&
2830
			($this->literal(":") && $this->expression($value) || true) &&
2831
			$this->literal(")"))
2832
		{
2833
			$out = array("mediaExp", $feature);
2834
			if ($value) $out[] = $value;
2835
			return true;
2836
		} elseif ($this->variable($variable)) {
2837
			$out = array('variable', $variable);
2838
			return true;
2839
		}
2840
2841
		$this->seek($s);
2842
		return false;
2843
	}
2844
2845
	// an unbounded string stopped by $end
2846
	protected function openString($end, &$out, $nestingOpen=null, $rejectStrs = null) {
2847
		$oldWhite = $this->eatWhiteDefault;
2848
		$this->eatWhiteDefault = false;
2849
2850
		$stop = array("'", '"', "@{", $end);
2851
		$stop = array_map(array("lessc", "preg_quote"), $stop);
2852
		// $stop[] = self::$commentMulti;
2853
2854
		if (!is_null($rejectStrs)) {
2855
			$stop = array_merge($stop, $rejectStrs);
2856
		}
2857
2858
		$patt = '(.*?)('.implode("|", $stop).')';
2859
2860
		$nestingLevel = 0;
2861
2862
		$content = array();
2863
		while ($this->match($patt, $m, false)) {
2864
			if (!empty($m[1])) {
2865
				$content[] = $m[1];
2866
				if ($nestingOpen) {
2867
					$nestingLevel += substr_count($m[1], $nestingOpen);
2868
				}
2869
			}
2870
2871
			$tok = $m[2];
2872
2873
			$this->count-= strlen($tok);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 0 spaces

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

$a = "a";
$ab = "ab";
$abc = "abc";

will have no issues, while

$a   = "a";
$ab  = "ab";
$abc = "abc";

will report issues in lines 1 and 2.

Loading history...
2874
			if ($tok == $end) {
2875
				if ($nestingLevel == 0) {
2876
					break;
2877
				} else {
2878
					$nestingLevel--;
2879
				}
2880
			}
2881
2882 View Code Duplication
			if (($tok == "'" || $tok == '"') && $this->string($str)) {
2883
				$content[] = $str;
2884
				continue;
2885
			}
2886
2887
			if ($tok == "@{" && $this->interpolation($inter)) {
2888
				$content[] = $inter;
2889
				continue;
2890
			}
2891
2892
			if (!empty($rejectStrs) && in_array($tok, $rejectStrs)) {
2893
				break;
2894
			}
2895
2896
			$content[] = $tok;
2897
			$this->count+= strlen($tok);
2898
		}
2899
2900
		$this->eatWhiteDefault = $oldWhite;
2901
2902
		if (count($content) == 0) return false;
2903
2904
		// trim the end
2905 View Code Duplication
		if (is_string(end($content))) {
2906
			$content[count($content) - 1] = rtrim(end($content));
2907
		}
2908
2909
		$out = array("string", "", $content);
2910
		return true;
2911
	}
2912
2913
	protected function string(&$out) {
2914
		$s = $this->seek();
2915 View Code Duplication
		if ($this->literal('"', false)) {
2916
			$delim = '"';
2917
		} elseif ($this->literal("'", false)) {
2918
			$delim = "'";
2919
		} else {
2920
			return false;
2921
		}
2922
2923
		$content = array();
2924
2925
		// look for either ending delim , escape, or string interpolation
2926
		$patt = '([^\n]*?)(@\{|\\\\|' .
2927
			lessc::preg_quote($delim).')';
2928
2929
		$oldWhite = $this->eatWhiteDefault;
2930
		$this->eatWhiteDefault = false;
2931
2932 View Code Duplication
		while ($this->match($patt, $m, false)) {
2933
			$content[] = $m[1];
2934
			if ($m[2] == "@{") {
2935
				$this->count -= strlen($m[2]);
2936
				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...
2937
					$content[] = $inter;
2938
				} else {
2939
					$this->count += strlen($m[2]);
2940
					$content[] = "@{"; // ignore it
2941
				}
2942
			} elseif ($m[2] == '\\') {
2943
				$content[] = $m[2];
2944
				if ($this->literal($delim, false)) {
2945
					$content[] = $delim;
2946
				}
2947
			} else {
2948
				$this->count -= strlen($delim);
2949
				break; // delim
2950
			}
2951
		}
2952
2953
		$this->eatWhiteDefault = $oldWhite;
2954
2955 View Code Duplication
		if ($this->literal($delim)) {
2956
			$out = array("string", $delim, $content);
2957
			return true;
2958
		}
2959
2960
		$this->seek($s);
2961
		return false;
2962
	}
2963
2964
	protected function interpolation(&$out) {
2965
		$oldWhite = $this->eatWhiteDefault;
2966
		$this->eatWhiteDefault = true;
2967
2968
		$s = $this->seek();
2969
		if ($this->literal("@{") &&
2970
			$this->openString("}", $interp, null, array("'", '"', ";")) &&
2971
			$this->literal("}", false))
2972
		{
2973
			$out = array("interpolate", $interp);
2974
			$this->eatWhiteDefault = $oldWhite;
2975
			if ($this->eatWhiteDefault) $this->whitespace();
2976
			return true;
2977
		}
2978
2979
		$this->eatWhiteDefault = $oldWhite;
2980
		$this->seek($s);
2981
		return false;
2982
	}
2983
2984
	protected function unit(&$unit) {
2985
		// speed shortcut
2986
		if (isset($this->buffer[$this->count])) {
2987
			$char = $this->buffer[$this->count];
2988
			if (!ctype_digit($char) && $char != ".") return false;
2989
		}
2990
2991 View Code Duplication
		if ($this->match('([0-9]+(?:\.[0-9]*)?|\.[0-9]+)([%a-zA-Z]+)?', $m)) {
2992
			$unit = array("number", $m[1], empty($m[2]) ? "" : $m[2]);
2993
			return true;
2994
		}
2995
		return false;
2996
	}
2997
2998
	// a # color
2999
	protected function color(&$out) {
3000
		if ($this->match('(#(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3}))', $m)) {
3001
			if (strlen($m[1]) > 7) {
3002
				$out = array("string", "", array($m[1]));
3003
			} else {
3004
				$out = array("raw_color", $m[1]);
3005
			}
3006
			return true;
3007
		}
3008
3009
		return false;
3010
	}
3011
3012
	// consume an argument definition list surrounded by ()
3013
	// each argument is a variable name with optional value
3014
	// or at the end a ... or a variable named followed by ...
3015
	// arguments are separated by , unless a ; is in the list, then ; is the
3016
	// delimiter.
3017
	protected function argumentDef(&$args, &$isVararg) {
3018
		$s = $this->seek();
3019
		if (!$this->literal('(')) return false;
3020
3021
		$values = array();
3022
		$delim = ",";
3023
		$method = "expressionList";
3024
3025
		$isVararg = false;
3026
		while (true) {
3027
			if ($this->literal("...")) {
3028
				$isVararg = true;
3029
				break;
3030
			}
3031
3032
			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...
3033
				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...
3034
					$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...
3035
					$ss = $this->seek();
3036
3037
					if ($this->assign() && $this->$method($rhs)) {
3038
						$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...
3039
					} else {
3040
						$this->seek($ss);
3041
						if ($this->literal("...")) {
3042
							$arg[0] = "rest";
3043
							$isVararg = true;
3044
						}
3045
					}
3046
3047
					$values[] = $arg;
3048
					if ($isVararg) break;
3049
					continue;
3050
				} else {
3051
					$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...
3052
				}
3053
			}
3054
3055
3056
			if (!$this->literal($delim)) {
3057
				if ($delim == "," && $this->literal(";")) {
3058
					// found new delim, convert existing args
3059
					$delim = ";";
3060
					$method = "propertyValue";
3061
3062
					// transform arg list
3063
					if (isset($values[1])) { // 2 items
3064
						$newList = array();
3065
						foreach ($values as $i => $arg) {
3066
							switch($arg[0]) {
3067
							case "arg":
3068
								if ($i) {
3069
									$this->throwError("Cannot mix ; and , as delimiter types");
3070
								}
3071
								$newList[] = $arg[2];
3072
								break;
3073
							case "lit":
3074
								$newList[] = $arg[1];
3075
								break;
3076
							case "rest":
3077
								$this->throwError("Unexpected rest before semicolon");
3078
							}
3079
						}
3080
3081
						$newList = array("list", ", ", $newList);
3082
3083
						switch ($values[0][0]) {
3084
						case "arg":
3085
							$newArg = array("arg", $values[0][1], $newList);
3086
							break;
3087
						case "lit":
3088
							$newArg = array("lit", $newList);
3089
							break;
3090
						}
3091
3092
					} 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...
3093
						$newArg = $values[0];
3094
					}
3095
3096
					if ($newArg) {
3097
						$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...
3098
					}
3099
				} else {
3100
					break;
3101
				}
3102
			}
3103
		}
3104
3105
		if (!$this->literal(')')) {
3106
			$this->seek($s);
3107
			return false;
3108
		}
3109
3110
		$args = $values;
3111
3112
		return true;
3113
	}
3114
3115
	// consume a list of tags
3116
	// this accepts a hanging delimiter
3117 View Code Duplication
	protected function tags(&$tags, $simple = false, $delim = ',') {
3118
		$tags = array();
3119
		while ($this->tag($tt, $simple)) {
3120
			$tags[] = $tt;
3121
			if (!$this->literal($delim)) break;
3122
		}
3123
		if (count($tags) == 0) return false;
3124
3125
		return true;
3126
	}
3127
3128
	// list of tags of specifying mixin path
3129
	// optionally separated by > (lazy, accepts extra >)
3130 View Code Duplication
	protected function mixinTags(&$tags) {
3131
		$tags = array();
3132
		while ($this->tag($tt, true)) {
3133
			$tags[] = $tt;
3134
			$this->literal(">");
3135
		}
3136
3137
		if (count($tags) == 0) return false;
3138
3139
		return true;
3140
	}
3141
3142
	// a bracketed value (contained within in a tag definition)
3143
	protected function tagBracket(&$parts, &$hasExpression) {
3144
		// speed shortcut
3145 View Code Duplication
		if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "[") {
3146
			return false;
3147
		}
3148
3149
		$s = $this->seek();
3150
3151
		$hasInterpolation = false;
3152
3153
		if ($this->literal("[", false)) {
3154
			$attrParts = array("[");
3155
			// keyword, string, operator
3156
			while (true) {
3157
				if ($this->literal("]", false)) {
3158
					$this->count--;
3159
					break; // get out early
3160
				}
3161
3162
				if ($this->match('\s+', $m)) {
3163
					$attrParts[] = " ";
3164
					continue;
3165
				}
3166
				if ($this->string($str)) {
3167
					// escape parent selector, (yuck)
3168
					foreach ($str[2] as &$chunk) {
3169
						$chunk = str_replace($this->lessc->parentSelector, "$&$", $chunk);
3170
					}
3171
3172
					$attrParts[] = $str;
3173
					$hasInterpolation = true;
3174
					continue;
3175
				}
3176
3177
				if ($this->keyword($word)) {
3178
					$attrParts[] = $word;
3179
					continue;
3180
				}
3181
3182
				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...
3183
					$attrParts[] = $inter;
3184
					$hasInterpolation = true;
3185
					continue;
3186
				}
3187
3188
				// operator, handles attr namespace too
3189
				if ($this->match('[|-~\$\*\^=]+', $m)) {
3190
					$attrParts[] = $m[0];
3191
					continue;
3192
				}
3193
3194
				break;
3195
			}
3196
3197
			if ($this->literal("]", false)) {
3198
				$attrParts[] = "]";
3199
				foreach ($attrParts as $part) {
3200
					$parts[] = $part;
3201
				}
3202
				$hasExpression = $hasExpression || $hasInterpolation;
3203
				return true;
3204
			}
3205
			$this->seek($s);
3206
		}
3207
3208
		$this->seek($s);
3209
		return false;
3210
	}
3211
3212
	// a space separated list of selectors
3213
	protected function tag(&$tag, $simple = false) {
3214
		if ($simple)
3215
			$chars = '^@,:;{}\][>\(\) "\'';
3216
		else
3217
			$chars = '^@,;{}["\'';
3218
3219
		$s = $this->seek();
3220
3221
		$hasExpression = false;
3222
		$parts = array();
3223
		while ($this->tagBracket($parts, $hasExpression));
3224
3225
		$oldWhite = $this->eatWhiteDefault;
3226
		$this->eatWhiteDefault = false;
3227
3228
		while (true) {
3229
			if ($this->match('(['.$chars.'0-9]['.$chars.']*)', $m)) {
3230
				$parts[] = $m[1];
3231
				if ($simple) break;
3232
3233
				while ($this->tagBracket($parts, $hasExpression));
3234
				continue;
3235
			}
3236
3237
			if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "@") {
3238
				if ($this->interpolation($interp)) {
3239
					$hasExpression = true;
3240
					$interp[2] = true; // don't unescape
3241
					$parts[] = $interp;
3242
					continue;
3243
				}
3244
3245
				if ($this->literal("@")) {
3246
					$parts[] = "@";
3247
					continue;
3248
				}
3249
			}
3250
3251
			if ($this->unit($unit)) { // for keyframes
3252
				$parts[] = $unit[1];
3253
				$parts[] = $unit[2];
3254
				continue;
3255
			}
3256
3257
			break;
3258
		}
3259
3260
		$this->eatWhiteDefault = $oldWhite;
3261
		if (!$parts) {
3262
			$this->seek($s);
3263
			return false;
3264
		}
3265
3266
		if ($hasExpression) {
3267
			$tag = array("exp", array("string", "", $parts));
3268
		} else {
3269
			$tag = trim(implode($parts));
3270
		}
3271
3272
		$this->whitespace();
3273
		return true;
3274
	}
3275
3276
	// a css function
3277
	protected function func(&$func) {
3278
		$s = $this->seek();
3279
3280
		if ($this->match('(%|[\w\-_][\w\-_:\.]+|[\w_])', $m) && $this->literal('(')) {
3281
			$fname = $m[1];
3282
3283
			$sPreArgs = $this->seek();
3284
3285
			$args = array();
3286
			while (true) {
3287
				$ss = $this->seek();
3288
				// this ugly nonsense is for ie filter properties
3289
				if ($this->keyword($name) && $this->literal('=') && $this->expressionList($value)) {
3290
					$args[] = array("string", "", array($name, "=", $value));
3291
				} else {
3292
					$this->seek($ss);
3293
					if ($this->expressionList($value)) {
3294
						$args[] = $value;
3295
					}
3296
				}
3297
3298
				if (!$this->literal(',')) break;
3299
			}
3300
			$args = array('list', ',', $args);
3301
3302
			if ($this->literal(')')) {
3303
				$func = array('function', $fname, $args);
3304
				return true;
3305
			} elseif ($fname == 'url') {
3306
				// couldn't parse and in url? treat as string
3307
				$this->seek($sPreArgs);
3308
				if ($this->openString(")", $string) && $this->literal(")")) {
3309
					$func = array('function', $fname, $string);
3310
					return true;
3311
				}
3312
			}
3313
		}
3314
3315
		$this->seek($s);
3316
		return false;
3317
	}
3318
3319
	// consume a less variable
3320
	protected function variable(&$name) {
3321
		$s = $this->seek();
3322
		if ($this->literal($this->lessc->vPrefix, false) &&
3323
			($this->variable($sub) || $this->keyword($name)))
3324
		{
3325
			if (!empty($sub)) {
3326
				$name = array('variable', $sub);
3327
			} else {
3328
				$name = $this->lessc->vPrefix.$name;
3329
			}
3330
			return true;
3331
		}
3332
3333
		$name = null;
3334
		$this->seek($s);
3335
		return false;
3336
	}
3337
3338
	/**
3339
	 * Consume an assignment operator
3340
	 * Can optionally take a name that will be set to the current property name
3341
	 */
3342
	protected function assign($name = null) {
3343
		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...
3344
		return $this->literal(':') || $this->literal('=');
3345
	}
3346
3347
	// consume a keyword
3348 View Code Duplication
	protected function keyword(&$word) {
3349
		if ($this->match('([\w_\-\*!"][\w\-_"]*)', $m)) {
3350
			$word = $m[1];
3351
			return true;
3352
		}
3353
		return false;
3354
	}
3355
3356
	// consume an end of statement delimiter
3357 View Code Duplication
	protected function end() {
3358
		if ($this->literal(';', false)) {
3359
			return true;
3360
		} elseif ($this->count == strlen($this->buffer) || $this->buffer[$this->count] == '}') {
3361
			// if there is end of file or a closing block next then we don't need a ;
3362
			return true;
3363
		}
3364
		return false;
3365
	}
3366
3367
	protected function guards(&$guards) {
3368
		$s = $this->seek();
3369
3370
		if (!$this->literal("when")) {
3371
			$this->seek($s);
3372
			return false;
3373
		}
3374
3375
		$guards = array();
3376
3377
		while ($this->guardGroup($g)) {
3378
			$guards[] = $g;
3379
			if (!$this->literal(",")) break;
3380
		}
3381
3382
		if (count($guards) == 0) {
3383
			$guards = null;
3384
			$this->seek($s);
3385
			return false;
3386
		}
3387
3388
		return true;
3389
	}
3390
3391
	// a bunch of guards that are and'd together
3392
	// TODO rename to guardGroup
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
3393 View Code Duplication
	protected function guardGroup(&$guardGroup) {
3394
		$s = $this->seek();
3395
		$guardGroup = array();
3396
		while ($this->guard($guard)) {
3397
			$guardGroup[] = $guard;
3398
			if (!$this->literal("and")) break;
3399
		}
3400
3401
		if (count($guardGroup) == 0) {
3402
			$guardGroup = null;
3403
			$this->seek($s);
3404
			return false;
3405
		}
3406
3407
		return true;
3408
	}
3409
3410
	protected function guard(&$guard) {
3411
		$s = $this->seek();
3412
		$negate = $this->literal("not");
3413
3414
		if ($this->literal("(") && $this->expression($exp) && $this->literal(")")) {
3415
			$guard = $exp;
3416
			if ($negate) $guard = array("negate", $guard);
3417
			return true;
3418
		}
3419
3420
		$this->seek($s);
3421
		return false;
3422
	}
3423
3424
	/* raw parsing functions */
3425
3426
	protected function literal($what, $eatWhitespace = null) {
3427
		if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault;
3428
3429
		// shortcut on single letter
3430 View Code Duplication
		if (!isset($what[1]) && isset($this->buffer[$this->count])) {
3431
			if ($this->buffer[$this->count] == $what) {
3432
				if (!$eatWhitespace) {
3433
					$this->count++;
3434
					return true;
3435
				}
3436
				// goes below...
3437
			} else {
3438
				return false;
3439
			}
3440
		}
3441
3442
		if (!isset(self::$literalCache[$what])) {
3443
			self::$literalCache[$what] = lessc::preg_quote($what);
3444
		}
3445
3446
		return $this->match(self::$literalCache[$what], $m, $eatWhitespace);
3447
	}
3448
3449 View Code Duplication
	protected function genericList(&$out, $parseItem, $delim="", $flatten=true) {
3450
		$s = $this->seek();
3451
		$items = array();
3452
		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...
3453
			$items[] = $value;
3454
			if ($delim) {
3455
				if (!$this->literal($delim)) break;
3456
			}
3457
		}
3458
3459
		if (count($items) == 0) {
3460
			$this->seek($s);
3461
			return false;
3462
		}
3463
3464
		if ($flatten && count($items) == 1) {
3465
			$out = $items[0];
3466
		} else {
3467
			$out = array("list", $delim, $items);
3468
		}
3469
3470
		return true;
3471
	}
3472
3473
3474
	// advance counter to next occurrence of $what
3475
	// $until - don't include $what in advance
3476
	// $allowNewline, if string, will be used as valid char set
3477 View Code Duplication
	protected function to($what, &$out, $until = false, $allowNewline = false) {
3478
		if (is_string($allowNewline)) {
3479
			$validChars = $allowNewline;
3480
		} else {
3481
			$validChars = $allowNewline ? "." : "[^\n]";
3482
		}
3483
		if (!$this->match('('.$validChars.'*?)'.lessc::preg_quote($what), $m, !$until)) return false;
3484
		if ($until) $this->count -= strlen($what); // give back $what
3485
		$out = $m[1];
3486
		return true;
3487
	}
3488
3489
	// try to match something on head of buffer
3490
	protected function match($regex, &$out, $eatWhitespace = null) {
3491
		if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault;
3492
3493
		$r = '/'.$regex.($eatWhitespace && !$this->writeComments ? '\s*' : '').'/Ais';
3494 View Code Duplication
		if (preg_match($r, $this->buffer, $out, null, $this->count)) {
3495
			$this->count += strlen($out[0]);
3496
			if ($eatWhitespace && $this->writeComments) $this->whitespace();
3497
			return true;
3498
		}
3499
		return false;
3500
	}
3501
3502
	// match some whitespace
3503
	protected function whitespace() {
3504
		if ($this->writeComments) {
3505
			$gotWhite = false;
3506 View Code Duplication
			while (preg_match(self::$whitePattern, $this->buffer, $m, null, $this->count)) {
3507
				if (isset($m[1]) && empty($this->seenComments[$this->count])) {
3508
					$this->append(array("comment", $m[1]));
3509
					$this->seenComments[$this->count] = true;
3510
				}
3511
				$this->count += strlen($m[0]);
3512
				$gotWhite = true;
3513
			}
3514
			return $gotWhite;
3515
		} else {
3516
			$this->match("", $m);
3517
			return strlen($m[0]) > 0;
3518
		}
3519
	}
3520
3521
	// match something without consuming it
3522 View Code Duplication
	protected function peek($regex, &$out = null, $from=null) {
3523
		if (is_null($from)) $from = $this->count;
3524
		$r = '/'.$regex.'/Ais';
3525
		$result = preg_match($r, $this->buffer, $out, null, $from);
3526
3527
		return $result;
3528
	}
3529
3530
	// seek to a spot in the buffer or return where we are on no argument
3531
	protected function seek($where = null) {
3532
		if ($where === null) return $this->count;
3533
		else $this->count = $where;
3534
		return true;
3535
	}
3536
3537
	/* misc functions */
3538
3539
	public function throwError($msg = "parse error", $count = null) {
3540
		$count = is_null($count) ? $this->count : $count;
3541
3542
		$line = $this->line +
3543
			substr_count(substr($this->buffer, 0, $count), "\n");
3544
3545 View Code Duplication
		if (!empty($this->sourceName)) {
3546
			$loc = "$this->sourceName on line $line";
3547
		} else {
3548
			$loc = "line: $line";
3549
		}
3550
3551
		// TODO this depends on $this->count
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
3552 View Code Duplication
		if ($this->peek("(.*?)(\n|$)", $m, $count)) {
3553
			throw new exception("$msg: failed at `$m[1]` $loc");
3554
		} else {
3555
			throw new exception("$msg: $loc");
3556
		}
3557
	}
3558
3559
	protected function pushBlock($selectors=null, $type=null) {
3560
		$b = new stdclass;
3561
		$b->parent = $this->env;
3562
3563
		$b->type = $type;
3564
		$b->id = self::$nextBlockId++;
3565
3566
		$b->isVararg = false; // TODO: kill me from here
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
3567
		$b->tags = $selectors;
3568
3569
		$b->props = array();
3570
		$b->children = array();
3571
3572
		$this->env = $b;
3573
		return $b;
3574
	}
3575
3576
	// push a block that doesn't multiply tags
3577
	protected function pushSpecialBlock($type) {
3578
		return $this->pushBlock(null, $type);
3579
	}
3580
3581
	// append a property to the current block
3582
	protected function append($prop, $pos = null) {
3583
		if ($pos !== null) $prop[-1] = $pos;
3584
		$this->env->props[] = $prop;
3585
	}
3586
3587
	// pop something off the stack
3588
	protected function pop() {
3589
		$old = $this->env;
3590
		$this->env = $this->env->parent;
3591
		return $old;
3592
	}
3593
3594
	// remove comments from $text
3595
	// todo: make it work for all functions, not just url
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
3596
	protected function removeComments($text) {
3597
		$look = array(
3598
			'url(', '//', '/*', '"', "'"
3599
		);
3600
3601
		$out = '';
3602
		$min = null;
3603
		while (true) {
3604
			// find the next item
3605
			foreach ($look as $token) {
3606
				$pos = strpos($text, $token);
3607
				if ($pos !== false) {
3608
					if (!isset($min) || $pos < $min[1]) $min = array($token, $pos);
3609
				}
3610
			}
3611
3612
			if (is_null($min)) break;
3613
3614
			$count = $min[1];
3615
			$skip = 0;
3616
			$newlines = 0;
3617
			switch ($min[0]) {
3618 View Code Duplication
			case 'url(':
3619
				if (preg_match('/url\(.*?\)/', $text, $m, 0, $count))
3620
					$count += strlen($m[0]) - strlen($min[0]);
3621
				break;
3622
			case '"':
3623
			case "'":
3624
				if (preg_match('/'.$min[0].'.*?(?<!\\\\)'.$min[0].'/', $text, $m, 0, $count))
3625
					$count += strlen($m[0]) - 1;
3626
				break;
3627
			case '//':
3628
				$skip = strpos($text, "\n", $count);
3629
				if ($skip === false) $skip = strlen($text) - $count;
3630
				else $skip -= $count;
3631
				break;
3632 View Code Duplication
			case '/*':
3633
				if (preg_match('/\/\*.*?\*\//s', $text, $m, 0, $count)) {
3634
					$skip = strlen($m[0]);
3635
					$newlines = substr_count($m[0], "\n");
3636
				}
3637
				break;
3638
			}
3639
3640
			if ($skip == 0) $count += strlen($min[0]);
3641
3642
			$out .= substr($text, 0, $count).str_repeat("\n", $newlines);
3643
			$text = substr($text, $count + $skip);
3644
3645
			$min = null;
3646
		}
3647
3648
		return $out.$text;
3649
	}
3650
3651
}
3652
3653
class lessc_formatter_classic {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
3654
	public $indentChar = "  ";
3655
3656
	public $break = "\n";
3657
	public $open = " {";
3658
	public $close = "}";
3659
	public $selectorSeparator = ", ";
3660
	public $assignSeparator = ":";
3661
3662
	public $openSingle = " { ";
3663
	public $closeSingle = " }";
3664
3665
	public $disableSingle = false;
3666
	public $breakSelectors = false;
3667
3668
	public $compressColors = false;
3669
3670
	public function __construct() {
3671
		$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...
3672
	}
3673
3674
	public function indentStr($n = 0) {
3675
		return str_repeat($this->indentChar, max($this->indentLevel + $n, 0));
3676
	}
3677
3678
	public function property($name, $value) {
3679
		return $name . $this->assignSeparator . $value . ";";
3680
	}
3681
3682
	protected function isEmpty($block) {
3683
		if (empty($block->lines)) {
3684
			foreach ($block->children as $child) {
3685
				if (!$this->isEmpty($child)) return false;
3686
			}
3687
3688
			return true;
3689
		}
3690
		return false;
3691
	}
3692
3693
	public function block($block) {
3694
		if ($this->isEmpty($block)) return;
3695
3696
		$inner = $pre = $this->indentStr();
3697
3698
		$isSingle = !$this->disableSingle &&
3699
			is_null($block->type) && count($block->lines) == 1;
3700
3701
		if (!empty($block->selectors)) {
3702
			$this->indentLevel++;
3703
3704
			if ($this->breakSelectors) {
3705
				$selectorSeparator = $this->selectorSeparator . $this->break . $pre;
3706
			} else {
3707
				$selectorSeparator = $this->selectorSeparator;
3708
			}
3709
3710
			echo $pre .
3711
				implode($selectorSeparator, $block->selectors);
3712
			if ($isSingle) {
3713
				echo $this->openSingle;
3714
				$inner = "";
3715
			} else {
3716
				echo $this->open . $this->break;
3717
				$inner = $this->indentStr();
3718
			}
3719
3720
		}
3721
3722 View Code Duplication
		if (!empty($block->lines)) {
3723
			$glue = $this->break.$inner;
3724
			echo $inner . implode($glue, $block->lines);
3725
			if (!$isSingle && !empty($block->children)) {
3726
				echo $this->break;
3727
			}
3728
		}
3729
3730
		foreach ($block->children as $child) {
3731
			$this->block($child);
3732
		}
3733
3734
		if (!empty($block->selectors)) {
3735
			if (!$isSingle && empty($block->children)) echo $this->break;
3736
3737
			if ($isSingle) {
3738
				echo $this->closeSingle . $this->break;
3739
			} else {
3740
				echo $pre . $this->close . $this->break;
3741
			}
3742
3743
			$this->indentLevel--;
3744
		}
3745
	}
3746
}
3747
3748
class lessc_formatter_compressed extends lessc_formatter_classic {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
3749
	public $disableSingle = true;
3750
	public $open = "{";
3751
	public $selectorSeparator = ",";
3752
	public $assignSeparator = ":";
3753
	public $break = "";
3754
	public $compressColors = true;
3755
3756
	public function indentStr($n = 0) {
3757
		return "";
3758
	}
3759
}
3760
3761
class lessc_formatter_lessjs extends lessc_formatter_classic {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
3762
	public $disableSingle = true;
3763
	public $breakSelectors = true;
3764
	public $assignSeparator = ": ";
3765
	public $selectorSeparator = ",";
3766
}
3767
3768