diff --git a/lib/vendor/scss/Base/Range.class.php b/lib/vendor/scss/Base/Range.class.php index a591d7b0..3f3a067d 100644 --- a/lib/vendor/scss/Base/Range.class.php +++ b/lib/vendor/scss/Base/Range.class.php @@ -2,7 +2,7 @@ /** * SCSSPHP * - * @copyright 2015 Leaf Corcoran + * @copyright 2017 Leaf Corcoran * * @license http://opensource.org/licenses/MIT MIT * diff --git a/lib/vendor/scss/Block.class.php b/lib/vendor/scss/Block.class.php index 6b972a1b..fc21e5a4 100644 --- a/lib/vendor/scss/Block.class.php +++ b/lib/vendor/scss/Block.class.php @@ -2,7 +2,7 @@ /** * SCSSPHP * - * @copyright 2012-2015 Leaf Corcoran + * @copyright 2012-2017 Leaf Corcoran * * @license http://opensource.org/licenses/MIT MIT * @@ -28,6 +28,11 @@ class Block */ public $parent; + /** + * @var string + */ + public $sourceName; + /** * @var integer */ diff --git a/lib/vendor/scss/Colors.class.php b/lib/vendor/scss/Colors.class.php index ff48c199..61a71ab6 100644 --- a/lib/vendor/scss/Colors.class.php +++ b/lib/vendor/scss/Colors.class.php @@ -2,7 +2,7 @@ /** * SCSSPHP * - * @copyright 2012-2015 Leaf Corcoran + * @copyright 2012-2017 Leaf Corcoran * * @license http://opensource.org/licenses/MIT MIT * diff --git a/lib/vendor/scss/Compiler.class.php b/lib/vendor/scss/Compiler.class.php index b19b9e56..f12cf7ee 100644 --- a/lib/vendor/scss/Compiler.class.php +++ b/lib/vendor/scss/Compiler.class.php @@ -2,7 +2,7 @@ /** * SCSSPHP * - * @copyright 2012-2015 Leaf Corcoran + * @copyright 2012-2017 Leaf Corcoran * * @license http://opensource.org/licenses/MIT MIT * @@ -18,6 +18,7 @@ use Leafo\ScssPhp\Compiler\Environment; use Leafo\ScssPhp\Exception\CompilerException; use Leafo\ScssPhp\Formatter\OutputBlock; use Leafo\ScssPhp\Node; +use Leafo\ScssPhp\SourceMap\SourceMapGenerator; use Leafo\ScssPhp\Type; use Leafo\ScssPhp\Parser; use Leafo\ScssPhp\Util; @@ -64,6 +65,10 @@ class Compiler const WITH_SUPPORTS = 4; const WITH_ALL = 7; + const SOURCE_MAP_NONE = 0; + const SOURCE_MAP_INLINE = 1; + const SOURCE_MAP_FILE = 2; + /** * @var array */ @@ -120,25 +125,35 @@ class Compiler protected $encoding = null; protected $lineNumberStyle = null; + protected $sourceMap = self::SOURCE_MAP_NONE; + protected $sourceMapOptions = []; + + /** + * @var string|\Leafo\ScssPhp\Formatter + */ protected $formatter = 'Leafo\ScssPhp\Formatter\Nested'; protected $rootEnv; protected $rootBlock; + /** + * @var \Leafo\ScssPhp\Compiler\Environment + */ + protected $env; + protected $scope; + protected $storeEnv; + protected $charsetSeen; + protected $sourceNames; + private $indentLevel; private $commentsSeen; private $extends; private $extendsMap; private $parsedFiles; - private $env; - private $scope; private $parser; - private $sourceNames; private $sourceIndex; private $sourceLine; private $sourceColumn; - private $storeEnv; - private $charsetSeen; private $stderr; private $shouldEvaluate; private $ignoreErrors; @@ -164,9 +179,6 @@ class Compiler */ public function compile($code, $path = null) { - $locale = setlocale(LC_NUMERIC, 0); - setlocale(LC_NUMERIC, 'C'); - $this->indentLevel = -1; $this->commentsSeen = []; $this->extends = []; @@ -193,9 +205,30 @@ class Compiler $this->compileRoot($tree); $this->popEnv(); - $out = $this->formatter->format($this->scope); + $sourceMapGenerator = null; - setlocale(LC_NUMERIC, $locale); + if ($this->sourceMap && $this->sourceMap !== self::SOURCE_MAP_NONE) { + $sourceMapGenerator = new SourceMapGenerator($this->sourceMapOptions); + } + + $out = $this->formatter->format($this->scope, $sourceMapGenerator); + + if (! empty($out) && $this->sourceMap && $this->sourceMap !== self::SOURCE_MAP_NONE) { + $sourceMap = $sourceMapGenerator->generateJson(); + $sourceMapUrl = null; + + switch ($this->sourceMap) { + case self::SOURCE_MAP_INLINE: + $sourceMapUrl = sprintf('data:application/json,%s', Util::encodeURIComponent($sourceMap)); + break; + + case self::SOURCE_MAP_FILE: + $sourceMapUrl = $sourceMapGenerator->saveMap($sourceMap); + break; + } + + $out .= sprintf('/*# sourceMappingURL=%s */', $sourceMapUrl); + } return $out; } @@ -207,7 +240,7 @@ class Compiler * * @return \Leafo\ScssPhp\Parser */ - private function parserFactory($path) + protected function parserFactory($path) { $parser = new Parser($path, count($this->sourceNames), $this->encoding); @@ -272,12 +305,15 @@ class Compiler protected function makeOutputBlock($type, $selectors = null) { $out = new OutputBlock; - $out->type = $type; - $out->lines = []; - $out->children = []; - $out->parent = $this->scope; - $out->selectors = $selectors; - $out->depth = $this->env->depth; + $out->type = $type; + $out->lines = []; + $out->children = []; + $out->parent = $this->scope; + $out->selectors = $selectors; + $out->depth = $this->env->depth; + $out->sourceName = $this->env->block->sourceName; + $out->sourceLine = $this->env->block->sourceLine; + $out->sourceColumn = $this->env->block->sourceColumn; return $out; } @@ -396,23 +432,42 @@ class Compiler } if ($this->matchExtendsSingle($part, $origin)) { - $before = array_slice($selector, 0, $i); $after = array_slice($selector, $i + 1); - $s = count($before); + $before = array_slice($selector, 0, $i); + + list($before, $nonBreakableBefore) = $this->extractRelationshipFromFragment($before); foreach ($origin as $new) { $k = 0; // remove shared parts if ($initial) { - while ($k < $s && isset($new[$k]) && $before[$k] === $new[$k]) { + while ($k < $i && isset($new[$k]) && $selector[$k] === $new[$k]) { $k++; } } + $replacement = []; + $tempReplacement = $k > 0 ? array_slice($new, $k) : $new; + + for ($l = count($tempReplacement) - 1; $l >= 0; $l--) { + $slice = $tempReplacement[$l]; + array_unshift($replacement, $slice); + + if (! $this->isImmediateRelationshipCombinator(end($slice))) { + break; + } + } + + $afterBefore = $l != 0 ? array_slice($tempReplacement, 0, $l) : []; + + // Merge shared direct relationships. + $mergedBefore = $this->mergeDirectRelationships($afterBefore, $nonBreakableBefore); + $result = array_merge( $before, - $k > 0 ? array_slice($new, $k) : $new, + $mergedBefore, + $replacement, $after ); @@ -423,14 +478,22 @@ class Compiler $out[] = $result; // recursively check for more matches - $this->matchExtends($result, $out, $i, false); + $this->matchExtends($result, $out, count($before) + count($mergedBefore), false); // selector sequence merging if (! empty($before) && count($new) > 1) { + $sharedParts = $k > 0 ? array_slice($before, 0, $k) : []; + $postSharedParts = $k > 0 ? array_slice($before, $k) : $before; + + list($injectBetweenSharedParts, $nonBreakable2) = $this->extractRelationshipFromFragment($afterBefore); + $result2 = array_merge( - array_slice($new, 0, -1), - $k > 0 ? array_slice($before, $k) : $before, - array_slice($new, -1), + $sharedParts, + $injectBetweenSharedParts, + $postSharedParts, + $nonBreakable2, + $nonBreakableBefore, + $replacement, $after ); @@ -467,6 +530,13 @@ class Compiler } } + $extendingDecoratedTag = false; + + if (count($single) > 1) { + $matches = null; + $extendingDecoratedTag = preg_match('/^[a-z0-9]+$/i', $single[0], $matches) ? $matches[0] : false; + } + foreach ($single as $part) { if (isset($this->extendsMap[$part])) { foreach ($this->extendsMap[$part] as $idx) { @@ -496,7 +566,17 @@ class Compiler return false; } - $combined = $this->combineSelectorSingle(end($new), $rem); + $replacement = end($new); + + // Extending a decorated tag with another tag is not possible. + if ($extendingDecoratedTag && $replacement[0] != $extendingDecoratedTag && + preg_match('/^[a-z0-9]+$/i', $replacement[0]) + ) { + unset($origin[$j]); + continue; + } + + $combined = $this->combineSelectorSingle($replacement, $rem); if (count(array_diff($combined, $origin[$j][count($origin[$j]) - 1]))) { $origin[$j][count($origin[$j]) - 1] = $combined; @@ -511,6 +591,39 @@ class Compiler return $found; } + + /** + * Extract a relationship from the fragment. + * + * When extracting the last portion of a selector we will be left with a + * fragment which may end with a direction relationship combinator. This + * method will extract the relationship fragment and return it along side + * the rest. + * + * @param array $fragment The selector fragment maybe ending with a direction relationship combinator. + * @return array The selector without the relationship fragment if any, the relationship fragment. + */ + protected function extractRelationshipFromFragment(array $fragment) + { + $parents = []; + $children = []; + $j = $i = count($fragment); + + for (;;) { + $children = $j != $i ? array_slice($fragment, $j, $i - $j) : []; + $parents = array_slice($fragment, 0, $j); + $slice = end($parents); + + if (empty($slice) || ! $this->isImmediateRelationshipCombinator($slice[0])) { + break; + } + + $j -= 2; + } + + return [$parents, $children]; + } + /** * Combine selector single * @@ -583,6 +696,7 @@ class Compiler if ($needsWrap) { $wrapped = new Block; + $wrapped->sourceName = $media->sourceName; $wrapped->sourceIndex = $media->sourceIndex; $wrapped->sourceLine = $media->sourceLine; $wrapped->sourceColumn = $media->sourceColumn; @@ -651,11 +765,12 @@ class Compiler { $env = $this->pushEnv($block); $envs = $this->compactEnv($env); - $without = isset($block->with) ? $this->compileWith($block->with) : self::WITH_RULE; + $without = isset($block->with) ? $this->compileWith($block->with) : static::WITH_RULE; // wrap inline selector if ($block->selector) { $wrapped = new Block; + $wrapped->sourceName = $block->sourceName; $wrapped->sourceIndex = $block->sourceIndex; $wrapped->sourceLine = $block->sourceLine; $wrapped->sourceColumn = $block->sourceColumn; @@ -712,6 +827,7 @@ class Compiler } $b = new Block; + $b->sourceName = $e->block->sourceName; $b->sourceIndex = $e->block->sourceIndex; $b->sourceLine = $e->block->sourceLine; $b->sourceColumn = $e->block->sourceColumn; @@ -776,12 +892,12 @@ class Compiler ]; // exclude selectors by default - $without = self::WITH_RULE; + $without = static::WITH_RULE; - if ($this->libMapHasKey([$with, self::$with])) { - $without = self::WITH_ALL; + if ($this->libMapHasKey([$with, static::$with])) { + $without = static::WITH_ALL; - $list = $this->coerceList($this->libMapGet([$with, self::$with])); + $list = $this->coerceList($this->libMapGet([$with, static::$with])); foreach ($list[2] as $item) { $keyword = $this->compileStringContent($this->coerceString($item)); @@ -792,10 +908,10 @@ class Compiler } } - if ($this->libMapHasKey([$with, self::$without])) { + if ($this->libMapHasKey([$with, static::$without])) { $without = 0; - $list = $this->coerceList($this->libMapGet([$with, self::$without])); + $list = $this->coerceList($this->libMapGet([$with, static::$without])); foreach ($list[2] as $item) { $keyword = $this->compileStringContent($this->coerceString($item)); @@ -842,10 +958,10 @@ class Compiler */ private function isWithout($without, Block $block) { - if ((($without & self::WITH_RULE) && isset($block->selectors)) || - (($without & self::WITH_MEDIA) && + if ((($without & static::WITH_RULE) && isset($block->selectors)) || + (($without & static::WITH_MEDIA) && isset($block->type) && $block->type === Type::T_MEDIA) || - (($without & self::WITH_SUPPORTS) && + (($without & static::WITH_SUPPORTS) && isset($block->type) && $block->type === Type::T_DIRECTIVE && isset($block->name) && $block->name === 'supports') ) { @@ -936,13 +1052,16 @@ class Compiler $line = $block->sourceLine; switch ($this->lineNumberStyle) { - case self::LINE_COMMENTS: - $annotation->lines[] = '/* line ' . $line . ', ' . $file . ' */'; + case static::LINE_COMMENTS: + $annotation->lines[] = '/* line ' . $line + . ($file ? ', ' . $file : '') + . ' */'; break; - case self::DEBUG_INFO: - $annotation->lines[] = '@media -sass-debug-info{filename{font-family:"' . $file - . '"}line{font-family:' . $line . '}}'; + case static::DEBUG_INFO: + $annotation->lines[] = '@media -sass-debug-info{' + . ($file ? 'filename{font-family:"' . $file . '"}' : '') + . 'line{font-family:' . $line . '}}'; break; } @@ -1100,7 +1219,7 @@ class Compiler /** * Compile selector to string; self(&) should have been replaced by now * - * @param array $selector + * @param string|array $selector * * @return string */ @@ -1122,7 +1241,7 @@ class Compiler /** * Compile selector part * - * @param arary $piece + * @param array $piece * * @return string */ @@ -1283,6 +1402,37 @@ class Compiler return $out; } + protected function mergeDirectRelationships($selectors1, $selectors2) + { + if (empty($selectors1) || empty($selectors2)) { + return array_merge($selectors1, $selectors2); + } + + $part1 = end($selectors1); + $part2 = end($selectors2); + + if (! $this->isImmediateRelationshipCombinator($part1[0]) || $part1 !== $part2) { + return array_merge($selectors1, $selectors2); + } + + $merged = []; + + do { + $part1 = array_pop($selectors1); + $part2 = array_pop($selectors2); + + if ($this->isImmediateRelationshipCombinator($part1[0]) && $part1 !== $part2) { + $merged = array_merge($selectors1, [$part1], $selectors2, [$part2], $merged); + break; + } + + array_unshift($merged, $part1); + array_unshift($merged, [array_pop($selectors1)[0] . array_pop($selectors2)[0]]); + } while (! empty($selectors1) && ! empty($selectors2)); + + return $merged; + } + /** * Merge media types * @@ -1460,9 +1610,9 @@ class Compiler list(, $name, $value) = $child; if ($name[0] === Type::T_VARIABLE) { - $flag = isset($child[3]) ? $child[3] : null; - $isDefault = $flag === '!default'; - $isGlobal = $flag === '!global'; + $flags = isset($child[3]) ? $child[3] : []; + $isDefault = in_array('!default', $flags); + $isGlobal = in_array('!global', $flags); if ($isGlobal) { $this->set($name[1], $this->reduce($value), false, $this->rootEnv); @@ -1471,7 +1621,7 @@ class Compiler $shouldSet = $isDefault && (($result = $this->get($name[1], false)) === null - || $result === self::$null); + || $result === static::$null); if (! $isDefault || $shouldSet) { $this->set($name[1], $this->reduce($value)); @@ -1499,7 +1649,7 @@ class Compiler if ($value[0] !== Type::T_NULL) { $value = $this->reduce($value); - if ($value[0] === Type::T_NULL || $value === self::$nullString) { + if ($value[0] === Type::T_NULL || $value === static::$nullString) { break; } } @@ -1525,7 +1675,7 @@ class Compiler case Type::T_FUNCTION: list(, $block) = $child; - $this->set(self::$namespaces[$block->type] . $block->name, $block); + $this->set(static::$namespaces[$block->type] . $block->name, $block); break; case Type::T_EXTEND: @@ -1573,7 +1723,7 @@ class Compiler list(,, $values) = $this->coerceList($item); foreach ($each->vars as $i => $var) { - $this->set($var, isset($values[$i]) ? $values[$i] : self::$null, true); + $this->set($var, isset($values[$i]) ? $values[$i] : static::$null, true); } } @@ -1622,7 +1772,7 @@ class Compiler $end = $end[1]; $d = $start < $end ? 1 : -1; - while (true) { + for (;;) { if ((! $for->until && $start - $d == $end) || ($for->until && $start == $end) ) { @@ -1682,7 +1832,7 @@ class Compiler // including a mixin list(, $name, $argValues, $content) = $child; - $mixin = $this->get(self::$namespaces['mixin'] . $name, false); + $mixin = $this->get(static::$namespaces['mixin'] . $name, false); if (! $mixin) { $this->throwError("Undefined mixin $name"); @@ -1695,10 +1845,13 @@ class Compiler $this->pushEnv(); $this->env->depth--; + $storeEnv = $this->storeEnv; + $this->storeEnv = $this->env; + if (isset($content)) { $content->scope = $callingScope; - $this->setRaw(self::$namespaces['special'] . 'content', $content, $this->env); + $this->setRaw(static::$namespaces['special'] . 'content', $content, $this->env); } if (isset($mixin->args)) { @@ -1709,15 +1862,19 @@ class Compiler $this->compileChildrenNoReturn($mixin->children, $out); + $this->storeEnv = $storeEnv; + $this->popEnv(); break; case Type::T_MIXIN_CONTENT: - $content = $this->get(self::$namespaces['special'] . 'content', false, $this->getStoreEnv()) - ?: $this->get(self::$namespaces['special'] . 'content', false, $this->env); + $content = $this->get(static::$namespaces['special'] . 'content', false, $this->getStoreEnv()) + ?: $this->get(static::$namespaces['special'] . 'content', false, $this->env); if (! $content) { - $this->throwError('Expected @content inside of mixin'); + $content = new \stdClass(); + $content->scope = new \stdClass(); + $content->children = $this->storeEnv->parent->block->children; break; } @@ -1742,7 +1899,7 @@ class Compiler $line = $this->sourceLine; $value = $this->compileValue($this->reduce($value, true)); - echo "Line $line WARN: $value\n"; + fwrite($this->stderr, "Line $line WARN: $value\n"); break; case Type::T_ERROR: @@ -1799,7 +1956,19 @@ class Compiler */ protected function isTruthy($value) { - return $value !== self::$false && $value !== self::$null; + return $value !== static::$false && $value !== static::$null; + } + + /** + * Is the value a direct relationship combinator? + * + * @param string $value + * + * @return boolean + */ + protected function isImmediateRelationshipCombinator($value) + { + return $value === '>' || $value === '+' || $value === '~'; } /** @@ -1832,7 +2001,7 @@ class Compiler * @param array $value * @param boolean $inExp * - * @return array + * @return array|\Leafo\ScssPhp\Node\Number */ protected function reduce($value, $inExp = false) { @@ -1842,7 +2011,7 @@ class Compiler case Type::T_EXPRESSION: list(, $op, $left, $right, $inParens) = $value; - $opName = isset(self::$operatorNames[$op]) ? self::$operatorNames[$op] : $op; + $opName = isset(static::$operatorNames[$op]) ? static::$operatorNames[$op] : $op; $inExp = $inExp || $this->shouldEval($left) || $this->shouldEval($right); $left = $this->reduce($left, true); @@ -1958,11 +2127,11 @@ class Compiler if ($op === 'not') { if ($inExp || $inParens) { - if ($exp === self::$false || $exp === self::$null) { - return self::$true; + if ($exp === static::$false || $exp === static::$null) { + return static::$true; } - return self::$false; + return static::$false; } $op = $op . ' '; @@ -2107,7 +2276,7 @@ class Compiler * @param array $left * @param array $right * - * @return array + * @return \Leafo\ScssPhp\Node\Number */ protected function opAddNumberNumber($left, $right) { @@ -2120,7 +2289,7 @@ class Compiler * @param array $left * @param array $right * - * @return array + * @return \Leafo\ScssPhp\Node\Number */ protected function opMulNumberNumber($left, $right) { @@ -2133,7 +2302,7 @@ class Compiler * @param array $left * @param array $right * - * @return array + * @return \Leafo\ScssPhp\Node\Number */ protected function opSubNumberNumber($left, $right) { @@ -2146,7 +2315,7 @@ class Compiler * @param array $left * @param array $right * - * @return array + * @return array|\Leafo\ScssPhp\Node\Number */ protected function opDivNumberNumber($left, $right) { @@ -2163,7 +2332,7 @@ class Compiler * @param array $left * @param array $right * - * @return array + * @return \Leafo\ScssPhp\Node\Number */ protected function opModNumberNumber($left, $right) { @@ -2216,7 +2385,7 @@ class Compiler return; } - if ($left !== self::$false) { + if ($left !== static::$false and $left !== static::$null) { return $this->reduce($right, true); } @@ -2238,7 +2407,7 @@ class Compiler return; } - if ($left !== self::$false) { + if ($left !== static::$false and $left !== static::$null) { return $left; } @@ -2449,7 +2618,7 @@ class Compiler * @param array $left * @param array $right * - * @return array + * @return \Leafo\ScssPhp\Node\Number */ protected function opCmpNumberNumber($left, $right) { @@ -2469,7 +2638,7 @@ class Compiler */ public function toBool($thing) { - return $thing ? self::$true : self::$false; + return $thing ? static::$true : static::$false; } /** @@ -2595,6 +2764,39 @@ class Compiler $reduced = $this->reduce($exp); switch ($reduced[0]) { + case Type::T_LIST: + $reduced = $this->extractInterpolation($reduced); + + if ($reduced[0] !== Type::T_LIST) { + break; + } + + list(, $delim, $items) = $reduced; + + if ($delim !== ' ') { + $delim .= ' '; + } + + $filtered = []; + + foreach ($items as $item) { + if ($item[0] === Type::T_NULL) { + continue; + } + + $temp = $this->compileValue([Type::T_KEYWORD, $item]); + if ($temp[0] === Type::T_STRING) { + $filtered[] = $this->compileStringContent($temp); + } elseif ($temp[0] === Type::T_KEYWORD) { + $filtered[] = $temp[1]; + } else { + $filtered[] = $this->compileValue($temp); + } + } + + $reduced = [Type::T_KEYWORD, implode("$delim", $filtered)]; + break; + case Type::T_STRING: $reduced = [Type::T_KEYWORD, $this->compileStringContent($reduced)]; break; @@ -2719,7 +2921,7 @@ class Compiler $newPart = []; foreach ($part as $p) { - if ($p === self::$selfSelector) { + if ($p === static::$selfSelector) { $setSelf = true; foreach ($parent as $i => $parentPart) { @@ -2937,20 +3139,28 @@ class Compiler */ public function get($name, $shouldThrow = true, Environment $env = null) { - $name = $this->normalizeName($name); + $normalizedName = $this->normalizeName($name); + $specialContentKey = static::$namespaces['special'] . 'content'; if (! isset($env)) { $env = $this->getStoreEnv(); } - $hasNamespace = $name[0] === '^' || $name[0] === '@' || $name[0] === '%'; + $nextIsRoot = false; + $hasNamespace = $normalizedName[0] === '^' || $normalizedName[0] === '@' || $normalizedName[0] === '%'; for (;;) { - if (array_key_exists($name, $env->store)) { - return $env->store[$name]; + if (array_key_exists($normalizedName, $env->store)) { + return $env->store[$normalizedName]; } if (! $hasNamespace && isset($env->marker)) { + if (! $nextIsRoot && ! empty($env->store[$specialContentKey])) { + $env = $env->store[$specialContentKey]->scope; + $nextIsRoot = true; + continue; + } + $env = $this->rootEnv; continue; } @@ -3132,6 +3342,30 @@ class Compiler $this->lineNumberStyle = $lineNumberStyle; } + /** + * Enable/disable source maps + * + * @api + * + * @param integer $sourceMap + */ + public function setSourceMap($sourceMap) + { + $this->sourceMap = $sourceMap; + } + + /** + * Set source map options + * + * @api + * + * @param array $sourceMapOptions + */ + public function setSourceMapOptions($sourceMapOptions) + { + $this->sourceMapOptions = $sourceMapOptions; + } + /** * Register function * @@ -3218,6 +3452,8 @@ class Compiler $urls = [$url, preg_replace('/[^\/]+$/', '_\0', $url)]; } + $hasExtension = preg_match('/[.]s?css$/', $url); + foreach ($this->importPaths as $dir) { if (is_string($dir)) { // check urls for normal import paths @@ -3227,7 +3463,7 @@ class Compiler . $full; if ($this->fileExists($file = $full . '.scss') || - $this->fileExists($file = $full) + ($hasExtension && $this->fileExists($file = $full)) ) { return $file; } @@ -3303,7 +3539,7 @@ class Compiler * * @throws \Exception */ - private function handleImportLoop($name) + protected function handleImportLoop($name) { for ($env = $this->env; $env; $env = $env->parent) { $file = $this->sourceNames[$env->block->sourceIndex]; @@ -3324,21 +3560,21 @@ class Compiler */ protected function fileExists($name) { - return is_file($name); + return file_exists($name) && is_file($name); } /** * Call SCSS @function * * @param string $name - * @param array $args + * @param array $argValues * @param array $returnValue * * @return boolean Returns true if returnValue is set; otherwise, false */ protected function callScssFunction($name, $argValues, &$returnValue) { - $func = $this->get(self::$namespaces['function'] . $name, false); + $func = $this->get(static::$namespaces['function'] . $name, false); if (! $func) { return false; @@ -3346,6 +3582,9 @@ class Compiler $this->pushEnv(); + $storeEnv = $this->storeEnv; + $this->storeEnv = $this->env; + // set the args if (isset($func->args)) { $this->applyArguments($func->args, $argValues); @@ -3360,9 +3599,11 @@ class Compiler $ret = $this->compileChildren($func->children, $tmp); + $this->storeEnv = $storeEnv; + $this->popEnv(); - $returnValue = ! isset($ret) ? self::$defaultValue : $ret; + $returnValue = ! isset($ret) ? static::$defaultValue : $ret; return true; } @@ -3386,7 +3627,7 @@ class Compiler list($f, $prototype) = $this->userFunctions[$name]; } elseif (($f = $this->getBuiltinFunction($name)) && is_callable($f)) { $libName = $f[1]; - $prototype = isset(self::$$libName) ? self::$$libName : null; + $prototype = isset(static::$$libName) ? static::$$libName : null; } else { return false; } @@ -3598,7 +3839,7 @@ class Compiler * * @param mixed $value * - * @return array + * @return array|\Leafo\ScssPhp\Node\Number */ private function coerceValue($value) { @@ -3611,7 +3852,7 @@ class Compiler } if ($value === null) { - $value = self::$null; + return static::$null; } if (is_numeric($value)) { @@ -3619,7 +3860,30 @@ class Compiler } if ($value === '') { - return self::$emptyString; + return static::$emptyString; + } + + if (preg_match('/^(#([0-9a-f]{6})|#([0-9a-f]{3}))$/i', $value, $m)) { + $color = [Type::T_COLOR]; + + if (isset($m[3])) { + $num = hexdec($m[3]); + + foreach ([3, 2, 1] as $i) { + $t = $num & 0xf; + $color[$i] = $t << 4 | $t; + $num >>= 4; + } + } else { + $num = hexdec($m[2]); + + foreach ([3, 2, 1] as $i) { + $color[$i] = $num & 0xff; + $num >>= 8; + } + } + + return $color; } return [Type::T_KEYWORD, $value]; @@ -3638,17 +3902,18 @@ class Compiler return $item; } - if ($item === self::$emptyList) { - return self::$emptyMap; + if ($item === static::$emptyList) { + return static::$emptyMap; } - return [Type::T_MAP, [$item], [self::$null]]; + return [Type::T_MAP, [$item], [static::$null]]; } /** * Coerce something to list * - * @param array $item + * @param array $item + * @param string $delim * * @return array */ @@ -4012,7 +4277,7 @@ class Compiler list($list, $value) = $args; if ($value[0] === Type::T_MAP) { - return self::$null; + return static::$null; } if ($list[0] === Type::T_MAP || @@ -4024,7 +4289,7 @@ class Compiler } if ($list[0] !== Type::T_LIST) { - return self::$null; + return static::$null; } $values = []; @@ -4035,7 +4300,7 @@ class Compiler $key = array_search($this->normalizeValue($value), $values); - return false === $key ? self::$null : $key + 1; + return false === $key ? static::$null : $key + 1; } protected static $libRgb = ['red', 'green', 'blue']; @@ -4605,7 +4870,7 @@ class Compiler $n += count($list[2]); } - return isset($list[2][$n]) ? $list[2][$n] : self::$defaultValue; + return isset($list[2][$n]) ? $list[2][$n] : static::$defaultValue; } protected static $libSetNth = ['list', 'n', 'value']; @@ -4643,7 +4908,7 @@ class Compiler } } - return self::$null; + return static::$null; } protected static $libMapKeys = ['map']; @@ -4794,7 +5059,7 @@ class Compiler switch ($value[0]) { case Type::T_KEYWORD: - if ($value === self::$true || $value === self::$false) { + if ($value === static::$true || $value === static::$false) { return 'bool'; } @@ -4867,7 +5132,7 @@ class Compiler $result = strpos($stringContent, $substringContent); - return $result === false ? self::$null : new Node\Number($result + 1, ''); + return $result === false ? static::$null : new Node\Number($result + 1, ''); } protected static $libStrInsert = ['string', 'insert', 'index']; @@ -4899,7 +5164,7 @@ class Compiler protected function libStrSlice($args) { if (isset($args[2]) && $args[2][1] == 0) { - return self::$nullString; + return static::$nullString; } $string = $this->coerceString($args[0]); @@ -4927,7 +5192,7 @@ class Compiler $string = $this->coerceString($args[0]); $stringContent = $this->compileStringContent($string); - $string[2] = [mb_strtolower($stringContent)]; + $string[2] = [function_exists('mb_strtolower') ? mb_strtolower($stringContent) : strtolower($stringContent)]; return $string; } @@ -4938,7 +5203,7 @@ class Compiler $string = $this->coerceString($args[0]); $stringContent = $this->compileStringContent($string); - $string[2] = [mb_strtoupper($stringContent)]; + $string[2] = [function_exists('mb_strtoupper') ? mb_strtoupper($stringContent) : strtoupper($stringContent)]; return $string; } @@ -4961,7 +5226,7 @@ class Compiler $name = $this->compileStringContent($string); // user defined functions - if ($this->has(self::$namespaces['function'] . $name)) { + if ($this->has(static::$namespaces['function'] . $name)) { return true; } @@ -4992,7 +5257,7 @@ class Compiler $string = $this->coerceString($args[0]); $name = $this->compileStringContent($string); - return $this->has(self::$namespaces['mixin'] . $name); + return $this->has(static::$namespaces['mixin'] . $name); } protected static $libVariableExists = ['name']; @@ -5008,6 +5273,8 @@ class Compiler * Workaround IE7's content counter bug. * * @param array $args + * + * @return array */ protected function libCounter($args) { @@ -5050,7 +5317,7 @@ class Compiler protected static $libInspect = ['value']; protected function libInspect($args) { - if ($args[0] === self::$null) { + if ($args[0] === static::$null) { return [Type::T_KEYWORD, 'null']; } diff --git a/lib/vendor/scss/Compiler/Environment.class.php b/lib/vendor/scss/Compiler/Environment.class.php index d44bff76..b4b86e32 100644 --- a/lib/vendor/scss/Compiler/Environment.class.php +++ b/lib/vendor/scss/Compiler/Environment.class.php @@ -2,7 +2,7 @@ /** * SCSSPHP * - * @copyright 2012-2015 Leaf Corcoran + * @copyright 2012-2017 Leaf Corcoran * * @license http://opensource.org/licenses/MIT MIT * diff --git a/lib/vendor/scss/Exception/CompilerException.class.php b/lib/vendor/scss/Exception/CompilerException.class.php index 777e40ac..45fc1671 100644 --- a/lib/vendor/scss/Exception/CompilerException.class.php +++ b/lib/vendor/scss/Exception/CompilerException.class.php @@ -2,7 +2,7 @@ /** * SCSSPHP * - * @copyright 2012-2015 Leaf Corcoran + * @copyright 2012-2017 Leaf Corcoran * * @license http://opensource.org/licenses/MIT MIT * diff --git a/lib/vendor/scss/Exception/ParserException.class.php b/lib/vendor/scss/Exception/ParserException.class.php index fbe6388f..c0ee002d 100644 --- a/lib/vendor/scss/Exception/ParserException.class.php +++ b/lib/vendor/scss/Exception/ParserException.class.php @@ -2,7 +2,7 @@ /** * SCSSPHP * - * @copyright 2012-2015 Leaf Corcoran + * @copyright 2012-2017 Leaf Corcoran * * @license http://opensource.org/licenses/MIT MIT * diff --git a/lib/vendor/scss/Exception/RangeException.class.php b/lib/vendor/scss/Exception/RangeException.class.php new file mode 100644 index 00000000..47192ff5 --- /dev/null +++ b/lib/vendor/scss/Exception/RangeException.class.php @@ -0,0 +1,21 @@ + + */ +class RangeException extends \Exception +{ +} diff --git a/lib/vendor/scss/Exception/ServerException.class.php b/lib/vendor/scss/Exception/ServerException.class.php index 5a878d2f..68d3a290 100644 --- a/lib/vendor/scss/Exception/ServerException.class.php +++ b/lib/vendor/scss/Exception/ServerException.class.php @@ -2,7 +2,7 @@ /** * SCSSPHP * - * @copyright 2012-2015 Leaf Corcoran + * @copyright 2012-2017 Leaf Corcoran * * @license http://opensource.org/licenses/MIT MIT * diff --git a/lib/vendor/scss/Formatter.class.php b/lib/vendor/scss/Formatter.class.php index 2770bb2d..4d2fdd08 100644 --- a/lib/vendor/scss/Formatter.class.php +++ b/lib/vendor/scss/Formatter.class.php @@ -2,7 +2,7 @@ /** * SCSSPHP * - * @copyright 2012-2015 Leaf Corcoran + * @copyright 2012-2017 Leaf Corcoran * * @license http://opensource.org/licenses/MIT MIT * @@ -12,6 +12,7 @@ namespace Leafo\ScssPhp; use Leafo\ScssPhp\Formatter\OutputBlock; +use Leafo\ScssPhp\SourceMap\SourceMapGenerator; /** * Base formatter @@ -56,10 +57,30 @@ abstract class Formatter public $assignSeparator; /** - * @var boolea + * @var boolean */ public $keepSemicolons; + /** + * @var \Leafo\ScssPhp\Formatter\OutputBlock + */ + protected $currentBlock; + + /** + * @var integer + */ + protected $currentLine; + + /** + * @var integer + */ + protected $currentColumn; + + /** + * @var \Leafo\ScssPhp\SourceMap\SourceMapGenerator + */ + protected $sourceMapGenerator; + /** * Initialize formatter * @@ -123,10 +144,10 @@ abstract class Formatter $glue = $this->break . $inner; - echo $inner . implode($glue, $block->lines); + $this->write($inner . implode($glue, $block->lines)); if (! empty($block->children)) { - echo $this->break; + $this->write($this->break); } } @@ -139,9 +160,9 @@ abstract class Formatter { $inner = $this->indentStr(); - echo $inner + $this->write($inner . implode($this->tagSeparator, $block->selectors) - . $this->open . $this->break; + . $this->open . $this->break); } /** @@ -167,6 +188,8 @@ abstract class Formatter return; } + $this->currentBlock = $block; + $pre = $this->indentStr(); if (! empty($block->selectors)) { @@ -187,10 +210,10 @@ abstract class Formatter $this->indentLevel--; if (empty($block->children)) { - echo $this->break; + $this->write($this->break); } - echo $pre . $this->close . $this->break; + $this->write($pre . $this->close . $this->break); } } @@ -199,12 +222,21 @@ abstract class Formatter * * @api * - * @param \Leafo\ScssPhp\Formatter\OutputBlock $block An abstract syntax tree + * @param \Leafo\ScssPhp\Formatter\OutputBlock $block An abstract syntax tree + * @param \Leafo\ScssPhp\SourceMap\SourceMapGenerator|null $sourceMapGenerator Optional source map generator * * @return string */ - public function format(OutputBlock $block) + public function format(OutputBlock $block, SourceMapGenerator $sourceMapGenerator = null) { + $this->sourceMapGenerator = null; + + if ($sourceMapGenerator) { + $this->currentLine = 1; + $this->currentColumn = 0; + $this->sourceMapGenerator = $sourceMapGenerator; + } + ob_start(); $this->block($block); @@ -213,4 +245,30 @@ abstract class Formatter return $out; } + + /** + * @param string $str + */ + protected function write($str) + { + if ($this->sourceMapGenerator) { + $this->sourceMapGenerator->addMapping( + $this->currentLine, + $this->currentColumn, + $this->currentBlock->sourceLine, + $this->currentBlock->sourceColumn - 1, //columns from parser are off by one + $this->currentBlock->sourceName + ); + + $lines = explode("\n", $str); + $lineCount = count($lines); + $this->currentLine += $lineCount-1; + + $lastLine = array_pop($lines); + + $this->currentColumn = ($lineCount === 1 ? $this->currentColumn : 0) + strlen($lastLine); + } + + echo $str; + } } diff --git a/lib/vendor/scss/Formatter/Compact.class.php b/lib/vendor/scss/Formatter/Compact.class.php index 94abe329..aaf972b7 100644 --- a/lib/vendor/scss/Formatter/Compact.class.php +++ b/lib/vendor/scss/Formatter/Compact.class.php @@ -2,7 +2,7 @@ /** * SCSSPHP * - * @copyright 2012-2015 Leaf Corcoran + * @copyright 2012-2017 Leaf Corcoran * * @license http://opensource.org/licenses/MIT MIT * diff --git a/lib/vendor/scss/Formatter/Compressed.class.php b/lib/vendor/scss/Formatter/Compressed.class.php index df7bc75f..21cbf28e 100644 --- a/lib/vendor/scss/Formatter/Compressed.class.php +++ b/lib/vendor/scss/Formatter/Compressed.class.php @@ -2,7 +2,7 @@ /** * SCSSPHP * - * @copyright 2012-2015 Leaf Corcoran + * @copyright 2012-2017 Leaf Corcoran * * @license http://opensource.org/licenses/MIT MIT * @@ -53,10 +53,10 @@ class Compressed extends Formatter } } - echo $inner . implode($glue, $block->lines); + $this->write($inner . implode($glue, $block->lines)); if (! empty($block->children)) { - echo $this->break; + $this->write($this->break); } } } diff --git a/lib/vendor/scss/Formatter/Crunched.class.php b/lib/vendor/scss/Formatter/Crunched.class.php index ccba1333..43d63833 100644 --- a/lib/vendor/scss/Formatter/Crunched.class.php +++ b/lib/vendor/scss/Formatter/Crunched.class.php @@ -2,7 +2,7 @@ /** * SCSSPHP * - * @copyright 2012-2015 Leaf Corcoran + * @copyright 2012-2017 Leaf Corcoran * * @license http://opensource.org/licenses/MIT MIT * @@ -51,10 +51,10 @@ class Crunched extends Formatter } } - echo $inner . implode($glue, $block->lines); + $this->write($inner . implode($glue, $block->lines)); if (! empty($block->children)) { - echo $this->break; + $this->write($this->break); } } } diff --git a/lib/vendor/scss/Formatter/Debug.class.php b/lib/vendor/scss/Formatter/Debug.class.php index 855742e7..b31c8c7a 100644 --- a/lib/vendor/scss/Formatter/Debug.class.php +++ b/lib/vendor/scss/Formatter/Debug.class.php @@ -2,7 +2,7 @@ /** * SCSSPHP * - * @copyright 2012-2015 Leaf Corcoran + * @copyright 2012-2017 Leaf Corcoran * * @license http://opensource.org/licenses/MIT MIT * @@ -52,13 +52,13 @@ class Debug extends Formatter $indent = $this->indentStr(); if (empty($block->lines)) { - echo "{$indent}block->lines: []\n"; + $this->write("{$indent}block->lines: []\n"); return; } foreach ($block->lines as $index => $line) { - echo "{$indent}block->lines[{$index}]: $line\n"; + $this->write("{$indent}block->lines[{$index}]: $line\n"); } } @@ -70,13 +70,13 @@ class Debug extends Formatter $indent = $this->indentStr(); if (empty($block->selectors)) { - echo "{$indent}block->selectors: []\n"; + $this->write("{$indent}block->selectors: []\n"); return; } foreach ($block->selectors as $index => $selector) { - echo "{$indent}block->selectors[{$index}]: $selector\n"; + $this->write("{$indent}block->selectors[{$index}]: $selector\n"); } } @@ -88,7 +88,7 @@ class Debug extends Formatter $indent = $this->indentStr(); if (empty($block->children)) { - echo "{$indent}block->children: []\n"; + $this->write("{$indent}block->children: []\n"); return; } @@ -109,8 +109,10 @@ class Debug extends Formatter { $indent = $this->indentStr(); - echo "{$indent}block->type: {$block->type}\n" . - "{$indent}block->depth: {$block->depth}\n"; + $this->write("{$indent}block->type: {$block->type}\n" . + "{$indent}block->depth: {$block->depth}\n"); + + $this->currentBlock = $block; $this->blockSelectors($block); $this->blockLines($block); diff --git a/lib/vendor/scss/Formatter/Expanded.class.php b/lib/vendor/scss/Formatter/Expanded.class.php index 54db742f..0449787d 100644 --- a/lib/vendor/scss/Formatter/Expanded.class.php +++ b/lib/vendor/scss/Formatter/Expanded.class.php @@ -2,7 +2,7 @@ /** * SCSSPHP * - * @copyright 2012-2015 Leaf Corcoran + * @copyright 2012-2017 Leaf Corcoran * * @license http://opensource.org/licenses/MIT MIT * @@ -59,10 +59,10 @@ class Expanded extends Formatter } } - echo $inner . implode($glue, $block->lines); + $this->write($inner . implode($glue, $block->lines)); if (empty($block->selectors) || ! empty($block->children)) { - echo $this->break; + $this->write($this->break); } } } diff --git a/lib/vendor/scss/Formatter/Nested.class.php b/lib/vendor/scss/Formatter/Nested.class.php index 9fdb4dd0..626fec13 100644 --- a/lib/vendor/scss/Formatter/Nested.class.php +++ b/lib/vendor/scss/Formatter/Nested.class.php @@ -2,7 +2,7 @@ /** * SCSSPHP * - * @copyright 2012-2015 Leaf Corcoran + * @copyright 2012-2017 Leaf Corcoran * * @license http://opensource.org/licenses/MIT MIT * @@ -66,10 +66,10 @@ class Nested extends Formatter } } - echo $inner . implode($glue, $block->lines); + $this->write($inner . implode($glue, $block->lines)); if (! empty($block->children)) { - echo $this->break; + $this->write($this->break); } } @@ -80,9 +80,9 @@ class Nested extends Formatter { $inner = $this->indentStr(); - echo $inner + $this->write($inner . implode($this->tagSeparator, $block->selectors) - . $this->open . $this->break; + . $this->open . $this->break); } /** @@ -94,13 +94,13 @@ class Nested extends Formatter $this->block($child); if ($i < count($block->children) - 1) { - echo $this->break; + $this->write($this->break); if (isset($block->children[$i + 1])) { $next = $block->children[$i + 1]; if ($next->depth === max($block->depth, 1) && $child->depth >= $next->depth) { - echo $this->break; + $this->write($this->break); } } } @@ -120,6 +120,9 @@ class Nested extends Formatter return; } + $this->currentBlock = $block; + + $this->depth = $block->depth; if (! empty($block->selectors)) { @@ -139,11 +142,11 @@ class Nested extends Formatter if (! empty($block->selectors)) { $this->indentLevel--; - echo $this->close; + $this->write($this->close); } if ($block->type === 'root') { - echo $this->break; + $this->write($this->break); } } diff --git a/lib/vendor/scss/Formatter/OutputBlock.class.php b/lib/vendor/scss/Formatter/OutputBlock.class.php index bb8d99b4..e4092175 100644 --- a/lib/vendor/scss/Formatter/OutputBlock.class.php +++ b/lib/vendor/scss/Formatter/OutputBlock.class.php @@ -2,7 +2,7 @@ /** * SCSSPHP * - * @copyright 2012-2015 Leaf Corcoran + * @copyright 2012-2017 Leaf Corcoran * * @license http://opensource.org/licenses/MIT MIT * @@ -47,4 +47,19 @@ class OutputBlock * @var \Leafo\ScssPhp\Formatter\OutputBlock */ public $parent; + + /** + * @var string + */ + public $sourceName; + + /** + * @var integer + */ + public $sourceLine; + + /** + * @var integer + */ + public $sourceColumn; } diff --git a/lib/vendor/scss/Node.class.php b/lib/vendor/scss/Node.class.php index e6ed178c..eb543c73 100644 --- a/lib/vendor/scss/Node.class.php +++ b/lib/vendor/scss/Node.class.php @@ -2,7 +2,7 @@ /** * SCSSPHP * - * @copyright 2012-2015 Leaf Corcoran + * @copyright 2012-2017 Leaf Corcoran * * @license http://opensource.org/licenses/MIT MIT * diff --git a/lib/vendor/scss/Node/Number.class.php b/lib/vendor/scss/Node/Number.class.php index a803a6ca..efa83f5b 100644 --- a/lib/vendor/scss/Node/Number.class.php +++ b/lib/vendor/scss/Node/Number.class.php @@ -2,7 +2,7 @@ /** * SCSSPHP * - * @copyright 2012-2015 Leaf Corcoran + * @copyright 2012-2017 Leaf Corcoran * * @license http://opensource.org/licenses/MIT MIT * @@ -31,7 +31,7 @@ class Number extends Node implements \ArrayAccess /** * @var integer */ - static public $precision = 5; + static public $precision = 10; /** * @see http://www.w3.org/TR/2012/WD-css3-values-20120308/ @@ -110,7 +110,7 @@ class Number extends Node implements \ArrayAccess $dimension = $this->dimension; - foreach (self::$unitTable['in'] as $unit => $conv) { + foreach (static::$unitTable['in'] as $unit => $conv) { $from = isset($this->units[$unit]) ? $this->units[$unit] : 0; $to = isset($units[$unit]) ? $units[$unit] : 0; $factor = pow($conv, $from - $to); @@ -265,7 +265,7 @@ class Number extends Node implements \ArrayAccess */ public function output(Compiler $compiler = null) { - $dimension = round($this->dimension, self::$precision); + $dimension = round($this->dimension, static::$precision); $units = array_filter($this->units, function ($unitSize) { return $unitSize; @@ -277,7 +277,7 @@ class Number extends Node implements \ArrayAccess $this->normalizeUnits($dimension, $units, 'in'); - $dimension = round($dimension, self::$precision); + $dimension = round($dimension, static::$precision); $units = array_filter($units, function ($unitSize) { return $unitSize; }); @@ -290,9 +290,10 @@ class Number extends Node implements \ArrayAccess } reset($units); - list($unit, ) = each($units); + $unit = key($units); + $dimension = number_format($dimension, static::$precision, '.', ''); - return (string) $dimension . $unit; + return (static::$precision ? rtrim(rtrim($dimension, '0'), '.') : $dimension) . $unit; } /** @@ -316,8 +317,8 @@ class Number extends Node implements \ArrayAccess $units = []; foreach ($this->units as $unit => $exp) { - if (isset(self::$unitTable[$baseUnit][$unit])) { - $factor = pow(self::$unitTable[$baseUnit][$unit], $exp); + if (isset(static::$unitTable[$baseUnit][$unit])) { + $factor = pow(static::$unitTable[$baseUnit][$unit], $exp); $unit = $baseUnit; $dimension /= $factor; diff --git a/lib/vendor/scss/Parser.class.php b/lib/vendor/scss/Parser.class.php index 7874a97c..ccac0d3f 100644 --- a/lib/vendor/scss/Parser.class.php +++ b/lib/vendor/scss/Parser.class.php @@ -2,7 +2,7 @@ /** * SCSSPHP * - * @copyright 2012-2015 Leaf Corcoran + * @copyright 2012-2017 Leaf Corcoran * * @license http://opensource.org/licenses/MIT MIT * @@ -83,17 +83,17 @@ class Parser $this->utf8 = ! $encoding || strtolower($encoding) === 'utf-8'; $this->patternModifiers = $this->utf8 ? 'Aisu' : 'Ais'; - if (empty(self::$operatorPattern)) { - self::$operatorPattern = '([*\/%+-]|[!=]\=|\>\=?|\<\=\>|\<\=?|and|or)'; + if (empty(static::$operatorPattern)) { + static::$operatorPattern = '([*\/%+-]|[!=]\=|\>\=?|\<\=\>|\<\=?|and|or)'; $commentSingle = '\/\/'; $commentMultiLeft = '\/\*'; $commentMultiRight = '\*\/'; - self::$commentPattern = $commentMultiLeft . '.*?' . $commentMultiRight; - self::$whitePattern = $this->utf8 - ? '/' . $commentSingle . '[^\n]*\s*|(' . self::$commentPattern . ')\s*|\s+/AisuS' - : '/' . $commentSingle . '[^\n]*\s*|(' . self::$commentPattern . ')\s*|\s+/AisS'; + static::$commentPattern = $commentMultiLeft . '.*?' . $commentMultiRight; + static::$whitePattern = $this->utf8 + ? '/' . $commentSingle . '[^\n]*\s*|(' . static::$commentPattern . ')\s*|\s+/AisuS' + : '/' . $commentSingle . '[^\n]*\s*|(' . static::$commentPattern . ')\s*|\s+/AisS'; } } @@ -142,11 +142,16 @@ class Parser */ public function parse($buffer) { + // strip BOM (byte order marker) + if (substr($buffer, 0, 3) === "\xef\xbb\xbf") { + $buffer = substr($buffer, 3); + } + + $this->buffer = rtrim($buffer, "\x00..\x1f"); $this->count = 0; $this->env = null; $this->inParens = false; $this->eatWhiteDefault = true; - $this->buffer = rtrim($buffer, "\x00..\x1f"); $this->saveEncoding(); $this->extractLineNumbers($buffer); @@ -558,9 +563,9 @@ class Parser list($line, $column) = $this->getSourcePosition($s); - $statement[self::SOURCE_LINE] = $line; - $statement[self::SOURCE_COLUMN] = $column; - $statement[self::SOURCE_INDEX] = $this->sourceIndex; + $statement[static::SOURCE_LINE] = $line; + $statement[static::SOURCE_COLUMN] = $column; + $statement[static::SOURCE_INDEX] = $this->sourceIndex; $this->charset = $statement; } @@ -617,8 +622,8 @@ class Parser $this->end() ) { // check for '!flag' - $assignmentFlag = $this->stripAssignmentFlag($value); - $this->append([Type::T_ASSIGN, $name, $value, $assignmentFlag], $s); + $assignmentFlags = $this->stripAssignmentFlags($value); + $this->append([Type::T_ASSIGN, $name, $value, $assignmentFlags], $s); return true; } @@ -703,6 +708,7 @@ class Parser list($line, $column) = $this->getSourcePosition($pos); $b = new Block; + $b->sourceName = $this->sourceName; $b->sourceLine = $line; $b->sourceColumn = $column; $b->sourceIndex = $this->sourceIndex; @@ -895,7 +901,7 @@ class Parser $len = strlen($what); - if (substr($this->buffer, $this->count, $len) === $what) { + if (strcasecmp(substr($this->buffer, $this->count, $len), $what) === 0) { $this->count += $len; if ($eatWhitespace) { @@ -917,7 +923,7 @@ class Parser { $gotWhite = false; - while (preg_match(self::$whitePattern, $this->buffer, $m, null, $this->count)) { + while (preg_match(static::$whitePattern, $this->buffer, $m, null, $this->count)) { if (isset($m[1]) && empty($this->commentsSeen[$this->count])) { $this->appendComment([Type::T_COMMENT, $m[1]]); @@ -954,9 +960,9 @@ class Parser if ($pos !== null) { list($line, $column) = $this->getSourcePosition($pos); - $statement[self::SOURCE_LINE] = $line; - $statement[self::SOURCE_COLUMN] = $column; - $statement[self::SOURCE_INDEX] = $this->sourceIndex; + $statement[static::SOURCE_LINE] = $line; + $statement[static::SOURCE_COLUMN] = $column; + $statement[static::SOURCE_INDEX] = $this->sourceIndex; } $this->env->children[] = $statement; @@ -1244,13 +1250,13 @@ class Parser */ protected function expHelper($lhs, $minP) { - $operators = self::$operatorPattern; + $operators = static::$operatorPattern; $ss = $this->seek(); $whiteBefore = isset($this->buffer[$this->count - 1]) && ctype_space($this->buffer[$this->count - 1]); - while ($this->match($operators, $m, false) && self::$precedence[$m[1]] >= $minP) { + while ($this->match($operators, $m, false) && static::$precedence[$m[1]] >= $minP) { $whiteAfter = isset($this->buffer[$this->count]) && ctype_space($this->buffer[$this->count]); $varAfter = isset($this->buffer[$this->count]) && @@ -1270,8 +1276,8 @@ class Parser } // peek and see if rhs belongs to next operator - if ($this->peek($operators, $next) && self::$precedence[$next[1]] > self::$precedence[$op]) { - $rhs = $this->expHelper($rhs, self::$precedence[$next[1]]); + if ($this->peek($operators, $next) && static::$precedence[$next[1]] > static::$precedence[$op]) { + $rhs = $this->expHelper($rhs, static::$precedence[$next[1]]); } $lhs = [Type::T_EXPRESSION, $op, $lhs, $rhs, $this->inParens, $whiteBefore, $whiteAfter]; @@ -1807,7 +1813,7 @@ class Parser $oldWhite = $this->eatWhiteDefault; $this->eatWhiteDefault = false; - $patt = '(.*?)([\'"]|#\{|' . $this->pregQuote($end) . '|' . self::$commentPattern . ')'; + $patt = '(.*?)([\'"]|#\{|' . $this->pregQuote($end) . '|' . static::$commentPattern . ')'; $nestingLevel = 0; @@ -1941,7 +1947,7 @@ class Parser // match comment hack if (preg_match( - self::$whitePattern, + static::$whitePattern, $this->buffer, $m, null, @@ -2287,25 +2293,29 @@ class Parser * * @param array $value * - * @return string + * @return array */ - protected function stripAssignmentFlag(&$value) + protected function stripAssignmentFlags(&$value) { - $token = &$value; + $flags = []; for ($token = &$value; $token[0] === Type::T_LIST && ($s = count($token[2])); $token = &$lastNode) { $lastNode = &$token[2][$s - 1]; - if ($lastNode[0] === Type::T_KEYWORD && in_array($lastNode[1], ['!default', '!global'])) { + while ($lastNode[0] === Type::T_KEYWORD && in_array($lastNode[1], ['!default', '!global'])) { array_pop($token[2]); + $node = end($token[2]); + $token = $this->flattenList($token); - return $lastNode[1]; + $flags[] = $lastNode[1]; + + $lastNode = $node; } } - return false; + return $flags; } /** @@ -2459,7 +2469,13 @@ class Parser */ private function saveEncoding() { - if (ini_get('mbstring.func_overload') & 2) { + if (version_compare(PHP_VERSION, '7.2.0') >= 0) { + return; + } + + $iniDirective = 'mbstring' . '.func_overload'; // deprecated in PHP 7.2 + + if (ini_get($iniDirective) & 2) { $this->encoding = mb_internal_encoding(); mb_internal_encoding('iso-8859-1'); diff --git a/lib/vendor/scss/Server.class.php b/lib/vendor/scss/Server.class.php deleted file mode 100644 index 221655ca..00000000 --- a/lib/vendor/scss/Server.class.php +++ /dev/null @@ -1,463 +0,0 @@ - - */ -class Server -{ - /** - * @var boolean - */ - private $showErrorsAsCSS; - - /** - * @var string - */ - private $dir; - - /** - * @var string - */ - private $cacheDir; - - /** - * @var \Leafo\ScssPhp\Compiler - */ - private $scss; - - /** - * Join path components - * - * @param string $left Path component, left of the directory separator - * @param string $right Path component, right of the directory separator - * - * @return string - */ - protected function join($left, $right) - { - return rtrim($left, '/\\') . DIRECTORY_SEPARATOR . ltrim($right, '/\\'); - } - - /** - * Get name of requested .scss file - * - * @return string|null - */ - protected function inputName() - { - switch (true) { - case isset($_GET['p']): - return $_GET['p']; - case isset($_SERVER['PATH_INFO']): - return $_SERVER['PATH_INFO']; - case isset($_SERVER['DOCUMENT_URI']): - return substr($_SERVER['DOCUMENT_URI'], strlen($_SERVER['SCRIPT_NAME'])); - } - } - - /** - * Get path to requested .scss file - * - * @return string - */ - protected function findInput() - { - if (($input = $this->inputName()) - && strpos($input, '..') === false - && substr($input, -5) === '.scss' - ) { - $name = $this->join($this->dir, $input); - - if (is_file($name) && is_readable($name)) { - return $name; - } - } - - return false; - } - - /** - * Get path to cached .css file - * - * @return string - */ - protected function cacheName($fname) - { - return $this->join($this->cacheDir, md5($fname) . '.css'); - } - - /** - * Get path to meta data - * - * @return string - */ - protected function metadataName($out) - { - return $out . '.meta'; - } - - /** - * Determine whether .scss file needs to be re-compiled. - * - * @param string $out Output path - * @param string $etag ETag - * - * @return boolean True if compile required. - */ - protected function needsCompile($out, &$etag) - { - if (! is_file($out)) { - return true; - } - - $mtime = filemtime($out); - - $metadataName = $this->metadataName($out); - - if (is_readable($metadataName)) { - $metadata = unserialize(file_get_contents($metadataName)); - - foreach ($metadata['imports'] as $import => $originalMtime) { - $currentMtime = filemtime($import); - - if ($currentMtime !== $originalMtime || $currentMtime > $mtime) { - return true; - } - } - - $metaVars = crc32(serialize($this->scss->getVariables())); - - if ($metaVars !== $metadata['vars']) { - return true; - } - - $etag = $metadata['etag']; - - return false; - } - - return true; - } - - /** - * Get If-Modified-Since header from client request - * - * @return string|null - */ - protected function getIfModifiedSinceHeader() - { - $modifiedSince = null; - - if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { - $modifiedSince = $_SERVER['HTTP_IF_MODIFIED_SINCE']; - - if (false !== ($semicolonPos = strpos($modifiedSince, ';'))) { - $modifiedSince = substr($modifiedSince, 0, $semicolonPos); - } - } - - return $modifiedSince; - } - - /** - * Get If-None-Match header from client request - * - * @return string|null - */ - protected function getIfNoneMatchHeader() - { - $noneMatch = null; - - if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) { - $noneMatch = $_SERVER['HTTP_IF_NONE_MATCH']; - } - - return $noneMatch; - } - - /** - * Compile .scss file - * - * @param string $in Input path (.scss) - * @param string $out Output path (.css) - * - * @return array - */ - protected function compile($in, $out) - { - $start = microtime(true); - $css = $this->scss->compile(file_get_contents($in), $in); - $elapsed = round((microtime(true) - $start), 4); - - $v = Version::VERSION; - $t = date('r'); - $css = "/* compiled by scssphp $v on $t (${elapsed}s) */\n\n" . $css; - $etag = md5($css); - - file_put_contents($out, $css); - file_put_contents( - $this->metadataName($out), - serialize([ - 'etag' => $etag, - 'imports' => $this->scss->getParsedFiles(), - 'vars' => crc32(serialize($this->scss->getVariables())), - ]) - ); - - return [$css, $etag]; - } - - /** - * Format error as a pseudo-element in CSS - * - * @param \Exception $error - * - * @return string - */ - protected function createErrorCSS(\Exception $error) - { - $message = str_replace( - ["'", "\n"], - ["\\'", "\\A"], - $error->getfile() . ":\n\n" . $error->getMessage() - ); - - return "body { display: none !important; } - html:after { - background: white; - color: black; - content: '$message'; - display: block !important; - font-family: mono; - padding: 1em; - white-space: pre; - }"; - } - - /** - * Render errors as a pseudo-element within valid CSS, displaying the errors on any - * page that includes this CSS. - * - * @param boolean $show - */ - public function showErrorsAsCSS($show = true) - { - $this->showErrorsAsCSS = $show; - } - - /** - * Compile .scss file - * - * @param string $in Input file (.scss) - * @param string $out Output file (.css) optional - * - * @return string|bool - * - * @throws \Leafo\ScssPhp\Exception\ServerException - */ - public function compileFile($in, $out = null) - { - if (! is_readable($in)) { - throw new ServerException('load error: failed to find ' . $in); - } - - $pi = pathinfo($in); - - $this->scss->addImportPath($pi['dirname'] . '/'); - - $compiled = $this->scss->compile(file_get_contents($in), $in); - - if ($out !== null) { - return file_put_contents($out, $compiled); - } - - return $compiled; - } - - /** - * Check if file need compiling - * - * @param string $in Input file (.scss) - * @param string $out Output file (.css) - * - * @return bool - */ - public function checkedCompile($in, $out) - { - if (! is_file($out) || filemtime($in) > filemtime($out)) { - $this->compileFile($in, $out); - - return true; - } - - return false; - } - - /** - * Compile requested scss and serve css. Outputs HTTP response. - * - * @param string $salt Prefix a string to the filename for creating the cache name hash - */ - public function serve($salt = '') - { - $protocol = isset($_SERVER['SERVER_PROTOCOL']) - ? $_SERVER['SERVER_PROTOCOL'] - : 'HTTP/1.0'; - - if ($input = $this->findInput()) { - $output = $this->cacheName($salt . $input); - $etag = $noneMatch = trim($this->getIfNoneMatchHeader(), '"'); - - if ($this->needsCompile($output, $etag)) { - try { - list($css, $etag) = $this->compile($input, $output); - - $lastModified = gmdate('D, d M Y H:i:s', filemtime($output)) . ' GMT'; - - header('Last-Modified: ' . $lastModified); - header('Content-type: text/css'); - header('ETag: "' . $etag . '"'); - - echo $css; - } catch (\Exception $e) { - if ($this->showErrorsAsCSS) { - header('Content-type: text/css'); - - echo $this->createErrorCSS($e); - } else { - header($protocol . ' 500 Internal Server Error'); - header('Content-type: text/plain'); - - echo 'Parse error: ' . $e->getMessage() . "\n"; - } - } - - return; - } - - header('X-SCSS-Cache: true'); - header('Content-type: text/css'); - header('ETag: "' . $etag . '"'); - - if ($etag === $noneMatch) { - header($protocol . ' 304 Not Modified'); - - return; - } - - $modifiedSince = $this->getIfModifiedSinceHeader(); - $mtime = filemtime($output); - - if (strtotime($modifiedSince) === $mtime) { - header($protocol . ' 304 Not Modified'); - - return; - } - - $lastModified = gmdate('D, d M Y H:i:s', $mtime) . ' GMT'; - header('Last-Modified: ' . $lastModified); - - echo file_get_contents($output); - - return; - } - - header($protocol . ' 404 Not Found'); - header('Content-type: text/plain'); - - $v = Version::VERSION; - echo "/* INPUT NOT FOUND scss $v */\n"; - } - - /** - * Based on explicit input/output files does a full change check on cache before compiling. - * - * @param string $in - * @param string $out - * @param boolean $force - * - * @return string Compiled CSS results - * - * @throws \Leafo\ScssPhp\Exception\ServerException - */ - public function checkedCachedCompile($in, $out, $force = false) - { - if (! is_file($in) || ! is_readable($in)) { - throw new ServerException('Invalid or unreadable input file specified.'); - } - - if (is_dir($out) || ! is_writable(file_exists($out) ? $out : dirname($out))) { - throw new ServerException('Invalid or unwritable output file specified.'); - } - - if ($force || $this->needsCompile($out, $etag)) { - list($css, $etag) = $this->compile($in, $out); - } else { - $css = file_get_contents($out); - } - - return $css; - } - - /** - * Constructor - * - * @param string $dir Root directory to .scss files - * @param string $cacheDir Cache directory - * @param \Leafo\ScssPhp\Compiler|null $scss SCSS compiler instance - */ - public function __construct($dir, $cacheDir = null, $scss = null) - { - $this->dir = $dir; - - if (! isset($cacheDir)) { - $cacheDir = $this->join($dir, 'scss_cache'); - } - - $this->cacheDir = $cacheDir; - - if (! is_dir($this->cacheDir)) { - mkdir($this->cacheDir, 0755, true); - } - - if (! isset($scss)) { - $scss = new Compiler(); - $scss->setImportPaths($this->dir); - } - - $this->scss = $scss; - $this->showErrorsAsCSS = false; - - if (! ini_get('date.timezone')) { - date_default_timezone_set('UTC'); - } - } - - /** - * Helper method to serve compiled scss - * - * @param string $path Root path - */ - public static function serveFrom($path) - { - $server = new self($path); - $server->serve(); - } -} diff --git a/lib/vendor/scss/SourceMap/Base64VLQEncoder.class.php b/lib/vendor/scss/SourceMap/Base64VLQEncoder.class.php new file mode 100644 index 00000000..435a7c60 --- /dev/null +++ b/lib/vendor/scss/SourceMap/Base64VLQEncoder.class.php @@ -0,0 +1,217 @@ + + * @author Nicolas FRANÇOIS + */ +class Base64VLQEncoder +{ + /** + * Shift + * + * @var integer + */ + private $shift = 5; + + /** + * Mask + * + * @var integer + */ + private $mask = 0x1F; // == (1 << shift) == 0b00011111 + + /** + * Continuation bit + * + * @var integer + */ + private $continuationBit = 0x20; // == (mask - 1 ) == 0b00100000 + + /** + * Char to integer map + * + * @var array + */ + private $charToIntMap = array( + 'A' => 0, 'B' => 1, 'C' => 2, 'D' => 3, 'E' => 4, 'F' => 5, 'G' => 6, 'H' => 7, + 'I' => 8, 'J' => 9, 'K' => 10, 'L' => 11, 'M' => 12, 'N' => 13, 'O' => 14, 'P' => 15, + 'Q' => 16, 'R' => 17, 'S' => 18, 'T' => 19, 'U' => 20, 'V' => 21, 'W' => 22, 'X' => 23, + 'Y' => 24, 'Z' => 25, 'a' => 26, 'b' => 27, 'c' => 28, 'd' => 29, 'e' => 30, 'f' => 31, + 'g' => 32, 'h' => 33, 'i' => 34, 'j' => 35, 'k' => 36, 'l' => 37, 'm' => 38, 'n' => 39, + 'o' => 40, 'p' => 41, 'q' => 42, 'r' => 43, 's' => 44, 't' => 45, 'u' => 46, 'v' => 47, + 'w' => 48, 'x' => 49, 'y' => 50, 'z' => 51, 0 => 52, 1 => 53, 2 => 54, 3 => 55, + 4 => 56, 5 => 57, 6 => 58, 7 => 59, 8 => 60, 9 => 61, '+' => 62, '/' => 63, + ); + + /** + * Integer to char map + * + * @var array + */ + private $intToCharMap = array( + 0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D', 4 => 'E', 5 => 'F', 6 => 'G', 7 => 'H', + 8 => 'I', 9 => 'J', 10 => 'K', 11 => 'L', 12 => 'M', 13 => 'N', 14 => 'O', 15 => 'P', + 16 => 'Q', 17 => 'R', 18 => 'S', 19 => 'T', 20 => 'U', 21 => 'V', 22 => 'W', 23 => 'X', + 24 => 'Y', 25 => 'Z', 26 => 'a', 27 => 'b', 28 => 'c', 29 => 'd', 30 => 'e', 31 => 'f', + 32 => 'g', 33 => 'h', 34 => 'i', 35 => 'j', 36 => 'k', 37 => 'l', 38 => 'm', 39 => 'n', + 40 => 'o', 41 => 'p', 42 => 'q', 43 => 'r', 44 => 's', 45 => 't', 46 => 'u', 47 => 'v', + 48 => 'w', 49 => 'x', 50 => 'y', 51 => 'z', 52 => '0', 53 => '1', 54 => '2', 55 => '3', + 56 => '4', 57 => '5', 58 => '6', 59 => '7', 60 => '8', 61 => '9', 62 => '+', 63 => '/', + ); + + /** + * Constructor + */ + public function __construct() + { + // I leave it here for future reference + // foreach (str_split('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/') as $i => $char) + // { + // $this->charToIntMap[$char] = $i; + // $this->intToCharMap[$i] = $char; + // } + } + + /** + * Convert from a two-complement value to a value where the sign bit is + * is placed in the least significant bit. For example, as decimals: + * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary) + * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary) + * We generate the value for 32 bit machines, hence -2147483648 becomes 1, not 4294967297, + * even on a 64 bit machine. + * + * @param string $aValue + */ + public function toVLQSigned($aValue) + { + return 0xffffffff & ($aValue < 0 ? ((-$aValue) << 1) + 1 : ($aValue << 1) + 0); + } + + /** + * Convert to a two-complement value from a value where the sign bit is + * is placed in the least significant bit. For example, as decimals: + * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1 + * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2 + * We assume that the value was generated with a 32 bit machine in mind. + * Hence + * 1 becomes -2147483648 + * even on a 64 bit machine. + * + * @param integer $aValue + */ + public function fromVLQSigned($aValue) + { + return $aValue & 1 ? $this->zeroFill(~$aValue + 2, 1) | (-1 - 0x7fffffff) : $this->zeroFill($aValue, 1); + } + + /** + * Return the base 64 VLQ encoded value. + * + * @param string $aValue The value to encode + * + * @return string The encoded value + */ + public function encode($aValue) + { + $encoded = ''; + $vlq = $this->toVLQSigned($aValue); + + do { + $digit = $vlq & $this->mask; + $vlq = $this->zeroFill($vlq, $this->shift); + + if ($vlq > 0) { + $digit |= $this->continuationBit; + } + + $encoded .= $this->base64Encode($digit); + } while ($vlq > 0); + + return $encoded; + } + + /** + * Return the value decoded from base 64 VLQ. + * + * @param string $encoded The encoded value to decode + * + * @return integer The decoded value + */ + public function decode($encoded) + { + $vlq = 0; + $i = 0; + + do { + $digit = $this->base64Decode($encoded[$i]); + $vlq |= ($digit & $this->mask) << ($i * $this->shift); + $i++; + } while ($digit & $this->continuationBit); + + return $this->fromVLQSigned($vlq); + } + + /** + * Right shift with zero fill. + * + * @param integer $a number to shift + * @param integer $b number of bits to shift + * + * @return integer + */ + public function zeroFill($a, $b) + { + return ($a >= 0) ? ($a >> $b) : ($a >> $b) & (PHP_INT_MAX >> ($b - 1)); + } + + /** + * Encode single 6-bit digit as base64. + * + * @param integer $number + * + * @return string + * + * @throws \Exception If the number is invalid + */ + public function base64Encode($number) + { + if ($number < 0 || $number > 63) { + throw new \Exception(sprintf('Invalid number "%s" given. Must be between 0 and 63.', $number)); + } + + return $this->intToCharMap[$number]; + } + + /** + * Decode single 6-bit digit from base64 + * + * @param string $char + * + * @return integer + * + * @throws \Exception If the number is invalid + */ + public function base64Decode($char) + { + if (! array_key_exists($char, $this->charToIntMap)) { + throw new \Exception(sprintf('Invalid base 64 digit "%s" given.', $char)); + } + + return $this->charToIntMap[$char]; + } +} diff --git a/lib/vendor/scss/SourceMap/SourceMapGenerator.class.php b/lib/vendor/scss/SourceMap/SourceMapGenerator.class.php new file mode 100644 index 00000000..08e06168 --- /dev/null +++ b/lib/vendor/scss/SourceMap/SourceMapGenerator.class.php @@ -0,0 +1,337 @@ + + * @author Nicolas FRANÇOIS + */ +class SourceMapGenerator +{ + /** + * What version of source map does the generator generate? + */ + const VERSION = 3; + + /** + * Array of default options + * + * @var array + */ + protected $defaultOptions = array( + // an optional source root, useful for relocating source files + // on a server or removing repeated values in the 'sources' entry. + // This value is prepended to the individual entries in the 'source' field. + 'sourceRoot' => '', + + // an optional name of the generated code that this source map is associated with. + 'sourceMapFilename' => null, + + // url of the map + 'sourceMapURL' => null, + + // absolute path to a file to write the map to + 'sourceMapWriteTo' => null, + + // output source contents? + 'outputSourceFiles' => false, + + // base path for filename normalization + 'sourceMapRootpath' => '', + + // base path for filename normalization + 'sourceMapBasepath' => '' + ); + + /** + * The base64 VLQ encoder + * + * @var \Leafo\ScssPhp\SourceMap\Base64VLQEncoder + */ + protected $encoder; + + /** + * Array of mappings + * + * @var array + */ + protected $mappings = array(); + + /** + * Array of contents map + * + * @var array + */ + protected $contentsMap = array(); + + /** + * File to content map + * + * @var array + */ + protected $sources = array(); + protected $source_keys = array(); + + /** + * @var array + */ + private $options; + + public function __construct(array $options = []) + { + $this->options = array_merge($this->defaultOptions, $options); + $this->encoder = new Base64VLQEncoder(); + } + + /** + * Adds a mapping + * + * @param integer $generatedLine The line number in generated file + * @param integer $generatedColumn The column number in generated file + * @param integer $originalLine The line number in original file + * @param integer $originalColumn The column number in original file + * @param string $sourceFile The original source file + */ + public function addMapping($generatedLine, $generatedColumn, $originalLine, $originalColumn, $sourceFile) + { + $this->mappings[] = array( + 'generated_line' => $generatedLine, + 'generated_column' => $generatedColumn, + 'original_line' => $originalLine, + 'original_column' => $originalColumn, + 'source_file' => $sourceFile + ); + + $this->sources[$sourceFile] = $sourceFile; + } + + /** + * Saves the source map to a file + * + * @param string $file The absolute path to a file + * @param string $content The content to write + * + * @throws \Leafo\ScssPhp\Exception\CompilerException If the file could not be saved + */ + public function saveMap($content) + { + $file = $this->options['sourceMapWriteTo']; + $dir = dirname($file); + + // directory does not exist + if (! is_dir($dir)) { + // FIXME: create the dir automatically? + throw new CompilerException(sprintf('The directory "%s" does not exist. Cannot save the source map.', $dir)); + } + + // FIXME: proper saving, with dir write check! + if (file_put_contents($file, $content) === false) { + throw new CompilerException(sprintf('Cannot save the source map to "%s"', $file)); + } + + return $this->options['sourceMapURL']; + } + + /** + * Generates the JSON source map + * + * @return string + * + * @see https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit# + */ + public function generateJson() + { + $sourceMap = array(); + $mappings = $this->generateMappings(); + + // File version (always the first entry in the object) and must be a positive integer. + $sourceMap['version'] = self::VERSION; + + // An optional name of the generated code that this source map is associated with. + $file = $this->options['sourceMapFilename']; + + if ($file) { + $sourceMap['file'] = $file; + } + + // An optional source root, useful for relocating source files on a server or removing repeated values in the + // 'sources' entry. This value is prepended to the individual entries in the 'source' field. + $root = $this->options['sourceRoot']; + + if ($root) { + $sourceMap['sourceRoot'] = $root; + } + + // A list of original sources used by the 'mappings' entry. + $sourceMap['sources'] = array(); + + foreach ($this->sources as $source_uri => $source_filename) { + $sourceMap['sources'][] = $this->normalizeFilename($source_filename); + } + + // A list of symbol names used by the 'mappings' entry. + $sourceMap['names'] = array(); + + // A string with the encoded mapping data. + $sourceMap['mappings'] = $mappings; + + if ($this->options['outputSourceFiles']) { + // An optional list of source content, useful when the 'source' can't be hosted. + // The contents are listed in the same order as the sources above. + // 'null' may be used if some original sources should be retrieved by name. + $sourceMap['sourcesContent'] = $this->getSourcesContent(); + } + + // less.js compat fixes + if (count($sourceMap['sources']) && empty($sourceMap['sourceRoot'])) { + unset($sourceMap['sourceRoot']); + } + + return json_encode($sourceMap); + } + + /** + * Returns the sources contents + * + * @return array|null + */ + protected function getSourcesContent() + { + if (empty($this->sources)) { + return null; + } + + $content = array(); + + foreach ($this->sources as $sourceFile) { + $content[] = file_get_contents($sourceFile); + } + + return $content; + } + + /** + * Generates the mappings string + * + * @return string + */ + public function generateMappings() + { + if (! count($this->mappings)) { + return ''; + } + + $this->source_keys = array_flip(array_keys($this->sources)); + + // group mappings by generated line number. + $groupedMap = $groupedMapEncoded = array(); + + foreach ($this->mappings as $m) { + $groupedMap[$m['generated_line']][] = $m; + } + + ksort($groupedMap); + $lastGeneratedLine = $lastOriginalIndex = $lastOriginalLine = $lastOriginalColumn = 0; + + foreach ($groupedMap as $lineNumber => $line_map) { + while (++$lastGeneratedLine < $lineNumber) { + $groupedMapEncoded[] = ';'; + } + + $lineMapEncoded = array(); + $lastGeneratedColumn = 0; + + foreach ($line_map as $m) { + $mapEncoded = $this->encoder->encode($m['generated_column'] - $lastGeneratedColumn); + $lastGeneratedColumn = $m['generated_column']; + + // find the index + if ($m['source_file']) { + $index = $this->findFileIndex($m['source_file']); + + if ($index !== false) { + $mapEncoded .= $this->encoder->encode($index - $lastOriginalIndex); + $lastOriginalIndex = $index; + // lines are stored 0-based in SourceMap spec version 3 + $mapEncoded .= $this->encoder->encode($m['original_line'] - 1 - $lastOriginalLine); + $lastOriginalLine = $m['original_line'] - 1; + $mapEncoded .= $this->encoder->encode($m['original_column'] - $lastOriginalColumn); + $lastOriginalColumn = $m['original_column']; + } + } + + $lineMapEncoded[] = $mapEncoded; + } + + $groupedMapEncoded[] = implode(',', $lineMapEncoded) . ';'; + } + + return rtrim(implode($groupedMapEncoded), ';'); + } + + /** + * Finds the index for the filename + * + * @param string $filename + * + * @return integer|false + */ + protected function findFileIndex($filename) + { + return $this->source_keys[$filename]; + } + + protected function normalizeFilename($filename) + { + $filename = $this->fixWindowsPath($filename); + $rootpath = $this->options['sourceMapRootpath']; + $basePath = $this->options['sourceMapBasepath']; + + // "Trim" the 'sourceMapBasepath' from the output filename. + if (strpos($filename, $basePath) === 0) { + $filename = substr($filename, strlen($basePath)); + } + + // Remove extra leading path separators. + if (strpos($filename, '\\') === 0 || strpos($filename, '/') === 0) { + $filename = substr($filename, 1); + } + + return $rootpath . $filename; + } + + /** + * Fix windows paths + * + * @param string $path + * @param boolean $addEndSlash + * + * @return string + */ + public function fixWindowsPath($path, $addEndSlash = false) + { + $slash = ($addEndSlash) ? '/' : ''; + + if (! empty($path)) { + $path = str_replace('\\', '/', $path); + $path = rtrim($path, '/') . $slash; + } + + return $path; + } +} diff --git a/lib/vendor/scss/Type.class.php b/lib/vendor/scss/Type.class.php index 8c3886c8..2d659e4d 100644 --- a/lib/vendor/scss/Type.class.php +++ b/lib/vendor/scss/Type.class.php @@ -2,7 +2,7 @@ /** * SCSSPHP * - * @copyright 2012-2015 Leaf Corcoran + * @copyright 2012-2017 Leaf Corcoran * * @license http://opensource.org/licenses/MIT MIT * diff --git a/lib/vendor/scss/Util.class.php b/lib/vendor/scss/Util.class.php index 9f47c1d7..d49b265c 100644 --- a/lib/vendor/scss/Util.class.php +++ b/lib/vendor/scss/Util.class.php @@ -2,7 +2,7 @@ /** * SCSSPHP * - * @copyright 2012-2015 Leaf Corcoran + * @copyright 2012-2017 Leaf Corcoran * * @license http://opensource.org/licenses/MIT MIT * @@ -12,9 +12,10 @@ namespace Leafo\ScssPhp; use Leafo\ScssPhp\Base\Range; +use Leafo\ScssPhp\Exception\RangeException; /** - * Utilties + * Utilty functions * * @author Anthon Pang */ @@ -24,14 +25,14 @@ class Util * Asserts that `value` falls within `range` (inclusive), leaving * room for slight floating-point errors. * - * @param string $name The name of the value. Used in the error message. - * @param Range $range Range of values. - * @param array $value The value to check. - * @param string $unit The unit of the value. Used in error reporting. + * @param string $name The name of the value. Used in the error message. + * @param \Leafo\ScssPhp\Base\Range $range Range of values. + * @param array $value The value to check. + * @param string $unit The unit of the value. Used in error reporting. * * @return mixed `value` adjusted to fall within range, if it was outside by a floating-point margin. * - * @throws \Exception + * @throws \Leafo\ScssPhp\Exception\RangeException */ public static function checkRange($name, Range $range, $value, $unit = '') { @@ -50,6 +51,20 @@ class Util return $range->last; } - throw new \Exception("$name {$val} must be between {$range->first} and {$range->last}$unit"); + throw new RangeException("$name {$val} must be between {$range->first} and {$range->last}$unit"); + } + + /** + * Encode URI component + * + * @param string $string + * + * @return string + */ + public static function encodeURIComponent($string) + { + $revert = array('%21' => '!', '%2A' => '*', '%27' => "'", '%28' => '(', '%29' => ')'); + + return strtr(rawurlencode($string), $revert); } } diff --git a/lib/vendor/scss/Version.class.php b/lib/vendor/scss/Version.class.php index 8dfa0e93..6d07d5ab 100644 --- a/lib/vendor/scss/Version.class.php +++ b/lib/vendor/scss/Version.class.php @@ -2,7 +2,7 @@ /** * SCSSPHP * - * @copyright 2012-2015 Leaf Corcoran + * @copyright 2012-2017 Leaf Corcoran * * @license http://opensource.org/licenses/MIT MIT * @@ -18,5 +18,5 @@ namespace Leafo\ScssPhp; */ class Version { - const VERSION = 'v0.6.3'; + const VERSION = 'v0.7.4'; }