diff --git a/src/Block.php b/src/Block.php index 2edbc8a7..41abf01a 100644 --- a/src/Block.php +++ b/src/Block.php @@ -62,6 +62,7 @@ class Block * @var array */ public $children; + /** * @var \Leafo\ScssPhp\Block */ diff --git a/src/Compiler.php b/src/Compiler.php index ad4f192f..a6014c0a 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -466,6 +466,7 @@ protected function matchExtends($selector, &$out, $from = 0, $initial = true) for ($l = count($tempReplacement) - 1; $l >= 0; $l--) { $slice = []; + foreach ($tempReplacement[$l] as $chunk) { if (!in_array($chunk, $slice)) { $slice[] = $chunk; @@ -610,7 +611,6 @@ protected function matchExtendsSingle($rawSingle, &$outOrigin) return $found; } - /** * Extract a relationship from the fragment. * @@ -620,6 +620,7 @@ protected function matchExtendsSingle($rawSingle, &$outOrigin) * 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) @@ -804,6 +805,7 @@ protected function compileAtRoot(Block $block) } $selfParent = $block->selfParent; + if (! $block->selfParent->selectors && isset($block->parent) && $block->parent && isset($block->parent->selectors) && $block->parent->selectors) { $selfParent = $block->parent; } @@ -825,8 +827,10 @@ protected function compileAtRoot(Block $block) /** * Filter at-root scope depending of with/without option - * @param $scope - * @param $without + * + * @param \Leafo\ScssPhp\Formatter\OutputBlock $scope + * @param mixed $without + * * @return mixed */ protected function filterScopeWithout($scope, $without) @@ -846,14 +850,17 @@ protected function filterScopeWithout($scope, $without) if (! $scope) { break; } + if (! $this->isWithout($without, $scope)) { $s = clone $scope; $s->children = []; $s->lines = []; $s->parent = null; + if ($s->type !== Type::T_MEDIA && $s->type !== Type::T_DIRECTIVE) { $s->selectors = []; } + $filteredScopes[] = $s; } @@ -863,15 +870,18 @@ protected function filterScopeWithout($scope, $without) $scope = null; } } + if (!count($filteredScopes)) { return $this->rootBlock; } $newScope = array_shift($filteredScopes); $newScope->parent = $this->rootBlock; + $this->rootBlock->children[] = $newScope; $p = &$newScope; + while (count($filteredScopes)) { $s = array_shift($filteredScopes); $s->parent = $p; @@ -885,8 +895,10 @@ protected function filterScopeWithout($scope, $without) /** * found missing selector from a at-root compilation in the previous scope * (if at-root is just enclosing a property, the selector is in the parent tree) - * @param $scope - * @param $previousScope + * + * @param \Leafo\ScssPhp\Formatter\OutputBlock $scope + * @param \Leafo\ScssPhp\Formatter\OutputBlock $previousScope + * * @return mixed */ protected function completeScope($scope, $previousScope) @@ -894,6 +906,7 @@ protected function completeScope($scope, $previousScope) if (! $scope->type && (! $scope->selectors || ! count($scope->selectors)) && count($scope->lines)) { $scope->selectors = $this->findScopeSelectors($previousScope, $scope->depth); } + if ($scope->children) { foreach ($scope->children as $k => $c) { $scope->children[$k] = $this->completeScope($c, $previousScope); @@ -905,8 +918,10 @@ protected function completeScope($scope, $previousScope) /** * Find a selector by the depth node in the scope - * @param $scope - * @param $depth + * + * @param \Leafo\ScssPhp\Formatter\OutputBlock $scope + * @param integer $depth + * * @return array */ protected function findScopeSelectors($scope, $depth) @@ -914,6 +929,7 @@ protected function findScopeSelectors($scope, $depth) if ($scope->depth === $depth && $scope->selectors) { return $scope->selectors; } + if ($scope->children) { foreach (array_reverse($scope->children) as $c) { if ($s = $this->findScopeSelectors($c, $depth)) { @@ -921,6 +937,7 @@ protected function findScopeSelectors($scope, $depth) } } } + return []; } @@ -1016,11 +1033,13 @@ protected function isWithout($without, $block) if (isset($block->name) && $block->name === 'supports') { return ($without & static::WITH_SUPPORTS) ? true : false; } + if (isset($block->selectors) && strpos(serialize($block->selectors), '@supports') !== false) { return ($without & static::WITH_SUPPORTS) ? true : false; } } } + if ((($without & static::WITH_RULE) && isset($block->selectors))) { return true; } @@ -1147,13 +1166,17 @@ protected function compileBlock(Block $block) if (count($block->children)) { $out->selectors = $this->multiplySelectors($env, $block->selfParent); + // propagate selfParent to the children where they still can be useful $selfParentSelectors = null; + if (isset($block->selfParent->selectors)) { $selfParentSelectors = $block->selfParent->selectors; $block->selfParent->selectors = $out->selectors; } + $this->compileChildrenNoReturn($block->children, $out, $block->selfParent); + // and revert for the following childs of the same block if ($selfParentSelectors) { $block->selfParent->selectors = $selfParentSelectors; @@ -1174,6 +1197,7 @@ protected function compileComment($block) { $out = $this->makeOutputBlock(Type::T_COMMENT); $out->lines[] = $block[1]; + $this->scope->children[] = $out; } @@ -1406,6 +1430,7 @@ protected function compileChildren($stms, OutputBlock $out) return $ret; } } + return null; } @@ -1420,14 +1445,12 @@ protected function compileChildren($stms, OutputBlock $out) */ protected function compileChildrenNoReturn($stms, OutputBlock $out, $selfParent = null) { - foreach ($stms as $stm) { if ($selfParent && isset($stm[1]) && is_object($stm[1]) && get_class($stm[1]) == 'Leafo\ScssPhp\Block') { $stm[1]->selfParent = $selfParent; $ret = $this->compileChild($stm, $out); $stm[1]->selfParent = null; - } - elseif ($selfParent && $stm[0] === TYPE::T_INCLUDE) { + } elseif ($selfParent && $stm[0] === TYPE::T_INCLUDE) { $stm['selfParent'] = $selfParent; $ret = $this->compileChild($stm, $out); unset($stm['selfParent']); @@ -1681,6 +1704,7 @@ protected function compileChild($child, OutputBlock $out) } elseif (! empty($out->sourceLine) and ! empty($out->sourceName)) { $this->sourceLine = $out->sourceLine; $this->sourceIndex = array_search($out->sourceName, $this->sourceNames); + if ($this->sourceIndex === false) { $this->sourceIndex = null; } @@ -1761,6 +1785,7 @@ protected function compileChild($child, OutputBlock $out) } $fontValue=&$value; + if ($value[0] === Type::T_LIST && $value[1]==',') { // this is the case if more than one font is given: example: "font: 400 1em/1.3 arial,helvetica" // we need to handle the first list element @@ -1992,14 +2017,16 @@ protected function compileChild($child, OutputBlock $out) // Find the parent selectors in the env to be able to know what '&' refers to in the mixin // and assign this fake parent to childs $selfParent = null; + if (isset($child['selfParent']) && isset($child['selfParent']->selectors)) { $selfParent = $child['selfParent']; - } - else { + } else { $parentSelectors = $this->multiplySelectors($this->env); + if ($parentSelectors) { $parent = new Block(); $parent->selectors = $parentSelectors; + foreach ($mixin->children as $k => $child) { if (isset($child[1]) && is_object($child[1]) && get_class($child[1]) == 'Leafo\ScssPhp\Block') { $mixin->children[$k][1]->parent = $parent; @@ -2344,6 +2371,7 @@ protected function reduce($value, $inExp = false) case Type::T_SELF: $selfSelector = $this->multiplySelectors($this->env); $selfSelector = $this->collapseSelectors($selfSelector); + return [Type::T_STRING, '', [$selfSelector]]; default: @@ -2532,6 +2560,7 @@ protected function opAdd($left, $right) return $strRight; } + return null; } @@ -3049,6 +3078,7 @@ protected function multiplySelectors(Environment $env, $selfParent = null) $parentSelectors = [[]]; $selfParentSelectors = null; + if (!is_null($selfParent) and $selfParent->selectors) { $selfParentSelectors = $this->evalSelectors($selfParent->selectors); } @@ -3064,12 +3094,15 @@ protected function multiplySelectors(Environment $env, $selfParent = null) foreach ($parentSelectors as $parent) { if ($selfParentSelectors) { $previous = null; + foreach ($selfParentSelectors as $selfParent) { // if no '&' in the selector, each call will give same result, only add once $s = $this->joinSelectors($parent, $selector, $selfParent); + if ($s !== $previous) { $selectors[serialize($s)] = $s; } + $previous = $s; } } else { @@ -3083,6 +3116,7 @@ protected function multiplySelectors(Environment $env, $selfParent = null) } $selectors = array_values($selectors); + return $selectors; } @@ -3092,6 +3126,7 @@ protected function multiplySelectors(Environment $env, $selfParent = null) * @param array $parent * @param array $child * @param array $selfParentSelectors + * @return array */ protected function joinSelectors($parent, $child, $selfParentSelectors = null) @@ -3105,9 +3140,11 @@ protected function joinSelectors($parent, $child, $selfParentSelectors = null) foreach ($part as $p) { if ($p === static::$selfSelector) { $setSelf = true; + if (is_null($selfParentSelectors)) { $selfParentSelectors = $parent; } + foreach ($selfParentSelectors as $i => $parentPart) { if ($i > 0) { $out[] = $newPart; @@ -3305,6 +3342,7 @@ protected function setExisting($name, $value, Environment $env, $valueUnreduced } $env->store[$name] = $value; + if ($valueUnreduced) { $env->storeUnreduced[$name] = $valueUnreduced; } @@ -3321,6 +3359,7 @@ protected function setExisting($name, $value, Environment $env, $valueUnreduced protected function setRaw($name, $value, Environment $env, $valueUnreduced = null) { $env->store[$name] = $value; + if ($valueUnreduced) { $env->storeUnreduced[$name] = $valueUnreduced; } @@ -3355,6 +3394,7 @@ public function get($name, $shouldThrow = true, Environment $env = null, $unredu if ($unreduced && isset($env->storeUnreduced[$normalizedName])) { return $env->storeUnreduced[$normalizedName]; } + return $env->store[$normalizedName]; } @@ -3712,6 +3752,7 @@ public function setEncoding($encoding) public function setIgnoreErrors($ignoreErrors) { $this->ignoreErrors = $ignoreErrors; + return $this; } @@ -3737,9 +3778,11 @@ public function throwError($msg) $line = $this->sourceLine; $loc = isset($this->sourceNames[$this->sourceIndex]) ? $this->sourceNames[$this->sourceIndex] . " on line $line" : "line: $line"; $msg = "$msg: $loc"; + if ($this->callStack) { $msg .= "\nCall Stack:\n"; $ncall = 0; + foreach (array_reverse($this->callStack) as $call) { $msg .= "#" . $ncall++ . " " . $call['n'] . " "; $msg .= (isset($this->sourceNames[$call[Parser::SOURCE_INDEX]]) ? $this->sourceNames[$call[Parser::SOURCE_INDEX]] : '(unknown file)'); diff --git a/src/Parser.php b/src/Parser.php index 44747643..c7ef2558 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -418,7 +418,6 @@ protected function parseChunk() $this->seek($s); - if ($this->literal('@return', 7) && ($this->valueList($retVal) || true) && $this->end()) { $this->append([Type::T_RETURN, isset($retVal) ? $retVal : [Type::T_NULL]], $s); @@ -769,9 +768,11 @@ protected function popBlock() } $this->env = $block->parent; + unset($block->parent); $comments = $block->comments; + if ($comments) { $this->env->comments = $comments; unset($block->comments); @@ -865,7 +866,6 @@ protected function matchString(&$m, $delim) */ protected function match($regex, &$out, $eatWhitespace = null) { - $r = '/' . $regex . '/' . $this->patternModifiers; if (! preg_match($r, $this->buffer, $out, null, $this->count)) { @@ -885,7 +885,6 @@ protected function match($regex, &$out, $eatWhitespace = null) return true; } - /** * Match a single string * @@ -896,7 +895,6 @@ protected function match($regex, &$out, $eatWhitespace = null) */ protected function matchChar($char, $eatWhitespace = null) { - if (! isset($this->buffer[$this->count]) || $this->buffer[$this->count] !== $char) { return false; } @@ -913,7 +911,6 @@ protected function matchChar($char, $eatWhitespace = null) return true; } - /** * Match literal string * @@ -942,7 +939,6 @@ protected function literal($what, $len, $eatWhitespace = null) return true; } - /** * Match some whitespace * @@ -1204,6 +1200,7 @@ protected function genericList(&$out, $parseItem, $delim = '', $flatten = true) $s = $this->count; $items = []; $value = null; + while ($this->$parseItem($value)) { $items[] = $value; @@ -1329,7 +1326,6 @@ protected function expHelper($lhs, $minP) */ protected function value(&$out) { - if (! isset($this->buffer[$this->count])) { return false; } @@ -1356,6 +1352,7 @@ protected function value(&$out) if ($char === 'n' && $this->literal('not', 3, false)) { if ($this->whitespace() && $this->value($inner)) { $out = [Type::T_UNARY, 'not', $inner, $this->inParens]; + return true; } @@ -1363,6 +1360,7 @@ protected function value(&$out) if ($this->parenValue($inner)) { $out = [Type::T_UNARY, 'not', $inner, $this->inParens]; + return true; } @@ -1372,18 +1370,21 @@ protected function value(&$out) // addition if ($char === '+') { $this->count++; + if ($this->value($inner)) { $out = [Type::T_UNARY, '+', $inner, $this->inParens]; return true; } + $this->count--; + return false; } - // negation if ($char === '-') { $this->count++; + if ($this->variable($inner) || $this->unit($inner) || $this->parenValue($inner)) { $out = [Type::T_UNARY, '-', $inner, $this->inParens]; return true; @@ -1419,7 +1420,6 @@ protected function value(&$out) return true; } - if ($this->unit($out)) { return true; } @@ -2194,7 +2194,6 @@ protected function selectorSingle(&$out) break; } - //self switch ($char) { case '&': @@ -2211,20 +2210,20 @@ protected function selectorSingle(&$out) continue 2; } - if ($char === '\\' && $this->match('\\\\\S', $m)) { $parts[] = $m[0]; continue; } - if ($char === '%') { $this->count++; + if ($this->placeholder($placeholder)) { $parts[] = '%'; $parts[] = $placeholder; continue; } + break; } @@ -2239,7 +2238,6 @@ protected function selectorSingle(&$out) continue; } - // a pseudo selector if ($char === ':') { if ($this->buffer[$this->count + 1] === ':') { @@ -2249,6 +2247,7 @@ protected function selectorSingle(&$out) $this->count++; $part = ':'; } + if ($this->mixedKeyword($nameParts)) { $parts[] = $part; @@ -2277,10 +2276,8 @@ protected function selectorSingle(&$out) } } - $this->seek($s); - // attribute selector if ($char === '[' && $this->matchChar('[') && @@ -2300,7 +2297,6 @@ protected function selectorSingle(&$out) $this->seek($s); - // for keyframes if ($this->unit($unit)) { $parts[] = $unit; @@ -2312,9 +2308,6 @@ protected function selectorSingle(&$out) continue; } - - - break; } diff --git a/tests/FrameworkTest.php b/tests/FrameworkTest.php index 59d86621..3be524dc 100644 --- a/tests/FrameworkTest.php +++ b/tests/FrameworkTest.php @@ -56,10 +56,11 @@ public function testFramework($frameworkVerion, $inputdirectory, $inputfiles) { $this->scss->addImportPath($inputdirectory); - $input = file_get_contents($inputdirectory.$inputfiles); + $input = file_get_contents($inputdirectory . $inputfiles); //Test if no exeption are raised for the given framwork $e = null; + try { $this->scss->compile($input, $inputfiles); } catch (Exception $e) { @@ -69,7 +70,8 @@ public function testFramework($frameworkVerion, $inputdirectory, $inputfiles) $this->assertNull($e); } - public function frameworkProvider(){ + public function frameworkProvider() + { return self::$frameworks; } } diff --git a/tests/ServerTest.php b/tests/ServerTest.php index 290d8fbf..c65f00ac 100644 --- a/tests/ServerTest.php +++ b/tests/ServerTest.php @@ -11,8 +11,6 @@ namespace Leafo\ScssPhp\Tests; -require_once __DIR__ . '/../example/Server.php'; - use Leafo\ScssPhp\Server; /** @@ -24,6 +22,8 @@ class ServerTest extends \PHPUnit_Framework_TestCase { public function testCheckedCachedCompile() { + require_once __DIR__ . '/../example/Server.php'; + if (! file_exists(__DIR__ . '/inputs/scss_cache')) { mkdir(__DIR__ . '/inputs/scss_cache', 0755); }