From 3589ae543f8a1041554c3b8f9227cdb56acc4e48 Mon Sep 17 00:00:00 2001 From: Cerdic Date: Thu, 16 May 2019 10:47:39 +0200 Subject: [PATCH 01/48] Parser improvement to parse the content of :not() :has() :where() :is() itself as subselector - supporting all the selector syntax At the moment the subselector is re-flatten after the parsing because the compiler not know how to deal with nested selector structure https://github.com/leafo/scssphp/issues/368 --- src/Parser.php | 72 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/src/Parser.php b/src/Parser.php index eb264bce..f7e9043d 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -2142,19 +2142,19 @@ protected function propertyName(&$out) * * @return boolean */ - protected function selectors(&$out) + protected function selectors(&$out, $subSelector = false) { $s = $this->count; $selectors = []; - while ($this->selector($sel)) { + while ($this->selector($sel, $subSelector)) { $selectors[] = $sel; - if (! $this->matchChar(',')) { + if (! $this->matchChar(',', true)) { break; } - while ($this->matchChar(',')) { + while ($this->matchChar(',', true)) { ; // ignore extra } } @@ -2177,23 +2177,23 @@ protected function selectors(&$out) * * @return boolean */ - protected function selector(&$out) + protected function selector(&$out, $subSelector = false) { $selector = []; for (;;) { - if ($this->match('[>+~]+', $m)) { + if ($this->match('[>+~]+', $m, true)) { $selector[] = [$m[0]]; continue; } - if ($this->selectorSingle($part)) { + if ($this->selectorSingle($part, $subSelector)) { $selector[] = $part; $this->match('\s+', $m); continue; } - if ($this->match('\/[^\/]+\/', $m)) { + if ($this->match('\/[^\/]+\/', $m, true)) { $selector[] = [$m[0]]; continue; } @@ -2220,7 +2220,7 @@ protected function selector(&$out) * * @return boolean */ - protected function selectorSingle(&$out) + protected function selectorSingle(&$out, $subSelector = false) { $oldWhite = $this->eatWhiteDefault; $this->eatWhiteDefault = false; @@ -2244,6 +2244,11 @@ protected function selectorSingle(&$out) break; } + // parsing a sub selector in () stop with the closing ) + if ($subSelector && $char === ')') { + break; + } + //self switch ($char) { case '&': @@ -2307,21 +2312,46 @@ protected function selectorSingle(&$out) $ss = $this->count; - if ($this->matchChar('(') && - ($this->openString(')', $str, '(') || true) && - $this->matchChar(')') - ) { - $parts[] = '('; - - if (! empty($str)) { - $parts[] = $str; + if ($nameParts === ['not'] || $nameParts === ['is'] || $nameParts === ['has'] || $nameParts === ['where']) { + if ($this->matchChar('(') && + ($this->selectors($subs, true) || true) && + $this->matchChar(')') + ) { + $parts[] = '('; + + while ($sub = array_shift($subs)) { + while ($ps = array_shift($sub)) { + foreach ($ps as &$p) { + $parts[] = $p; + } + if (count($sub) && reset($sub)) { + $parts[] = ' '; + } + } + if (count($subs) && reset($subs)) { + $parts[] = ', '; + } + } + $parts[] = ')'; + } else { + $this->seek($ss); } - - $parts[] = ')'; } else { - $this->seek($ss); + if ($this->matchChar('(') && + ($this->openString(')', $str, '(') || true) && + $this->matchChar(')') + ) { + $parts[] = '('; + + if (! empty($str)) { + $parts[] = $str; + } + + $parts[] = ')'; + } else { + $this->seek($ss); + } } - continue; } } From 2c25b8b179e09e1b515b46ea3ff35b5ea624bcfa Mon Sep 17 00:00:00 2001 From: Cerdic Date: Thu, 16 May 2019 15:08:56 +0200 Subject: [PATCH 02/48] Add a test case for named grid lines syntax https://github.com/leafo/scssphp/issues/542 --- tests/inputs/values.scss | 1 + tests/outputs/values.css | 3 ++- tests/outputs_numbered/values.css | 11 ++++++----- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/inputs/values.scss b/tests/inputs/values.scss index 8279447d..8b35bbd0 100644 --- a/tests/inputs/values.scss +++ b/tests/inputs/values.scss @@ -15,6 +15,7 @@ multiline block \ content: "This is a \ multiline string."; border-radius: -1px -1px -1px black; + grid-template-columns: [avatar] 2fr [body] 6fr; } #subtraction { diff --git a/tests/outputs/values.css b/tests/outputs/values.css index d6937374..419412f2 100644 --- a/tests/outputs/values.css +++ b/tests/outputs/values.css @@ -13,7 +13,8 @@ multiline block \ margin: 4, 3, 1; content: "This is a \ multiline string."; - border-radius: -1px -1px -1px black; } + border-radius: -1px -1px -1px black; + grid-template-columns: [avatar] 2fr [body] 6fr; } #subtraction { lit: 10 -11; diff --git a/tests/outputs_numbered/values.css b/tests/outputs_numbered/values.css index af231709..24a961eb 100644 --- a/tests/outputs_numbered/values.css +++ b/tests/outputs_numbered/values.css @@ -14,8 +14,9 @@ multiline block \ margin: 4, 3, 1; content: "This is a \ multiline string."; - border-radius: -1px -1px -1px black; } -/* line 20, inputs/values.scss */ + border-radius: -1px -1px -1px black; + grid-template-columns: [avatar] 2fr [body] 6fr; } +/* line 21, inputs/values.scss */ #subtraction { lit: 10 -11; lit: -1; @@ -25,14 +26,14 @@ multiline string."; var: -90; var: -90; var: -90; } -/* line 34, inputs/values.scss */ +/* line 35, inputs/values.scss */ #special { a: 12px expression(1 + (3 / Foo.bar("baz" + "bang") + function() {return 12;}) % 12); } -/* line 38, inputs/values.scss */ +/* line 39, inputs/values.scss */ #unary { b: 0.5em; c: -foo(12px); d: +foo(12px); } -/* line 44, inputs/values.scss */ +/* line 45, inputs/values.scss */ #self { content: "#self"; } From c0ba191b3c4d3587fe50d71bbde270bb34ced2bb Mon Sep 17 00:00:00 2001 From: Cerdic Date: Thu, 16 May 2019 14:55:48 +0200 Subject: [PATCH 03/48] https://github.com/leafo/scssphp/issues/542 : be able to parse bracket enclosed keyword as part of a property value --- src/Parser.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Parser.php b/src/Parser.php index eb264bce..96e427ab 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -1270,7 +1270,10 @@ protected function expression(&$out) } if ($this->matchChar('[')) { - if ($this->parenExpression($out, $s, "]")) { + if ($this->parenExpression($out, $s, "]", [Type::T_LIST, Type::T_KEYWORD])) { + if ($out[0] !== Type::T_LIST && $out[0] !== Type::T_MAP) { + $out = [Type::T_STRING, '', [ '[', $out, ']' ]]; + } return true; } @@ -1292,10 +1295,11 @@ protected function expression(&$out) * @param array $out * @param integer $s * @param string $closingParen + * @param array $allowedTypes * * @return boolean */ - protected function parenExpression(&$out, $s, $closingParen = ")") + protected function parenExpression(&$out, $s, $closingParen = ")", $allowedTypes = [Type::T_LIST, Type::T_MAP]) { if ($this->matchChar($closingParen)) { $out = [Type::T_LIST, '', []]; @@ -1303,13 +1307,13 @@ protected function parenExpression(&$out, $s, $closingParen = ")") return true; } - if ($this->valueList($out) && $this->matchChar($closingParen) && $out[0] === Type::T_LIST) { + if ($this->valueList($out) && $this->matchChar($closingParen) && in_array($out[0], $allowedTypes)) { return true; } $this->seek($s); - if ($this->map($out)) { + if (in_array(Type::T_MAP, $allowedTypes) && $this->map($out)) { return true; } From a24a0b6890a7bc76d7b1cc0af93c01472dbb0319 Mon Sep 17 00:00:00 2001 From: Anthon Pang Date: Thu, 16 May 2019 10:18:57 -0400 Subject: [PATCH 04/48] phpcs --- src/Parser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Parser.php b/src/Parser.php index e9053f9f..6732e937 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -2154,7 +2154,7 @@ protected function selectors(&$out, $subSelector = false) while ($this->selector($sel, $subSelector)) { $selectors[] = $sel; - if (! $this->matchChar(',', true)) { + if (! $this->matchChar(',', true)) { break; } From 4ba0e47c5facdbb1155087d736e576b9602b5fa2 Mon Sep 17 00:00:00 2001 From: Anthon Pang Date: Thu, 16 May 2019 13:18:40 -0400 Subject: [PATCH 05/48] coding style tweaks --- src/Cache.php | 148 +++++++++++------------ src/Compiler.php | 174 +++++++++++++++++---------- src/Formatter/Compressed.php | 11 +- src/Formatter/Crunched.php | 11 +- src/Parser.php | 78 +++++++----- src/SourceMap/SourceMapGenerator.php | 4 +- 6 files changed, 251 insertions(+), 175 deletions(-) diff --git a/src/Cache.php b/src/Cache.php index 1e12770a..49cf631b 100644 --- a/src/Cache.php +++ b/src/Cache.php @@ -18,10 +18,10 @@ * * In short: * - * allow to put in cache/get from fache a generic result from a known operation on a generic dataset, + * allow to put in cache/get from cache a generic result from a known operation on a generic dataset, * taking in account options that affects the result * - * The cache manager is agnostic about data format and only the operation is expected to be descripted by string + * The cache manager is agnostic about data format and only the operation is expected to be described by string * */ @@ -32,38 +32,36 @@ */ class Cache { - const CACHE_VERSION = 0; - // directory used for storing data - public static $cache_dir = false; + public static $cacheDir = false; // prefix for the storing data public static $prefix = 'scssphp_'; // force a refresh : 'once' for refreshing the first hit on a cache only, true to never use the cache in this hit - public static $force_refresh = false; + public static $forceFefresh = false; // specifies the number of seconds after which data cached will be seen as 'garbage' and potentially cleaned up - public static $gc_lifetime = 604800; + public static $gcLifetime = 604800; - - // array of already refreshed cache if $force_refresh==='once' + // array of already refreshed cache if $forceFefresh==='once' protected static $refreshed = []; - /** * Constructor + * + * @param array $options */ public function __construct($options) { - //check $cache_dir + // check $cacheDir if (isset($options['cache_dir'])) { - self::$cache_dir = $options['cache_dir']; + self::$cacheDir = $options['cache_dir']; } - if (empty(self::$cache_dir)) { + if (empty(self::$cacheDir)) { throw new Exception('cache_dir not set'); } @@ -75,40 +73,42 @@ public function __construct($options) throw new Exception('prefix not set'); } - if (isset($options['force_refresh'])) { - self::$force_refresh = $options['force_refresh']; + if (isset($options['forceRefresh'])) { + self::$forceFefresh = $options['force_refresh']; } self::checkCacheDir(); } - /** - * Get the cached result of $operation on $what, which is known as dependant from the content of $options + * Get the cached result of $operation on $what, + * which is known as dependant from the content of $options + * + * @param string $operation parse, compile... + * @param mixed $what content key (e.g., filename to be treated) + * @param array $options any option that affect the operation result on the content + * @param integer $lastModified last modified timestamp * - * @param string $operation - * parse, compile... - * @param $what - * content key (filename to be treated for instance) - * @param array $options - * any option that affect the operation result on the content - * @param int $last_modified * @return mixed - * @throws Exception + * + * @throws \Exception */ - public function getCache($operation, $what, $options = array(), $last_modified = null) + public function getCache($operation, $what, $options = [], $lastModified = null) { + $fileCache = self::$cacheDir . self::cacheName($operation, $what, $options); - $fileCache = self::$cache_dir . self::cacheName($operation, $what, $options); + if ((! self::$forceRefresh || (self::$forceRefresh === 'once' && isset(self::$refreshed[$fileCache]))) + && file_exists($fileCache) + ) { + $cacheTime = filemtime($fileCache); - if ((! self::$force_refresh || (self::$force_refresh === 'once' && isset(self::$refreshed[$fileCache]))) - and file_exists($fileCache)) { - $cache_time = filemtime($fileCache); - if ((is_null($last_modified) or $cache_time > $last_modified) - and $cache_time + self::$gc_lifetime > time()) { + if ((is_null($lastModified) || $cacheTime > $lastModified) + && $cacheTime + self::$gcLifetime > time() + ) { $c = file_get_contents($fileCache); $c = unserialize($c); - if (is_array($c) and isset($c['value'])) { + + if (is_array($c) && isset($c['value'])) { return $c['value']; } } @@ -118,43 +118,45 @@ public function getCache($operation, $what, $options = array(), $last_modified = } /** - * Put in cache the result of $operation on $what, which is known as dependant from the content of $options + * Put in cache the result of $operation on $what, + * which is known as dependant from the content of $options * * @param string $operation - * @param $what - * @param $value - * @param array $options + * @param mixed $what + * @param mixed $value + * @param array $options */ - public function setCache($operation, $what, $value, $options = array()) + public function setCache($operation, $what, $value, $options = []) { - $fileCache = self::$cache_dir . self::cacheName($operation, $what, $options); + $fileCache = self::$cacheDir . self::cacheName($operation, $what, $options); - $c = array('value' => $value); + $c = ['value' => $value]; $c = serialize($c); file_put_contents($fileCache, $c); - if (self::$force_refresh === 'once') { + if (self::$forceRefresh === 'once') { self::$refreshed[$fileCache] = true; } } - /** - * get the cachename for the caching of $opetation on $what, which is known as dependant from the content of $options + * Get the cache name for the caching of $operation on $what, + * which is known as dependant from the content of $options + * * @param string $operation - * @param $what - * @param array $options + * @param mixed $what + * @param array $options + * * @return string */ - private static function cacheName($operation, $what, $options = array()) + private static function cacheName($operation, $what, $options = []) { - - $t = array( + $t = [ 'version' => self::CACHE_VERSION, 'operation' => $operation, 'what' => $what, 'options' => $options - ); + ]; $t = self::$prefix . sha1(json_encode($t)) @@ -164,38 +166,35 @@ private static function cacheName($operation, $what, $options = array()) return $t; } - /** - * Check that the cache dir is existing and writeable - * @throws Exception + * Check that the cache dir exists and is writeable + * + * @throws \Exception */ public static function checkCacheDir() { + self::$cacheDir = str_replace('\\', '/', self::$cacheDir); + self::$cacheDir = rtrim(self::$cacheDir, '/') . '/'; - self::$cache_dir = str_replace('\\', '/', self::$cache_dir); - self::$cache_dir = rtrim(self::$cache_dir, '/') . '/'; - - if (! file_exists(self::$cache_dir)) { - if (! mkdir(self::$cache_dir)) { - throw new Exception('Cache directory couldn\'t be created: ' . self::$cache_dir); + if (! file_exists(self::$cacheDir)) { + if (! mkdir(self::$cacheDir)) { + throw new Exception('Cache directory couldn\'t be created: ' . self::$cacheDir); } - } elseif (! is_dir(self::$cache_dir)) { - throw new Exception('Cache directory doesn\'t exist: ' . self::$cache_dir); - } elseif (! is_writable(self::$cache_dir)) { - throw new Exception('Cache directory isn\'t writable: ' . self::$cache_dir); + } elseif (! is_dir(self::$cacheDir)) { + throw new Exception('Cache directory doesn\'t exist: ' . self::$cacheDir); + } elseif (! is_writable(self::$cacheDir)) { + throw new Exception('Cache directory isn\'t writable: ' . self::$cacheDir); } } /** * Delete unused cached files - * */ public static function cleanCache() { static $clean = false; - - if ($clean || empty(self::$cache_dir)) { + if ($clean || empty(self::$cacheDir)) { return; } @@ -203,14 +202,16 @@ public static function cleanCache() // only remove files with extensions created by SCSSPHP Cache // css files removed based on the list files - $remove_types = array('scsscache' => 1); + $removeTypes = ['scsscache' => 1]; + + $files = scandir(self::$cacheDir); - $files = scandir(self::$cache_dir); if (! $files) { return; } - $check_time = time() - self::$gc_lifetime; + $checkTime = time() - self::$gcLifetime; + foreach ($files as $file) { // don't delete if the file wasn't created with SCSSPHP Cache if (strpos($file, self::$prefix) !== 0) { @@ -220,20 +221,19 @@ public static function cleanCache() $parts = explode('.', $file); $type = array_pop($parts); - - if (! isset($remove_types[$type])) { + if (! isset($removeTypes[$type])) { continue; } - $full_path = self::$cache_dir . $file; - $mtime = filemtime($full_path); + $fullPath = self::$cacheDir . $file; + $mtime = filemtime($fullPath); // don't delete if it's a relatively new file - if ($mtime > $check_time) { + if ($mtime > $checkTime) { continue; } - unlink($full_path); + unlink($fullPath); } } } diff --git a/src/Compiler.php b/src/Compiler.php index 50785aac..782799d1 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -99,17 +99,17 @@ class Compiler 'function' => '^', ]; - static public $true = [Type::T_KEYWORD, 'true']; - static public $false = [Type::T_KEYWORD, 'false']; - static public $null = [Type::T_NULL]; - static public $nullString = [Type::T_STRING, '', []]; + static public $true = [Type::T_KEYWORD, 'true']; + static public $false = [Type::T_KEYWORD, 'false']; + static public $null = [Type::T_NULL]; + static public $nullString = [Type::T_STRING, '', []]; static public $defaultValue = [Type::T_KEYWORD, '']; static public $selfSelector = [Type::T_SELF]; - static public $emptyList = [Type::T_LIST, '', []]; - static public $emptyMap = [Type::T_MAP, [], []]; - static public $emptyString = [Type::T_STRING, '"', []]; - static public $with = [Type::T_KEYWORD, 'with']; - static public $without = [Type::T_KEYWORD, 'without']; + static public $emptyList = [Type::T_LIST, '', []]; + static public $emptyMap = [Type::T_MAP, [], []]; + static public $emptyString = [Type::T_STRING, '"', []]; + static public $with = [Type::T_KEYWORD, 'with']; + static public $without = [Type::T_KEYWORD, 'without']; protected $importPaths = ['']; protected $importCache = []; @@ -165,27 +165,28 @@ class Compiler /** * Constructor */ - public function __construct($cache_options = null) + public function __construct($cacheOptions = null) { $this->parsedFiles = []; $this->sourceNames = []; - if ($cache_options) { - $this->cache = new Cache($cache_options); + if ($cacheOptions) { + $this->cache = new Cache($cacheOptions); } } public function getCompileOptions() { - $options = array( - 'importPaths' => $this->importPaths, - 'registeredVars' => $this->registeredVars, + $options = [ + 'importPaths' => $this->importPaths, + 'registeredVars' => $this->registeredVars, 'registeredFeatures' => $this->registeredFeatures, - 'encoding' => $this->encoding, - 'sourceMap' => serialize($this->sourceMap), - 'sourceMapOptions' => $this->sourceMapOptions, - 'formater' => $this->formatter, - ); + 'encoding' => $this->encoding, + 'sourceMap' => serialize($this->sourceMap), + 'sourceMapOptions' => $this->sourceMapOptions, + 'formatter' => $this->formatter, + ]; + return $options; } @@ -201,22 +202,25 @@ public function getCompileOptions() */ public function compile($code, $path = null) { - if ($this->cache) { - $cache_key = ($path ? $path : "(stdin)") . ":" . md5($code); - $compile_options = $this->getCompileOptions(); - $cache = $this->cache->getCache("compile", $cache_key, $compile_options); + $cacheKey = ($path ? $path : "(stdin)") . ":" . md5($code); + $compileOptions = $this->getCompileOptions(); + $cache = $this->cache->getCache("compile", $cacheKey, $compileOptions); + if (is_array($cache) - and isset($cache['dependencies']) - and isset($cache['out']) ) { + && isset($cache['dependencies']) + && isset($cache['out']) + ) { // check if any dependency file changed before accepting the cache foreach ($cache['dependencies'] as $file => $mtime) { - if (!file_exists($file) - or filemtime($file) !== $mtime) { + if (! file_exists($file) + || filemtime($file) !== $mtime + ) { unset($cache); break; } } + if (isset($cache)) { return $cache['out']; } @@ -279,12 +283,13 @@ public function compile($code, $path = null) $out .= sprintf('/*# sourceMappingURL=%s */', $sourceMapUrl); } - if ($this->cache and isset($cache_key) and isset($compile_options)) { - $v = array( + if ($this->cache && isset($cacheKey) && isset($compileOptions)) { + $v = [ 'dependencies' => $this->getParsedFiles(), 'out' => &$out, - ); - $this->cache->setCache("compile", $cache_key, $v, $compile_options); + ]; + + $this->cache->setCache("compile", $cacheKey, $v, $compileOptions); } return $out; @@ -490,6 +495,7 @@ protected function flattenSelectors(OutputBlock $block, $parentKey = null) protected function glueFunctionSelectors($parts) { $new = []; + foreach ($parts as $part) { if (is_array($part)) { $part = $this->glueFunctionSelectors($part); @@ -506,6 +512,7 @@ protected function glueFunctionSelectors($parts) } } } + return $new; } @@ -520,6 +527,7 @@ protected function glueFunctionSelectors($parts) protected function matchExtends($selector, &$out, $from = 0, $initial = true) { static $partsPile = []; + $selector = $this->glueFunctionSelectors($selector); foreach ($selector as $i => $part) { @@ -539,8 +547,8 @@ protected function matchExtends($selector, &$out, $from = 0, $initial = true) if ($this->matchExtendsSingle($part, $origin)) { $partsPile[] = $part; - $after = array_slice($selector, $i + 1); - $before = array_slice($selector, 0, $i); + $after = array_slice($selector, $i + 1); + $before = array_slice($selector, 0, $i); list($before, $nonBreakableBefore) = $this->extractRelationshipFromFragment($before); @@ -548,7 +556,7 @@ protected function matchExtends($selector, &$out, $from = 0, $initial = true) $k = 0; // remove shared parts - if (count($new)>1) { + if (count($new) > 1) { while ($k < $i && isset($new[$k]) && $selector[$k] === $new[$k]) { $k++; } @@ -561,7 +569,7 @@ protected function matchExtends($selector, &$out, $from = 0, $initial = true) $slice = []; foreach ($tempReplacement[$l] as $chunk) { - if (!in_array($chunk, $slice)) { + if (! in_array($chunk, $slice)) { $slice[] = $chunk; } } @@ -592,19 +600,19 @@ protected function matchExtends($selector, &$out, $from = 0, $initial = true) $out[] = $result; // recursively check for more matches - $startRecursFrom = count($before) + min(count($nonBreakableBefore), count($mergedBefore)); - $this->matchExtends($result, $out, $startRecursFrom, false); + $startRecurseFrom = count($before) + min(count($nonBreakableBefore), count($mergedBefore)); + $this->matchExtends($result, $out, $startRecurseFrom, false); // selector sequence merging if (! empty($before) && count($new) > 1) { - $sharedParts = $k > 0 ? array_slice($before, 0, $k) : []; + $preSharedParts = $k > 0 ? array_slice($before, 0, $k) : []; $postSharedParts = $k > 0 ? array_slice($before, $k) : $before; - list($injectBetweenSharedParts, $nonBreakable2) = $this->extractRelationshipFromFragment($afterBefore); + list($betweenSharedParts, $nonBreakable2) = $this->extractRelationshipFromFragment($afterBefore); $result2 = array_merge( - $sharedParts, - $injectBetweenSharedParts, + $preSharedParts, + $betweenSharedParts, $postSharedParts, $nonBreakable2, $nonBreakableBefore, @@ -615,6 +623,7 @@ protected function matchExtends($selector, &$out, $from = 0, $initial = true) $out[] = $result2; } } + array_pop($partsPile); } } @@ -671,6 +680,7 @@ protected function matchExtendsSingle($rawSingle, &$outOrigin) foreach ($counts as $idx => $count) { list($target, $origin, /* $block */) = $this->extends[$idx]; + $origin = $this->glueFunctionSelectors($origin); // check count @@ -797,6 +807,7 @@ protected function compileMedia(Block $media) if (! empty($mediaQueries) && $mediaQueries) { $previousScope = $this->scope; $parentScope = $this->mediaParent($this->scope); + foreach ($mediaQueries as $mediaQuery) { $this->scope = $this->makeOutputBlock(Type::T_MEDIA, [$mediaQuery]); @@ -811,9 +822,9 @@ protected function compileMedia(Block $media) $type = $child[0]; if ($type !== Type::T_BLOCK && - $type !== Type::T_MEDIA && - $type !== Type::T_DIRECTIVE && - $type !== Type::T_IMPORT + $type !== Type::T_MEDIA && + $type !== Type::T_DIRECTIVE && + $type !== Type::T_IMPORT ) { $needsWrap = true; break; @@ -1002,7 +1013,7 @@ protected function filterScopeWithout($scope, $without) } } - if (!count($filteredScopes)) { + if (! count($filteredScopes)) { return $this->rootBlock; } @@ -1413,8 +1424,8 @@ protected function evalSelectorPart($part) /** * Collapse selectors * - * @param array $selectors - * @param bool $selectorFormat + * @param array $selectors + * @param boolean $selectorFormat * if false return a collapsed string * if true return an array description of a structured selector * @@ -1427,6 +1438,7 @@ protected function collapseSelectors($selectors, $selectorFormat = false) foreach ($selectors as $selector) { $output = []; $glueNext = false; + foreach ($selector as $node) { $compound = ''; @@ -1436,6 +1448,7 @@ function ($value, $key) use (&$compound) { $compound .= $value; } ); + if ($selectorFormat && $this->isImmediateRelationshipCombinator($compound)) { if (count($output)) { $output[count($output) - 1] .= ' ' . $compound; @@ -1459,6 +1472,7 @@ function ($value, $key) use (&$compound) { } else { $output = implode(' ', $output); } + $parts[] = $output; } @@ -1489,6 +1503,7 @@ protected function revertSelfSelector($selectors) } } } + return $selectors; } @@ -1604,6 +1619,7 @@ protected function pushCallStack($name = '') Parser::SOURCE_LINE => $this->sourceLine, Parser::SOURCE_COLUMN => $this->sourceColumn ]; + // infinite calling loop if (count($this->callStack) > 25000) { // not displayed but you can var_dump it to deep debug @@ -1630,6 +1646,7 @@ protected function popCallStack() protected function compileChildren($stms, OutputBlock $out, $traceName = '') { $this->pushCallStack($traceName); + foreach ($stms as $stm) { $ret = $this->compileChild($stm, $out); @@ -1637,6 +1654,7 @@ protected function compileChildren($stms, OutputBlock $out, $traceName = '') return $ret; } } + $this->popCallStack(); return null; @@ -1655,6 +1673,7 @@ protected function compileChildren($stms, OutputBlock $out, $traceName = '') protected function compileChildrenNoReturn($stms, OutputBlock $out, $selfParent = null, $traceName = '') { $this->pushCallStack($traceName); + foreach ($stms as $stm) { if ($selfParent && isset($stm[1]) && is_object($stm[1]) && $stm[1] instanceof Block) { $stm[1]->selfParent = $selfParent; @@ -1674,6 +1693,7 @@ protected function compileChildrenNoReturn($stms, OutputBlock $out, $selfParent return; } } + $this->popCallStack(); } @@ -1754,12 +1774,14 @@ protected function compileMediaQuery($queryList) $parts = []; $mediaTypeOnly = true; + foreach ($query as $q) { if ($q[0] !== Type::T_MEDIA_TYPE) { $mediaTypeOnly = false; break; } } + foreach ($query as $q) { switch ($q[0]) { case Type::T_MEDIA_TYPE: @@ -1770,23 +1792,29 @@ protected function compileMediaQuery($queryList) if ($type) { array_unshift($parts, implode(' ', array_filter($type))); } + if (! empty($parts)) { if (strlen($current)) { $current .= $this->formatter->tagSeparator; } + $current .= implode(' and ', $parts); } + if ($current) { $out[] = $start . $current; } + $current = ""; $type = null; $parts = []; } } + if ($newType === ['all'] && $default) { $default = $start . 'all'; } + // all can be safely ignored and mixed with whatever else if ($newType !== ['all']) { if ($type) { @@ -1840,10 +1868,12 @@ protected function compileMediaQuery($queryList) if ($current) { $out[] = $start . $current; } + // no @media type except all, and no conflict? - if (!$out && $default) { + if (! $out && $default) { $out[] = $default; } + return $out; } @@ -1881,6 +1911,7 @@ protected function mergeDirectRelationships($selectors1, $selectors2) } else { $merged = array_merge($selectors1, [$part1], $selectors2, [$part2], $merged); } + break; } @@ -2018,11 +2049,11 @@ protected function compileChild($child, OutputBlock $out) $this->sourceIndex = isset($child[Parser::SOURCE_INDEX]) ? $child[Parser::SOURCE_INDEX] : null; $this->sourceLine = isset($child[Parser::SOURCE_LINE]) ? $child[Parser::SOURCE_LINE] : -1; $this->sourceColumn = isset($child[Parser::SOURCE_COLUMN]) ? $child[Parser::SOURCE_COLUMN] : -1; - } elseif (is_array($child) and isset($child[1]->sourceLine)) { + } elseif (is_array($child) && isset($child[1]->sourceLine)) { $this->sourceIndex = $child[1]->sourceIndex; $this->sourceLine = $child[1]->sourceLine; $this->sourceColumn = $child[1]->sourceColumn; - } elseif (! empty($out->sourceLine) and ! empty($out->sourceName)) { + } elseif (! empty($out->sourceLine) && ! empty($out->sourceName)) { $this->sourceLine = $out->sourceLine; $this->sourceIndex = array_search($out->sourceName, $this->sourceNames); @@ -2379,7 +2410,8 @@ protected function compileChild($child, OutputBlock $out) break; case Type::T_MIXIN_CONTENT: - $content = $this->get(static::$namespaces['special'] . 'content', false, isset($this->storeEnv) ? $this->storeEnv : $this->env); + $env = isset($this->storeEnv) ? $this->storeEnv : $this->env; + $content = $this->get(static::$namespaces['special'] . 'content', false, $env); if (! $content) { $content = new \stdClass(); @@ -2901,7 +2933,7 @@ protected function opAnd($left, $right, $shouldEval) return null; } - if ($left !== static::$false and $left !== static::$null) { + if ($left !== static::$false && $left !== static::$null) { return $this->reduce($right, true); } @@ -2923,7 +2955,7 @@ protected function opOr($left, $right, $shouldEval) return null; } - if ($left !== static::$false and $left !== static::$null) { + if ($left !== static::$false && $left !== static::$null) { return $left; } @@ -3401,7 +3433,7 @@ protected function multiplySelectors(Environment $env, $selfParent = null) $selfParentSelectors = null; - if (!is_null($selfParent) and $selfParent->selectors) { + if (! is_null($selfParent) && $selfParent->selectors) { $selfParentSelectors = $this->evalSelectors($selfParent->selectors); } @@ -3444,10 +3476,10 @@ protected function multiplySelectors(Environment $env, $selfParent = null) /** * Join selectors; looks for & to replace, or append parent before child * - * @param array $parent - * @param array $child - * @param bool &$stillHasSelf - * @param array $selfParentSelectors + * @param array $parent + * @param array $child + * @param boolean &$stillHasSelf + * @param array $selfParentSelectors * @return array */ @@ -3464,7 +3496,8 @@ protected function joinSelectors($parent, $child, &$stillHasSelf, $selfParentSel if ($p === static::$selfSelector && $setSelf) { $stillHasSelf = true; } - if ($p === static::$selfSelector && !$setSelf) { + + if ($p === static::$selfSelector && ! $setSelf) { $setSelf = true; if (is_null($selfParentSelectors)) { @@ -3485,6 +3518,7 @@ protected function joinSelectors($parent, $child, &$stillHasSelf, $selfParentSel }); $pp = implode($flatten); } + $newPart[] = $pp; } } @@ -3538,7 +3572,11 @@ protected function multiplyMedia(Environment $env = null, $childQueries = null) foreach ($parentQueries as $parentQuery) { foreach ($originalQueries as $childQuery) { - $childQueries []= array_merge($parentQuery, [[Type::T_MEDIA_TYPE, [Type::T_KEYWORD, 'all']]], $childQuery); + $childQueries[] = array_merge( + $parentQuery, + [[Type::T_MEDIA_TYPE, [Type::T_KEYWORD, 'all']]], + $childQuery + ); } } } @@ -3727,6 +3765,7 @@ public function get($name, $shouldThrow = true, Environment $env = null, $unredu if ($maxDepth-- <= 0) { break; } + if (array_key_exists($normalizedName, $env->store)) { if ($unreduced && isset($env->storeUnreduced[$normalizedName])) { return $env->storeUnreduced[$normalizedName]; @@ -4040,7 +4079,7 @@ public function findImport($url) // check urls for normal import paths foreach ($urls as $full) { $separator = ( - !empty($dir) && + ! empty($dir) && substr($dir, -1) !== '/' && substr($full, 0, 1) !== '/' ) ? '/' : ''; @@ -4119,6 +4158,7 @@ public function throwError($msg) $msg = "$msg: $loc"; $callStackMsg = $this->callStackMessage(); + if ($callStackMsg) { $msg .= "\nCall Stack:\n" . $callStackMsg; } @@ -4127,8 +4167,11 @@ public function throwError($msg) } /** - * @param bool $all - * @param null $limit + * Beautify call stack for output + * + * @param boolean $all + * @param null $limit + * * @return string */ protected function callStackMessage($all = false, $limit = null) @@ -4145,7 +4188,8 @@ protected function callStackMessage($all = false, $limit = null) : '(unknown file)'); $msg .= " on line " . $call[Parser::SOURCE_LINE]; $callStackMsg[] = $msg; - if (!is_null($limit) && $ncall>$limit) { + + if (! is_null($limit) && $ncall>$limit) { break; } } diff --git a/src/Formatter/Compressed.php b/src/Formatter/Compressed.php index 52410b6c..ab38529d 100644 --- a/src/Formatter/Compressed.php +++ b/src/Formatter/Compressed.php @@ -69,8 +69,13 @@ protected function blockSelectors(OutputBlock $block) { $inner = $this->indentStr(); - $this->write($inner - . implode($this->tagSeparator, str_replace(array(' > ', ' + ', ' ~ '), array('>', '+', '~'), $block->selectors)) - . $this->open . $this->break); + $this->write( + $inner + . implode( + $this->tagSeparator, + str_replace([' > ', ' + ', ' ~ '], ['>', '+', '~'], $block->selectors) + ) + . $this->open . $this->break + ); } } diff --git a/src/Formatter/Crunched.php b/src/Formatter/Crunched.php index 31970255..da740ccd 100644 --- a/src/Formatter/Crunched.php +++ b/src/Formatter/Crunched.php @@ -67,8 +67,13 @@ protected function blockSelectors(OutputBlock $block) { $inner = $this->indentStr(); - $this->write($inner - . implode($this->tagSeparator, str_replace(array(' > ', ' + ', ' ~ '), array('>', '+', '~'), $block->selectors)) - . $this->open . $this->break); + $this->write( + $inner + . implode( + $this->tagSeparator, + str_replace([' > ', ' + ', ' ~ '], ['>', '+', '~'], $block->selectors) + ) + . $this->open . $this->break + ); } } diff --git a/src/Parser.php b/src/Parser.php index 6732e937..9f8cb2e8 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -12,6 +12,7 @@ namespace Leafo\ScssPhp; use Leafo\ScssPhp\Block; +use Leafo\ScssPhp\Cache; use Leafo\ScssPhp\Compiler; use Leafo\ScssPhp\Exception\ParserException; use Leafo\ScssPhp\Node; @@ -74,10 +75,10 @@ class Parser * * @api * - * @param string $sourceName - * @param integer $sourceIndex - * @param string $encoding - * @param Cache $cache + * @param string $sourceName + * @param integer $sourceIndex + * @param string $encoding + * @param \Leafo\ScssPhp\Cache $cache */ public function __construct($sourceName, $sourceIndex = 0, $encoding = 'utf-8', $cache = null) { @@ -86,7 +87,7 @@ public function __construct($sourceName, $sourceIndex = 0, $encoding = 'utf-8', $this->charset = null; $this->utf8 = ! $encoding || strtolower($encoding) === 'utf-8'; $this->patternModifiers = $this->utf8 ? 'Aisu' : 'Ais'; - $this->commentsSeen = []; + $this->commentsSeen = []; if (empty(static::$operatorPattern)) { static::$operatorPattern = '([*\/%+-]|[!=]\=|\>\=?|\<\=\>|\<\=?|and|or)'; @@ -151,15 +152,15 @@ public function throwParseError($msg = 'parse error') */ public function parse($buffer) { - if ($this->cache) { - $cache_key = $this->sourceName . ":" . md5($buffer); - $parse_options = array( + $cacheKey = $this->sourceName . ":" . md5($buffer); + $parseOptions = [ 'charset' => $this->charset, 'utf8' => $this->utf8, - ); - $v = $this->cache->getCache("parse", $cache_key, $parse_options); - if (!is_null($v)) { + ]; + $v = $this->cache->getCache("parse", $cacheKey, $parseOptions); + + if (! is_null($v)) { return $v; } } @@ -204,7 +205,7 @@ public function parse($buffer) $this->restoreEncoding(); if ($this->cache) { - $this->cache->setCache("parse", $cache_key, $this->env, $parse_options); + $this->cache->setCache("parse", $cacheKey, $this->env, $parseOptions); } return $this->env; @@ -932,6 +933,7 @@ protected function matchChar($char, $eatWhitespace = null) if ($eatWhitespace) { $this->whitespace(); } + return true; } @@ -946,7 +948,6 @@ protected function matchChar($char, $eatWhitespace = null) */ protected function literal($what, $len, $eatWhitespace = null) { - if (strcasecmp(substr($this->buffer, $this->count, $len), $what) !== 0) { return false; } @@ -960,6 +961,7 @@ protected function literal($what, $len, $eatWhitespace = null) if ($eatWhitespace) { $this->whitespace(); } + return true; } @@ -1235,7 +1237,7 @@ protected function genericList(&$out, $parseItem, $delim = '', $flatten = true) } } - if (!$items) { + if (! $items) { $this->seek($s); return false; @@ -1274,6 +1276,7 @@ protected function expression(&$out) if ($out[0] !== Type::T_LIST && $out[0] !== Type::T_MAP) { $out = [Type::T_STRING, '', [ '[', $out, ']' ]]; } + return true; } @@ -1388,7 +1391,11 @@ protected function value(&$out) $char = $this->buffer[$this->count]; if ($this->literal('url(', 4) && $this->match('data:([a-z]+)\/([a-z0-9.+-]+);base64,', $m, false)) { - $len = strspn($this->buffer, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwyxz0123456789+/=', $this->count); + $len = strspn( + $this->buffer, + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwyxz0123456789+/=', + $this->count + ); $this->count += $len; @@ -1427,6 +1434,7 @@ protected function value(&$out) if ($this->value($inner)) { $out = [Type::T_UNARY, '+', $inner, $this->inParens]; + return true; } @@ -1441,8 +1449,10 @@ protected function value(&$out) if ($this->variable($inner) || $this->unit($inner) || $this->parenValue($inner)) { $out = [Type::T_UNARY, '-', $inner, $this->inParens]; + return true; } + $this->count--; } @@ -1459,6 +1469,7 @@ protected function value(&$out) if ($this->matchChar('&', true)) { $out = [Type::T_SELF]; + return true; } @@ -1646,7 +1657,7 @@ protected function argumentList(&$out) $args[] = [Type::T_STRING, '', [', ']]; } - if (! $this->matchChar(')') || !$args) { + if (! $this->matchChar(')') || ! $args) { $this->seek($s); return false; @@ -1744,7 +1755,7 @@ protected function map(&$out) } } - if (!$keys || ! $this->matchChar(')')) { + if (! $keys || ! $this->matchChar(')')) { $this->seek($s); return false; @@ -1782,6 +1793,7 @@ protected function color(&$out) $color[$i] = $t << 4 | $t; $num >>= 4; } + break; case 6: @@ -1792,6 +1804,7 @@ protected function color(&$out) $color[$i] = $num & 0xff; $num >>= 8; } + break; default: @@ -1954,7 +1967,7 @@ protected function mixedKeyword(&$out) $this->eatWhiteDefault = $oldWhite; - if (!$parts) { + if (! $parts) { return false; } @@ -2020,7 +2033,7 @@ protected function openString($end, &$out, $nestingOpen = null) $this->eatWhiteDefault = $oldWhite; - if (!$content) { + if (! $content) { return false; } @@ -2062,6 +2075,7 @@ protected function interpolation(&$out, $lookWhite = true) $out = [Type::T_INTERPOLATE, $value, $left, $right]; } + $this->eatWhiteDefault = $oldWhite; if ($this->eatWhiteDefault) { @@ -2103,7 +2117,7 @@ protected function propertyName(&$out) continue; } - if (!$parts && $this->match('[:.#]', $m, false)) { + if (! $parts && $this->match('[:.#]', $m, false)) { // css hacks $parts[] = $m[0]; continue; @@ -2114,7 +2128,7 @@ protected function propertyName(&$out) $this->eatWhiteDefault = $oldWhite; - if (!$parts) { + if (! $parts) { return false; } @@ -2163,7 +2177,7 @@ protected function selectors(&$out, $subSelector = false) } } - if (!$selectors) { + if (! $selectors) { $this->seek($s); return false; @@ -2205,11 +2219,12 @@ protected function selector(&$out, $subSelector = false) break; } - if (!$selector) { + if (! $selector) { return false; } $out = $selector; + return true; } @@ -2259,10 +2274,12 @@ protected function selectorSingle(&$out, $subSelector = false) $parts[] = Compiler::$selfSelector; $this->count++; continue 2; + case '.': $parts[] = '.'; $this->count++; continue 2; + case '|': $parts[] = '|'; $this->count++; @@ -2316,7 +2333,9 @@ protected function selectorSingle(&$out, $subSelector = false) $ss = $this->count; - if ($nameParts === ['not'] || $nameParts === ['is'] || $nameParts === ['has'] || $nameParts === ['where']) { + if ($nameParts === ['not'] || $nameParts === ['is'] || + $nameParts === ['has'] || $nameParts === ['where'] + ) { if ($this->matchChar('(') && ($this->selectors($subs, true) || true) && $this->matchChar(')') @@ -2336,6 +2355,7 @@ protected function selectorSingle(&$out, $subSelector = false) $parts[] = ', '; } } + $parts[] = ')'; } else { $this->seek($ss); @@ -2356,6 +2376,7 @@ protected function selectorSingle(&$out, $subSelector = false) $this->seek($ss); } } + continue; } } @@ -2364,9 +2385,9 @@ protected function selectorSingle(&$out, $subSelector = false) // attribute selector if ($char === '[' && - $this->matchChar('[') && - ($this->openString(']', $str, '[') || true) && - $this->matchChar(']') + $this->matchChar('[') && + ($this->openString(']', $str, '[') || true) && + $this->matchChar(']') ) { $parts[] = '['; @@ -2375,7 +2396,6 @@ protected function selectorSingle(&$out, $subSelector = false) } $parts[] = ']'; - continue; } @@ -2397,7 +2417,7 @@ protected function selectorSingle(&$out, $subSelector = false) $this->eatWhiteDefault = $oldWhite; - if (!$parts) { + if (! $parts) { return false; } diff --git a/src/SourceMap/SourceMapGenerator.php b/src/SourceMap/SourceMapGenerator.php index d2001462..fb11a0bf 100644 --- a/src/SourceMap/SourceMapGenerator.php +++ b/src/SourceMap/SourceMapGenerator.php @@ -137,7 +137,9 @@ public function saveMap($content) // 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)); + throw new CompilerException( + sprintf('The directory "%s" does not exist. Cannot save the source map.', $dir) + ); } // FIXME: proper saving, with dir write check! From 2f88bb86b67883b2cb4598a9793679326dc1d3f2 Mon Sep 17 00:00:00 2001 From: Anthon Pang Date: Fri, 31 May 2019 11:48:09 -0400 Subject: [PATCH 06/48] Bump version to v0.8.3 --- src/Version.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Version.php b/src/Version.php index 19a6a02c..46ffedf2 100644 --- a/src/Version.php +++ b/src/Version.php @@ -18,5 +18,5 @@ */ class Version { - const VERSION = 'v0.8.2'; + const VERSION = 'v0.8.3'; } From ce644ca417214b7b96e11f099abcc4b2ebb15c03 Mon Sep 17 00:00:00 2001 From: Anthon Pang Date: Fri, 31 May 2019 12:30:54 -0400 Subject: [PATCH 07/48] Fixes #588 - invalid CSS outside of selector --- src/Parser.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Parser.php b/src/Parser.php index 9f8cb2e8..3afc2ffe 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -200,8 +200,6 @@ public function parse($buffer) array_unshift($this->env->children, $this->charset); } - $this->env->isRoot = true; - $this->restoreEncoding(); if ($this->cache) { @@ -675,6 +673,10 @@ protected function parseChunk() $foundSomething = false; if ($this->valueList($value)) { + if (empty($this->env->parent)) { + $this->throwParseError('expected "{"'); + } + $this->append([Type::T_ASSIGN, $name, $value], $s); $foundSomething = true; } From eea4938784e572e7723480d062ce2b192b1af4d3 Mon Sep 17 00:00:00 2001 From: Anthon Pang Date: Fri, 31 May 2019 12:39:47 -0400 Subject: [PATCH 08/48] Fix #698 add source column to thrown error message --- src/Compiler.php | 11 +++++++---- src/Parser.php | 6 ++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Compiler.php b/src/Compiler.php index 782799d1..e3f51d7c 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -4147,14 +4147,17 @@ public function throwError($msg) return; } + $line = $this->sourceLine; + $column = $this->sourceColumn; + + $loc = isset($this->sourceNames[$this->sourceIndex]) + ? $this->sourceNames[$this->sourceIndex] . " on line $line, at column $column" + : "line: $line, column: $column"; + if (func_num_args() > 1) { $msg = call_user_func_array('sprintf', func_get_args()); } - $line = $this->sourceLine; - $loc = isset($this->sourceNames[$this->sourceIndex]) - ? $this->sourceNames[$this->sourceIndex] . " on line $line" - : "line: $line"; $msg = "$msg: $loc"; $callStackMsg = $this->callStackMessage(); diff --git a/src/Parser.php b/src/Parser.php index 3afc2ffe..ebc8155e 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -130,9 +130,11 @@ public function getSourceName() */ public function throwParseError($msg = 'parse error') { - list($line, /* $column */) = $this->getSourcePosition($this->count); + list($line, $column) = $this->getSourcePosition($this->count); - $loc = empty($this->sourceName) ? "line: $line" : "$this->sourceName on line $line"; + $loc = empty($this->sourceName) + ? "line: $line, column: $column" + : "$this->sourceName on line $line, at column $column"; if ($this->peek("(.*?)(\n|$)", $m, $this->count)) { throw new ParserException("$msg: failed at `$m[1]` $loc"); From eb5e624b7c8c9260b802ab649d57ed74c87a1968 Mon Sep 17 00:00:00 2001 From: Anthon Pang Date: Fri, 31 May 2019 13:29:30 -0400 Subject: [PATCH 09/48] Fix #491 missing http(s) protocol from url() --- src/Parser.php | 13 +++++++++++++ tests/inputs/values.scss | 2 ++ tests/outputs/values.css | 1 + tests/outputs_numbered/values.css | 9 +++++---- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/Parser.php b/src/Parser.php index ebc8155e..076f1620 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -1413,6 +1413,19 @@ protected function value(&$out) $this->seek($s); + if ($this->literal('url(', 4, false) && $this->match('\s*(\/\/\S+)\s*', $m)) { + $content = 'url(' . $m[1]; + + if ($this->matchChar(')')) { + $content .= ')'; + $out = [Type::T_KEYWORD, $content]; + + return true; + } + } + + $this->seek($s); + // not if ($char === 'n' && $this->literal('not', 3, false)) { if ($this->whitespace() && $this->value($inner)) { diff --git a/tests/inputs/values.scss b/tests/inputs/values.scss index 8b35bbd0..d2e9a06d 100644 --- a/tests/inputs/values.scss +++ b/tests/inputs/values.scss @@ -6,6 +6,8 @@ width: 80%; color: "hello world"; height: url("http://google.com"); + background-image: url(//host/image.png +); dads: url(http://leafo.net); padding: 10px 10px 10px 10px, 3px 3px 3px; textblock: "This is a \ diff --git a/tests/outputs/values.css b/tests/outputs/values.css index 419412f2..ee227112 100644 --- a/tests/outputs/values.css +++ b/tests/outputs/values.css @@ -5,6 +5,7 @@ width: 80%; color: "hello world"; height: url("http://google.com"); + background-image: url(//host/image.png); dads: url(http://leafo.net); padding: 10px 10px 10px 10px, 3px 3px 3px; textblock: "This is a \ diff --git a/tests/outputs_numbered/values.css b/tests/outputs_numbered/values.css index 24a961eb..cc4d591a 100644 --- a/tests/outputs_numbered/values.css +++ b/tests/outputs_numbered/values.css @@ -6,6 +6,7 @@ width: 80%; color: "hello world"; height: url("http://google.com"); + background-image: url(//host/image.png); dads: url(http://leafo.net); padding: 10px 10px 10px 10px, 3px 3px 3px; textblock: "This is a \ @@ -16,7 +17,7 @@ multiline block \ multiline string."; border-radius: -1px -1px -1px black; grid-template-columns: [avatar] 2fr [body] 6fr; } -/* line 21, inputs/values.scss */ +/* line 23, inputs/values.scss */ #subtraction { lit: 10 -11; lit: -1; @@ -26,14 +27,14 @@ multiline string."; var: -90; var: -90; var: -90; } -/* line 35, inputs/values.scss */ +/* line 37, inputs/values.scss */ #special { a: 12px expression(1 + (3 / Foo.bar("baz" + "bang") + function() {return 12;}) % 12); } -/* line 39, inputs/values.scss */ +/* line 41, inputs/values.scss */ #unary { b: 0.5em; c: -foo(12px); d: +foo(12px); } -/* line 45, inputs/values.scss */ +/* line 47, inputs/values.scss */ #self { content: "#self"; } From 986957e227fe0b19774d479ea250f859a739776a Mon Sep 17 00:00:00 2001 From: Anthon Pang Date: Fri, 31 May 2019 13:40:17 -0400 Subject: [PATCH 10/48] Remove tests that no longer fail --- tests/FailingTest.php | 50 ------------------------------------------- 1 file changed, 50 deletions(-) diff --git a/tests/FailingTest.php b/tests/FailingTest.php index 86865049..95f693b2 100644 --- a/tests/FailingTest.php +++ b/tests/FailingTest.php @@ -62,56 +62,6 @@ public function provideFailing() { // @codingStandardsIgnoreStart return array( - array( - '#67 - weird @extend behavior', <<<'END_OF_SCSS' -.nav-bar { - background: #eee; - > .item { - margin: 0 10px; - } -} - - -header ul { - @extend .nav-bar; - > li { - @extend .item; - } -} -END_OF_SCSS - , << .item, header ul > .item, header ul > li { - margin: 0 10px; } -END_OF_EXPECTED - ), - array( - '#368 - self in selector', <<<'END_OF_SCSS' -.test { - &:last-child:not(+ &:first-child) { - padding-left: 10px; - } -} -END_OF_SCSS - , << Date: Mon, 3 Jun 2019 11:07:35 +0200 Subject: [PATCH 11/48] Unit tests for unicode range syntax with wildcards --- tests/inputs/values.scss | 8 ++++++++ tests/outputs/values.css | 7 +++++++ tests/outputs_numbered/values.css | 7 +++++++ 3 files changed, 22 insertions(+) diff --git a/tests/inputs/values.scss b/tests/inputs/values.scss index d2e9a06d..b4111ddb 100644 --- a/tests/inputs/values.scss +++ b/tests/inputs/values.scss @@ -49,3 +49,11 @@ multiline string."; $self2: &; content:'#{$self2}'; } + +@font-face { + unicode-range: U+26; + unicode-range: U+0-7F; + unicode-range: U+0025-00FF; + unicode-range: U+4??; + unicode-range: U+0025-00FF, U+4??; +} diff --git a/tests/outputs/values.css b/tests/outputs/values.css index ee227112..1550bc73 100644 --- a/tests/outputs/values.css +++ b/tests/outputs/values.css @@ -37,3 +37,10 @@ multiline string."; #self { content: "#self"; } + +@font-face { + unicode-range: U+26; + unicode-range: U+0-7F; + unicode-range: U+0025-00FF; + unicode-range: U+4??; + unicode-range: U+0025-00FF, U+4??; } diff --git a/tests/outputs_numbered/values.css b/tests/outputs_numbered/values.css index cc4d591a..29187ed7 100644 --- a/tests/outputs_numbered/values.css +++ b/tests/outputs_numbered/values.css @@ -38,3 +38,10 @@ multiline string."; /* line 47, inputs/values.scss */ #self { content: "#self"; } + +@font-face { + unicode-range: U+26; + unicode-range: U+0-7F; + unicode-range: U+0025-00FF; + unicode-range: U+4??; + unicode-range: U+0025-00FF, U+4??; } From 149f57267c3b5b2491c4ddad3bb367b09908b599 Mon Sep 17 00:00:00 2001 From: Cerdic Date: Mon, 3 Jun 2019 11:07:17 +0200 Subject: [PATCH 12/48] Fix https://github.com/leafo/scssphp/issues/507 : support for unicode range syntax with wildcards --- src/Parser.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Parser.php b/src/Parser.php index 076f1620..9c1129b6 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -1506,6 +1506,12 @@ protected function value(&$out) return true; } + // unicode range with wildcards + if ($this->literal('U+', 2) && $this->match('([0-9A-F]+\?*)(-([0-9A-F]+))?', $m, false)) { + $out = [Type::T_KEYWORD, 'U+' . $m[0]]; + return true; + } + if ($this->keyword($keyword, false)) { if ($this->func($keyword, $out)) { return true; From 0f8238ec90cb561867e3eba3803f847e0ae3ed8a Mon Sep 17 00:00:00 2001 From: Cerdic Date: Mon, 3 Jun 2019 14:28:34 +0200 Subject: [PATCH 13/48] Unit test for supporting unicode high range chars in selector name (https://github.com/leafo/scssphp/issues/531) --- tests/inputs/selectors.scss | 10 ++++++++++ tests/outputs/selectors.css | 7 +++++++ tests/outputs_numbered/selectors.css | 7 +++++++ 3 files changed, 24 insertions(+) diff --git a/tests/inputs/selectors.scss b/tests/inputs/selectors.scss index 36ae5a2b..0b9092c2 100644 --- a/tests/inputs/selectors.scss +++ b/tests/inputs/selectors.scss @@ -287,3 +287,13 @@ ul, ol { display: block; } } + +// unicode +.👤 { + display: inline-block; +} + +.❮ { + display:inline; + content:'↦'; +} diff --git a/tests/outputs/selectors.css b/tests/outputs/selectors.css index 64d55926..66a6ab15 100644 --- a/tests/outputs/selectors.css +++ b/tests/outputs/selectors.css @@ -370,3 +370,10 @@ span a, p a, div a { ul ul, ul ol, ol ul, ol ol { display: block; } + +.👤 { + display: inline-block; } + +.❮ { + display: inline; + content: '↦'; } diff --git a/tests/outputs_numbered/selectors.css b/tests/outputs_numbered/selectors.css index d2572233..7b6a8571 100644 --- a/tests/outputs_numbered/selectors.css +++ b/tests/outputs_numbered/selectors.css @@ -407,3 +407,10 @@ span a, p a, div a { ul ul, ul ol, ol ul, ol ol { display: block; } +/* line 292, inputs/selectors.scss */ +.👤 { + display: inline-block; } +/* line 296, inputs/selectors.scss */ +.❮ { + display: inline; + content: '↦'; } From 65a799584fb1812d8d97a90a9b228ee5bbdbb206 Mon Sep 17 00:00:00 2001 From: Cerdic Date: Mon, 3 Jun 2019 12:22:31 +0200 Subject: [PATCH 14/48] Fix https://github.com/leafo/scssphp/issues/531 : In CSS, identifiers (including element names, classes, and IDs in selectors) can contain only the characters [a-zA-Z0-9] and ISO 10646 characters U+00A0 and higher, plus the hyphen (-) and the underscore (_); they cannot start with a digit, two hyphens, or a hyphen followed by a digit. Identifiers can also contain escaped characters and any ISO 10646 character as a numeric code (see next item). --- src/Parser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Parser.php b/src/Parser.php index 076f1620..78f30475 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -2477,7 +2477,7 @@ protected function keyword(&$word, $eatWhitespace = null) { if ($this->match( $this->utf8 - ? '(([\pL\w_\-\*!"\']|[\\\\].)([\pL\w\-_"\']|[\\\\].)*)' + ? '(([\pL\w\x{00A0}-\x{10FFFF}_\-\*!"\']|[\\\\].)([\pL\w\x{00A0}-\x{10FFFF}\-_"\']|[\\\\].)*)' : '(([\w_\-\*!"\']|[\\\\].)([\w\-_"\']|[\\\\].)*)', $m, $eatWhitespace From 7e547154fe5864ed04e9e07655317384df082281 Mon Sep 17 00:00:00 2001 From: Cerdic Date: Mon, 3 Jun 2019 15:57:04 +0200 Subject: [PATCH 15/48] Unit test for https://github.com/leafo/scssphp/issues/565 : interpolation in comments --- tests/inputs/comments.scss | 8 ++++++++ tests/outputs/comments.css | 4 ++++ tests/outputs_numbered/comments.css | 4 ++++ 3 files changed, 16 insertions(+) diff --git a/tests/inputs/comments.scss b/tests/inputs/comments.scss index 3e641cc1..65cf8791 100644 --- a/tests/inputs/comments.scss +++ b/tests/inputs/comments.scss @@ -45,4 +45,12 @@ a { // коммент +$color:red; +$radius:2px; + +/* This is a comment with vars references: +color:#{$color} +radius:#{$radius} + */ + // end diff --git a/tests/outputs/comments.css b/tests/outputs/comments.css index f6f8d184..51088288 100644 --- a/tests/outputs/comments.css +++ b/tests/outputs/comments.css @@ -30,3 +30,7 @@ a { /* comment 6 */ } /* comment 7 */ /* коммент */ +/* This is a comment with vars references: +color:red +radius:2px + */ diff --git a/tests/outputs_numbered/comments.css b/tests/outputs_numbered/comments.css index 3669d1d8..874ae9f9 100644 --- a/tests/outputs_numbered/comments.css +++ b/tests/outputs_numbered/comments.css @@ -32,3 +32,7 @@ a { /* comment 6 */ } /* comment 7 */ /* коммент */ +/* This is a comment with vars references: +color:red +radius:2px + */ From b91f01d87d0fe17dfd873855b3c4ba439408761a Mon Sep 17 00:00:00 2001 From: Cerdic Date: Mon, 3 Jun 2019 15:58:41 +0200 Subject: [PATCH 16/48] Fix https://github.com/leafo/scssphp/issues/565 : parse and evaluate interpolation in comments --- src/Compiler.php | 6 +++++- src/Parser.php | 45 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/Compiler.php b/src/Compiler.php index e3f51d7c..bf269676 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -1349,7 +1349,11 @@ protected function compileBlock(Block $block) protected function compileComment($block) { $out = $this->makeOutputBlock(Type::T_COMMENT); - $out->lines[] = $block[1]; + if (is_string($block[1])) { + $out->lines[] = $block[1]; + } else { + $out->lines[] = $this->compileValue($block[1]); + } $this->scope->children[] = $out; } diff --git a/src/Parser.php b/src/Parser.php index 076f1620..9c286d49 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -980,12 +980,47 @@ protected function whitespace() 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]]); + // comment that are kept in the output CSS + $comment = []; + $endCommentCount = $this->count + strlen($m[1]); + // find interpolations in comment + $p = strpos($this->buffer, '#{', $this->count); + while ($p !== false && $p < $endCommentCount) { + $c = substr($this->buffer, $this->count, $p - $this->count); + $comment[] = $c; + $this->count = $p; + + $out = null; + if ($this->interpolation($out)) { + // keep right spaces in the following string part + if ($out[3]) { + while($this->buffer[$this->count-1] !== '}') { + $this->count--; + } + $out[3] = ''; + } + $comment[] = $out; + } + $p = strpos($this->buffer, '#{', $this->count); + } + // remaining part + $c = substr($this->buffer, $this->count, $endCommentCount - $this->count); + + if (!$comment) { + // single part static comment + $this->appendComment([Type::T_COMMENT, $c]); + } else { + $comment[] = $c; + var_dump($comment); + $this->appendComment([Type::T_COMMENT, [Type::T_STRING, '', $comment]]); + } $this->commentsSeen[$this->count] = true; + $this->count = $endCommentCount; + } else { + // comment that are ignored and not kept in the output css + $this->count += strlen($m[0]); } - - $this->count += strlen($m[0]); $gotWhite = true; } @@ -999,7 +1034,9 @@ protected function whitespace() */ protected function appendComment($comment) { - $comment[1] = substr(preg_replace(['/^\s+/m', '/^(.)/m'], ['', ' \1'], $comment[1]), 1); + if ($comment[0] === Type::T_COMMENT && is_string($comment[1])) { + $comment[1] = substr(preg_replace(['/^\s+/m', '/^(.)/m'], ['', ' \1'], $comment[1]), 1); + } $this->env->comments[] = $comment; } From fc89a5f4272066753b93ed1db8f884255f3488f2 Mon Sep 17 00:00:00 2001 From: Cerdic Date: Mon, 3 Jun 2019 17:37:02 +0200 Subject: [PATCH 17/48] Fix https://github.com/leafo/scssphp/issues/586 : ignore the backslash-newline sequence in strings as it is a continuation mark --- src/Parser.php | 5 +++++ tests/inputs/interpolation.scss | 13 ++++++++++++- tests/outputs/interpolation.css | 3 +++ tests/outputs/values.css | 7 ++----- tests/outputs_numbered/interpolation.css | 5 +++++ tests/outputs_numbered/values.css | 7 ++----- 6 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/Parser.php b/src/Parser.php index 076f1620..8346d5f8 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -1918,6 +1918,11 @@ protected function string(&$out) $content[] = $m[2] . "'"; } elseif ($this->literal("\\", 1, false)) { $content[] = $m[2] . "\\"; + } elseif ($this->literal("\r\n", 2, false) + || $this->matchChar("\r", false) + || $this->matchChar("\n", false) + || $this->matchChar("\f", false)) { + // this is a continuation escaping, to be ignored } else { $content[] = $m[2]; } diff --git a/tests/inputs/interpolation.scss b/tests/inputs/interpolation.scss index 45a5133b..a9419dd6 100644 --- a/tests/inputs/interpolation.scss +++ b/tests/inputs/interpolation.scss @@ -139,4 +139,15 @@ div { } .element { @include set-border(#{2px}); // compiles to "2px solid #000" -} \ No newline at end of file +} + +$foo: "\ +div > p, \ +div > h1 \ +"; + +#{$foo} { + & > strong { + color: red; + } +} diff --git a/tests/outputs/interpolation.css b/tests/outputs/interpolation.css index d9fd10e5..1b61ba1f 100644 --- a/tests/outputs/interpolation.css +++ b/tests/outputs/interpolation.css @@ -77,3 +77,6 @@ a.badge { .element { border: 2px solid #000; } + +div > p > strong, div > h1 > strong { + color: red; } diff --git a/tests/outputs/values.css b/tests/outputs/values.css index ee227112..ac6375ce 100644 --- a/tests/outputs/values.css +++ b/tests/outputs/values.css @@ -8,12 +8,9 @@ background-image: url(//host/image.png); dads: url(http://leafo.net); padding: 10px 10px 10px 10px, 3px 3px 3px; - textblock: "This is a \ -multiline block \ -#not { color: #eee;}"; + textblock: "This is a multiline block #not { color: #eee;}"; margin: 4, 3, 1; - content: "This is a \ -multiline string."; + content: "This is a multiline string."; border-radius: -1px -1px -1px black; grid-template-columns: [avatar] 2fr [body] 6fr; } diff --git a/tests/outputs_numbered/interpolation.css b/tests/outputs_numbered/interpolation.css index da509491..256dde01 100644 --- a/tests/outputs_numbered/interpolation.css +++ b/tests/outputs_numbered/interpolation.css @@ -96,3 +96,8 @@ a.badge { /* line 140, inputs/interpolation.scss */ .element { border: 2px solid #000; } +/* line 149, inputs/interpolation.scss */ +/* line 150, inputs/interpolation.scss */ + +div > p > strong, div > h1 > strong { + color: red; } diff --git a/tests/outputs_numbered/values.css b/tests/outputs_numbered/values.css index cc4d591a..980d0f06 100644 --- a/tests/outputs_numbered/values.css +++ b/tests/outputs_numbered/values.css @@ -9,12 +9,9 @@ background-image: url(//host/image.png); dads: url(http://leafo.net); padding: 10px 10px 10px 10px, 3px 3px 3px; - textblock: "This is a \ -multiline block \ -#not { color: #eee;}"; + textblock: "This is a multiline block #not { color: #eee;}"; margin: 4, 3, 1; - content: "This is a \ -multiline string."; + content: "This is a multiline string."; border-radius: -1px -1px -1px black; grid-template-columns: [avatar] 2fr [body] 6fr; } /* line 23, inputs/values.scss */ From 0ca337762d7a123c1f5cab245a532f9db78de428 Mon Sep 17 00:00:00 2001 From: Cerdic Date: Tue, 4 Jun 2019 09:19:37 +0200 Subject: [PATCH 18/48] Fix for infinite loop with a non interpolation #{ sequence, add this to the test case and a forgotten var_dump --- src/Parser.php | 5 ++++- tests/inputs/comments.scss | 1 + tests/outputs/comments.css | 1 + tests/outputs_numbered/comments.css | 1 + 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Parser.php b/src/Parser.php index 9c286d49..db96cfa4 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -1001,6 +1001,10 @@ protected function whitespace() } $comment[] = $out; } + else { + $comment[] = substr($this->buffer, $this->count, 2); + $this->count += 2; + } $p = strpos($this->buffer, '#{', $this->count); } @@ -1012,7 +1016,6 @@ protected function whitespace() $this->appendComment([Type::T_COMMENT, $c]); } else { $comment[] = $c; - var_dump($comment); $this->appendComment([Type::T_COMMENT, [Type::T_STRING, '', $comment]]); } $this->commentsSeen[$this->count] = true; diff --git a/tests/inputs/comments.scss b/tests/inputs/comments.scss index 65cf8791..10ff670e 100644 --- a/tests/inputs/comments.scss +++ b/tests/inputs/comments.scss @@ -50,6 +50,7 @@ $radius:2px; /* This is a comment with vars references: color:#{$color} +and a fake #{ doing nothing radius:#{$radius} */ diff --git a/tests/outputs/comments.css b/tests/outputs/comments.css index 51088288..6cafd02e 100644 --- a/tests/outputs/comments.css +++ b/tests/outputs/comments.css @@ -32,5 +32,6 @@ a { /* коммент */ /* This is a comment with vars references: color:red +and a fake #{ doing nothing radius:2px */ diff --git a/tests/outputs_numbered/comments.css b/tests/outputs_numbered/comments.css index 874ae9f9..7d751889 100644 --- a/tests/outputs_numbered/comments.css +++ b/tests/outputs_numbered/comments.css @@ -34,5 +34,6 @@ a { /* коммент */ /* This is a comment with vars references: color:red +and a fake #{ doing nothing radius:2px */ From 6b0d45fa738e527b1f2a6662621ff2ab22de7902 Mon Sep 17 00:00:00 2001 From: Cerdic Date: Tue, 4 Jun 2019 10:59:50 +0200 Subject: [PATCH 19/48] Test case for at-root variable scoping --- tests/inputs/at_root.scss | 12 ++++++++++++ tests/outputs/at_root.css | 4 ++++ tests/outputs_numbered/at_root.css | 6 ++++++ 3 files changed, 22 insertions(+) diff --git a/tests/inputs/at_root.scss b/tests/inputs/at_root.scss index 86d3f559..7e4dbc0b 100644 --- a/tests/inputs/at_root.scss +++ b/tests/inputs/at_root.scss @@ -220,3 +220,15 @@ $table-padding: 10px !default; text-decoration: none; } } + +// variable scoping +$test1: 'foo'; +.any { + $test2: 'bar'; + @at-root { + .other { + content: "#{$test2}"; + content: "#{$test1}"; + } + } +} diff --git a/tests/outputs/at_root.css b/tests/outputs/at_root.css index c80f3594..6fe091bf 100644 --- a/tests/outputs/at_root.css +++ b/tests/outputs/at_root.css @@ -107,3 +107,7 @@ a.badge { .btn:hover, .btn:focus { text-decoration: none; } + +.other { + content: "bar"; + content: "foo"; } diff --git a/tests/outputs_numbered/at_root.css b/tests/outputs_numbered/at_root.css index d57926ab..84d79764 100644 --- a/tests/outputs_numbered/at_root.css +++ b/tests/outputs_numbered/at_root.css @@ -160,3 +160,9 @@ a.badge:hover, a.badge:focus { .btn:hover, .btn:focus { text-decoration: none; } +/* line 226, inputs/at_root.scss */ +/* line 229, inputs/at_root.scss */ + +.other { + content: "bar"; + content: "foo"; } From f4444fcc357f4c8250c0cc3fa393f25f9b0712b7 Mon Sep 17 00:00:00 2001 From: Cerdic Date: Tue, 4 Jun 2019 11:03:23 +0200 Subject: [PATCH 20/48] Fix at-root vars scoping and https://github.com/leafo/scssphp/issues/380 : you should keep the full store env in at-root env, not removing parents but only their block and selectors when they are not matching the with condition --- src/Compiler.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Compiler.php b/src/Compiler.php index e3f51d7c..6ae4d18d 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -1147,10 +1147,14 @@ protected function filterWithout($envs, $without) foreach ($envs as $e) { if ($e->block && $this->isWithout($without, $e->block)) { - continue; + $ec = clone $e; + $ec->block = null; + $ec->selectors = []; + $filtered[] = $ec; + } + else { + $filtered[] = $e; } - - $filtered[] = $e; } return $this->extractEnv($filtered); From b69e8802b7a9e5c883db01eb7cb66cc40af07c08 Mon Sep 17 00:00:00 2001 From: Cerdic Date: Tue, 4 Jun 2019 11:38:05 +0200 Subject: [PATCH 21/48] PSR2 --- src/Parser.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Parser.php b/src/Parser.php index db96cfa4..15a3d719 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -994,14 +994,13 @@ protected function whitespace() if ($this->interpolation($out)) { // keep right spaces in the following string part if ($out[3]) { - while($this->buffer[$this->count-1] !== '}') { + while ($this->buffer[$this->count-1] !== '}') { $this->count--; } $out[3] = ''; } $comment[] = $out; - } - else { + } else { $comment[] = substr($this->buffer, $this->count, 2); $this->count += 2; } From abb02a443a04d3ce62f17d02a5fa662a8182a874 Mon Sep 17 00:00:00 2001 From: Cerdic Date: Tue, 4 Jun 2019 11:40:52 +0200 Subject: [PATCH 22/48] Test case for https://github.com/leafo/scssphp/issues/479 --- tests/inputs/comments.scss | 10 ++++++++++ tests/outputs/comments.css | 5 +++++ tests/outputs_numbered/comments.css | 7 +++++++ 3 files changed, 22 insertions(+) diff --git a/tests/inputs/comments.scss b/tests/inputs/comments.scss index 10ff670e..31712ac7 100644 --- a/tests/inputs/comments.scss +++ b/tests/inputs/comments.scss @@ -55,3 +55,13 @@ radius:#{$radius} */ // end + +// comment position in blocks +.textimage-background-fullheight { + .background { + /* @noflip */ + display: block; + /* hop */ + width: 100%; + } +} diff --git a/tests/outputs/comments.css b/tests/outputs/comments.css index 6cafd02e..c66b30bf 100644 --- a/tests/outputs/comments.css +++ b/tests/outputs/comments.css @@ -35,3 +35,8 @@ color:red and a fake #{ doing nothing radius:2px */ +.textimage-background-fullheight .background { + /* @noflip */ + display: block; + /* hop */ + width: 100%; } diff --git a/tests/outputs_numbered/comments.css b/tests/outputs_numbered/comments.css index 7d751889..f7256abb 100644 --- a/tests/outputs_numbered/comments.css +++ b/tests/outputs_numbered/comments.css @@ -37,3 +37,10 @@ color:red and a fake #{ doing nothing radius:2px */ +/* line 60, inputs/comments.scss */ +/* line 61, inputs/comments.scss */ + .textimage-background-fullheight .background { + /* @noflip */ + display: block; + /* hop */ + width: 100%; } From 8020c8128c83bd2d8ae65b0a4709d9fa9cb89420 Mon Sep 17 00:00:00 2001 From: Cerdic Date: Tue, 4 Jun 2019 11:37:38 +0200 Subject: [PATCH 23/48] Fix https://github.com/leafo/scssphp/issues/479: ensure comments starting a block will be in this block even if previous is empty --- src/Parser.php | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/Parser.php b/src/Parser.php index 15a3d719..f99e231e 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -662,9 +662,12 @@ protected function parseChunk() } // opening css block - if ($this->selectors($selectors) && $this->matchChar('{')) { + if ($this->selectors($selectors) && $this->matchChar('{', false)) { $this->pushBlock($selectors, $s); - + if ($this->eatWhiteDefault) { + $this->whitespace(); + $this->append(null); // collect comments at the begining if needed + } return true; } @@ -1051,15 +1054,17 @@ protected function appendComment($comment) */ protected function append($statement, $pos = null) { - if ($pos !== null) { - list($line, $column) = $this->getSourcePosition($pos); + if (! is_null($statement)) { + if ($pos !== null) { + list($line, $column) = $this->getSourcePosition($pos); - $statement[static::SOURCE_LINE] = $line; - $statement[static::SOURCE_COLUMN] = $column; - $statement[static::SOURCE_INDEX] = $this->sourceIndex; - } + $statement[static::SOURCE_LINE] = $line; + $statement[static::SOURCE_COLUMN] = $column; + $statement[static::SOURCE_INDEX] = $this->sourceIndex; + } - $this->env->children[] = $statement; + $this->env->children[] = $statement; + } $comments = $this->env->comments; From b787f45e4ecf9b6d7e13344801531d46e2ed7e5f Mon Sep 17 00:00:00 2001 From: Cerdic Date: Tue, 4 Jun 2019 16:18:20 +0200 Subject: [PATCH 24/48] Test case for is-superselector() function https://github.com/leafo/scssphp/issues/310 --- tests/inputs/selector_functions.scss | 35 ++++++++++++++++ tests/outputs/selector_functions.css | 40 +++++++++++++++++++ tests/outputs_numbered/selector_functions.css | 40 +++++++++++++++++++ 3 files changed, 115 insertions(+) diff --git a/tests/inputs/selector_functions.scss b/tests/inputs/selector_functions.scss index e97336e2..854c6b77 100644 --- a/tests/inputs/selector_functions.scss +++ b/tests/inputs/selector_functions.scss @@ -12,3 +12,38 @@ } } +.is-superselector { + content: is-superselector(".is-superselector", ".selector"); /* false */ +} +.is-superselector1 { + content: is-superselector("a", "a.disabled"); /*true*/ +} +.is-superselector2 { + content: is-superselector("a", "a:not(:visited)"); /*true*/ +} + +.is-superselector3 { + content: is-superselector("a", "a[href]"); /*true*/ +} + +.is-superselector4 { + content: is-superselector("a.disabled", "a"); /*false*/ +} + +.is-superselector5 { + content: is-superselector("a.disabled", "a.foo.disabled"); /* true*/ +} + +.is-superselector6 { + content: is-superselector("a.disabled", "a.foo#disabled"); /* false*/ +} + +.is-superselector7 { + content: is-superselector("a", "sidebar a"); /* true*/ +} +.is-superselector8 { + content: is-superselector("sidebar a", "a"); /* false*/ +} +.is-superselector9 { + content: is-superselector("a", "a"); /* true*/ +} \ No newline at end of file diff --git a/tests/outputs/selector_functions.css b/tests/outputs/selector_functions.css index d608dc9d..842e5d58 100644 --- a/tests/outputs/selector_functions.css +++ b/tests/outputs/selector_functions.css @@ -7,3 +7,43 @@ .main aside:hover, .sidebar p { content: ".main aside:hover"; content: ".sidebar p"; } + +.is-superselector { + content: false; + /* false */ } + +.is-superselector1 { + content: true; + /*true*/ } + +.is-superselector2 { + content: true; + /*true*/ } + +.is-superselector3 { + content: true; + /*true*/ } + +.is-superselector4 { + content: false; + /*false*/ } + +.is-superselector5 { + content: true; + /* true*/ } + +.is-superselector6 { + content: false; + /* false*/ } + +.is-superselector7 { + content: true; + /* true*/ } + +.is-superselector8 { + content: false; + /* false*/ } + +.is-superselector9 { + content: true; + /* true*/ } diff --git a/tests/outputs_numbered/selector_functions.css b/tests/outputs_numbered/selector_functions.css index 7bd5f1b4..1bcefb3e 100644 --- a/tests/outputs_numbered/selector_functions.css +++ b/tests/outputs_numbered/selector_functions.css @@ -8,3 +8,43 @@ .main aside:hover, .sidebar p { content: ".main aside:hover"; content: ".sidebar p"; } +/* line 15, inputs/selector_functions.scss */ +.is-superselector { + content: false; + /* false */ } +/* line 18, inputs/selector_functions.scss */ +.is-superselector1 { + content: true; + /*true*/ } +/* line 21, inputs/selector_functions.scss */ +.is-superselector2 { + content: true; + /*true*/ } +/* line 25, inputs/selector_functions.scss */ +.is-superselector3 { + content: true; + /*true*/ } +/* line 29, inputs/selector_functions.scss */ +.is-superselector4 { + content: false; + /*false*/ } +/* line 33, inputs/selector_functions.scss */ +.is-superselector5 { + content: true; + /* true*/ } +/* line 37, inputs/selector_functions.scss */ +.is-superselector6 { + content: false; + /* false*/ } +/* line 41, inputs/selector_functions.scss */ +.is-superselector7 { + content: true; + /* true*/ } +/* line 44, inputs/selector_functions.scss */ +.is-superselector8 { + content: false; + /* false*/ } +/* line 47, inputs/selector_functions.scss */ +.is-superselector9 { + content: true; + /* true*/ } From a5f6c08db73275f8bddce7b8b864d09b51bfc3b6 Mon Sep 17 00:00:00 2001 From: Cerdic Date: Tue, 4 Jun 2019 16:18:46 +0200 Subject: [PATCH 25/48] is-superselector() implementation https://github.com/leafo/scssphp/issues/310 --- src/Compiler.php | 110 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/src/Compiler.php b/src/Compiler.php index 4b801256..6ff768b8 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -6029,4 +6029,114 @@ protected function libInspect($args) return $args[0]; } + + /** + * Preprocess selector args + * @param $arg + * @return array|bool + */ + protected function getSelectorArg($arg) + { + static $parser = null; + if (is_null($parser)) { + $parser = $this->parserFactory(__METHOD__); + } + $arg = $this->libUnquote([$arg]); + $arg = $this->compileValue($arg); + $parsedSelector = []; + if ($parser->parseSelector($arg, $parsedSelector)) { + $selector = $this->evalSelectors($parsedSelector); + + return $selector; + } + + return false; + } + + protected static $libIsSuperselector = ['super', 'sub']; + protected function libIsSuperselector($args) + { + list($super, $sub) = $args; + $super = $this->getSelectorArg($super); + $sub = $this->getSelectorArg($sub); + + return $this->isSuperSelector($super, $sub); + } + + /** + * Test a $super selector again $sub + * @param array $super + * @param array $sub + * @return bool + */ + protected function isSuperSelector($super, $sub) { + // one and only one selector for each arg + if (! $super || count($super) !== 1) { + return false; + } + if (! $sub || count($sub) !== 1) { + return false; + } + $super = $this->glueFunctionSelectors($super); + $sub = $this->glueFunctionSelectors($sub); + $super = reset($super); + $sub = reset($sub); + + + $i = 0; + $nextMustMatch = false; + foreach ($super as $node) { + $compound = ''; + + array_walk_recursive( + $node, + function ($value, $key) use (&$compound) { + $compound .= $value; + } + ); + + if ($this->isImmediateRelationshipCombinator($compound)) { + if ($node !== $sub[$i]) { + return false; + } + $nextMustMatch = true; + $i++; + } + else { + while($iisSuperPart($node, $sub[$i])) { + if ($nextMustMatch) { + return false; + } + $i++; + } + if ($i >= count($sub)) { + return false; + } + $i++; + $nextMustMatch = false; + } + } + return true; + } + + /** + * Test a part of super selector again a part of sub selector + * @param array $superParts + * @param array $subParts + * @return bool + */ + protected function isSuperPart($superParts, $subParts) { + $i = 0; + foreach ($superParts as $superPart) { + while($i= count($subParts)) { + return false; + } + $i++; + } + return true; + } + } From fbcbb283a64b42ac521e0b15e055364bb2bd95b7 Mon Sep 17 00:00:00 2001 From: Cerdic Date: Tue, 4 Jun 2019 16:26:58 +0200 Subject: [PATCH 26/48] PSR2 --- src/Compiler.php | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/Compiler.php b/src/Compiler.php index 6ff768b8..fce46ec5 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -1151,8 +1151,7 @@ protected function filterWithout($envs, $without) $ec->block = null; $ec->selectors = []; $filtered[] = $ec; - } - else { + } else { $filtered[] = $e; } } @@ -6069,7 +6068,8 @@ protected function libIsSuperselector($args) * @param array $sub * @return bool */ - protected function isSuperSelector($super, $sub) { + protected function isSuperSelector($super, $sub) + { // one and only one selector for each arg if (! $super || count($super) !== 1) { return false; @@ -6089,10 +6089,10 @@ protected function isSuperSelector($super, $sub) { $compound = ''; array_walk_recursive( - $node, - function ($value, $key) use (&$compound) { - $compound .= $value; - } + $node, + function ($value, $key) use (&$compound) { + $compound .= $value; + } ); if ($this->isImmediateRelationshipCombinator($compound)) { @@ -6101,9 +6101,8 @@ function ($value, $key) use (&$compound) { } $nextMustMatch = true; $i++; - } - else { - while($iisSuperPart($node, $sub[$i])) { + } else { + while ($i < count($sub) && ! $this->isSuperPart($node, $sub[$i])) { if ($nextMustMatch) { return false; } @@ -6116,6 +6115,7 @@ function ($value, $key) use (&$compound) { $nextMustMatch = false; } } + return true; } @@ -6125,10 +6125,11 @@ function ($value, $key) use (&$compound) { * @param array $subParts * @return bool */ - protected function isSuperPart($superParts, $subParts) { + protected function isSuperPart($superParts, $subParts) + { $i = 0; foreach ($superParts as $superPart) { - while($i= count($subParts)) { @@ -6136,6 +6137,7 @@ protected function isSuperPart($superParts, $subParts) { } $i++; } + return true; } From 2da918461f95fbde090790ea4b0f6db64bd39ca3 Mon Sep 17 00:00:00 2001 From: Cerdic Date: Tue, 4 Jun 2019 16:45:05 +0200 Subject: [PATCH 27/48] Throw errors in case of invalid input selectors --- src/Compiler.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Compiler.php b/src/Compiler.php index fce46ec5..f4e6f8a1 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -6072,10 +6072,10 @@ protected function isSuperSelector($super, $sub) { // one and only one selector for each arg if (! $super || count($super) !== 1) { - return false; + $this->throwError("Invalid super selector for isSuperSelector()"); } if (! $sub || count($sub) !== 1) { - return false; + $this->throwError("Invalid sub selector for isSuperSelector()"); } $super = $this->glueFunctionSelectors($super); $sub = $this->glueFunctionSelectors($sub); From d5517d083bc77a951e5da6eeecd9244f1f853bf3 Mon Sep 17 00:00:00 2001 From: Cerdic Date: Tue, 4 Jun 2019 17:16:34 +0200 Subject: [PATCH 28/48] Test case for selector-append() function https://github.com/leafo/scssphp/issues/310 --- tests/inputs/selector_functions.scss | 18 +++++++++++++++++- tests/outputs/selector_functions.css | 12 ++++++++++++ tests/outputs_numbered/selector_functions.css | 12 ++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/tests/inputs/selector_functions.scss b/tests/inputs/selector_functions.scss index 854c6b77..d96e5bf1 100644 --- a/tests/inputs/selector_functions.scss +++ b/tests/inputs/selector_functions.scss @@ -46,4 +46,20 @@ } .is-superselector9 { content: is-superselector("a", "a"); /* true*/ -} \ No newline at end of file +} + +#{selector-append("a", ".disabled")} { + content:'a.disabled'; +} + +#{selector-append(".accordion", "__copy")} { + content:'.accordion__copy'; +} + +#{selector-append(".accordion", "__copy, __image")} { + content:'.accordion__copy, .accordion__image'; +} + +#{selector-append(".accordion, .slider", "__copy, __image")} { + content:'.accordion__copy, .slider__copy, .accordion__image, .slider__image'; +} diff --git a/tests/outputs/selector_functions.css b/tests/outputs/selector_functions.css index 842e5d58..0e95e838 100644 --- a/tests/outputs/selector_functions.css +++ b/tests/outputs/selector_functions.css @@ -47,3 +47,15 @@ .is-superselector9 { content: true; /* true*/ } + +a.disabled { + content: 'a.disabled'; } + +.accordion__copy { + content: '.accordion__copy'; } + +.accordion__copy, .accordion__image { + content: '.accordion__copy, .accordion__image'; } + +.accordion__copy, .slider__copy, .accordion__image, .slider__image { + content: '.accordion__copy, .slider__copy, .accordion__image, .slider__image'; } diff --git a/tests/outputs_numbered/selector_functions.css b/tests/outputs_numbered/selector_functions.css index 1bcefb3e..d29f439e 100644 --- a/tests/outputs_numbered/selector_functions.css +++ b/tests/outputs_numbered/selector_functions.css @@ -48,3 +48,15 @@ .is-superselector9 { content: true; /* true*/ } +/* line 51, inputs/selector_functions.scss */ +a.disabled { + content: 'a.disabled'; } +/* line 55, inputs/selector_functions.scss */ +.accordion__copy { + content: '.accordion__copy'; } +/* line 59, inputs/selector_functions.scss */ +.accordion__copy, .accordion__image { + content: '.accordion__copy, .accordion__image'; } +/* line 63, inputs/selector_functions.scss */ +.accordion__copy, .slider__copy, .accordion__image, .slider__image { + content: '.accordion__copy, .slider__copy, .accordion__image, .slider__image'; } From 3e85bd096b363609090b76e292868cd6a2712ea6 Mon Sep 17 00:00:00 2001 From: Cerdic Date: Tue, 4 Jun 2019 17:19:26 +0200 Subject: [PATCH 29/48] Implementation of selector-append() function https://github.com/leafo/scssphp/issues/310 --- src/Compiler.php | 68 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 3 deletions(-) diff --git a/src/Compiler.php b/src/Compiler.php index f4e6f8a1..05be51a2 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -6045,13 +6045,25 @@ protected function getSelectorArg($arg) $parsedSelector = []; if ($parser->parseSelector($arg, $parsedSelector)) { $selector = $this->evalSelectors($parsedSelector); + $gluedSelector = $this->glueFunctionSelectors($selector); - return $selector; + return $gluedSelector; } return false; } + /** + * Postprocess selector to output in right format + * @param $selectors + * @return string + */ + protected function formatOutputSelector($selectors) + { + $selectors = $this->collapseSelectors($selectors, true); + return $selectors; + } + protected static $libIsSuperselector = ['super', 'sub']; protected function libIsSuperselector($args) { @@ -6077,8 +6089,6 @@ protected function isSuperSelector($super, $sub) if (! $sub || count($sub) !== 1) { $this->throwError("Invalid sub selector for isSuperSelector()"); } - $super = $this->glueFunctionSelectors($super); - $sub = $this->glueFunctionSelectors($sub); $super = reset($super); $sub = reset($sub); @@ -6141,4 +6151,56 @@ protected function isSuperPart($superParts, $subParts) return true; } + protected static $libSelectorAppend = ['selector...']; + protected function libSelectorAppend($args) + { + if (count($args) < 1) { + $this->throwError("selector-append() needs at least 1 argument"); + } + + $selectors = array_map([$this, 'getSelectorArg'], $args); + return $this->formatOutputSelector($this->selectorAppend($selectors)); + } + + /** + * Append parts of the last selector in the list to the previous, recursively + * @param array $selectors + * @return array + * @throws CompilerException + */ + protected function selectorAppend($selectors) + { + $lastSelectors = array_pop($selectors); + if (!$lastSelectors) { + $this->throwError("Invalid selector list in selector-append()"); + } + + while (count($selectors)) { + $previousSelectors = array_pop($selectors); + if (!$previousSelectors) { + $this->throwError("Invalid selector list in selector-append()"); + } + + // do the trick, happening $lastSelector to $previousSelector + $appended = []; + foreach ($lastSelectors as $lastSelector) { + $previous = $previousSelectors; + foreach ($lastSelector as $lastSelectorParts) { + foreach ($lastSelectorParts as $lastSelectorPart) { + foreach ($previous as $i => $previousSelector) { + foreach ($previousSelector as $j => $previousSelectorParts) { + $previous[$i][$j][] = $lastSelectorPart; + } + } + } + } + foreach ($previous as $ps) { + $appended[] = $ps; + } + } + $lastSelectors = $appended; + } + + return $lastSelectors; + } } From 12f5c1c1416710537a5be38a6129a8d6cae00a47 Mon Sep 17 00:00:00 2001 From: Cerdic Date: Tue, 4 Jun 2019 19:21:52 +0200 Subject: [PATCH 30/48] Test case for selector-extend() function https://github.com/leafo/scssphp/issues/310 --- tests/inputs/selector_functions.scss | 11 +++++++++++ tests/outputs/selector_functions.css | 9 +++++++++ tests/outputs_numbered/selector_functions.css | 9 +++++++++ 3 files changed, 29 insertions(+) diff --git a/tests/inputs/selector_functions.scss b/tests/inputs/selector_functions.scss index d96e5bf1..7d120055 100644 --- a/tests/inputs/selector_functions.scss +++ b/tests/inputs/selector_functions.scss @@ -63,3 +63,14 @@ #{selector-append(".accordion, .slider", "__copy, __image")} { content:'.accordion__copy, .slider__copy, .accordion__image, .slider__image'; } + +#{selector-extend("a.disabled", "a", ".link")} { + content: "a.disabled, .link.disabled"; +} +#{selector-extend("a.disabled", "h1", "h2")} { + content: "a.disabled"; +} + +#{selector-extend(".guide .info", ".info", ".content nav.sidebar")} { + content: ".guide .info, .guide .content nav.sidebar, .content .guide nav.sidebar"; +} diff --git a/tests/outputs/selector_functions.css b/tests/outputs/selector_functions.css index 0e95e838..b3f7fc8b 100644 --- a/tests/outputs/selector_functions.css +++ b/tests/outputs/selector_functions.css @@ -59,3 +59,12 @@ a.disabled { .accordion__copy, .slider__copy, .accordion__image, .slider__image { content: '.accordion__copy, .slider__copy, .accordion__image, .slider__image'; } + +a.disabled, .link.disabled { + content: "a.disabled, .link.disabled"; } + +a.disabled { + content: "a.disabled"; } + +.guide .info, .guide .content nav.sidebar, .content .guide nav.sidebar { + content: ".guide .info, .guide .content nav.sidebar, .content .guide nav.sidebar"; } diff --git a/tests/outputs_numbered/selector_functions.css b/tests/outputs_numbered/selector_functions.css index d29f439e..edf19ca9 100644 --- a/tests/outputs_numbered/selector_functions.css +++ b/tests/outputs_numbered/selector_functions.css @@ -60,3 +60,12 @@ a.disabled { /* line 63, inputs/selector_functions.scss */ .accordion__copy, .slider__copy, .accordion__image, .slider__image { content: '.accordion__copy, .slider__copy, .accordion__image, .slider__image'; } +/* line 67, inputs/selector_functions.scss */ +a.disabled, .link.disabled { + content: "a.disabled, .link.disabled"; } +/* line 70, inputs/selector_functions.scss */ +a.disabled { + content: "a.disabled"; } +/* line 74, inputs/selector_functions.scss */ +.guide .info, .guide .content nav.sidebar, .content .guide nav.sidebar { + content: ".guide .info, .guide .content nav.sidebar, .content .guide nav.sidebar"; } From 2ae6d09ec015b5934c750bce1bf4d5351e97e48a Mon Sep 17 00:00:00 2001 From: Cerdic Date: Tue, 4 Jun 2019 19:23:25 +0200 Subject: [PATCH 31/48] Implementation of selector-extend() reusing the internal functions used for @extend https://github.com/leafo/scssphp/issues/310 --- src/Compiler.php | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/Compiler.php b/src/Compiler.php index 05be51a2..46878f4e 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -6203,4 +6203,38 @@ protected function selectorAppend($selectors) return $lastSelectors; } + + protected static $libSelectorExtend = ['selectors', 'extendee', 'extender']; + protected function libSelectorExtend($args) + { + list($selectors, $extendee, $extender) = $args; + $selectors = $this->getSelectorArg($selectors); + $extendee = $this->getSelectorArg($extendee); + $extender = $this->getSelectorArg($extender); + if (! $selectors || ! $extendee || ! $extender) { + $this->throwError("selector-extend() invalid arguments"); + } + + $saveExtends = $this->extends; + $saveExtendsMap = $this->extendsMap; + + $this->extends = []; + $this->extendsMap = []; + + foreach ($extendee as $es) { + // only use the first one + $this->pushExtends(reset($es), $extender, null); + } + + $extended = []; + foreach ($selectors as $selector) { + $extended[] = $selector; + $this->matchExtends($selector, $extended); + } + + $this->extends = $saveExtends; + $this->extendsMap = $saveExtendsMap; + + return $this->formatOutputSelector($extended); + } } From f97040c84887ca89d6d5f3f97fae0ae7479b27c3 Mon Sep 17 00:00:00 2001 From: Cerdic Date: Tue, 4 Jun 2019 19:34:08 +0200 Subject: [PATCH 32/48] Test case for selector-nest() function https://github.com/leafo/scssphp/issues/310 --- tests/inputs/selector_functions.scss | 16 ++++++++++++++++ tests/outputs/selector_functions.css | 12 ++++++++++++ tests/outputs_numbered/selector_functions.css | 12 ++++++++++++ 3 files changed, 40 insertions(+) diff --git a/tests/inputs/selector_functions.scss b/tests/inputs/selector_functions.scss index 7d120055..e2476e3b 100644 --- a/tests/inputs/selector_functions.scss +++ b/tests/inputs/selector_functions.scss @@ -74,3 +74,19 @@ #{selector-extend(".guide .info", ".info", ".content nav.sidebar")} { content: ".guide .info, .guide .content nav.sidebar, .content .guide nav.sidebar"; } + +#{selector-nest("ul", "li")} { + content: "ul li"; +} + +#{selector-nest(".alert, .warning", "p")} { + content: ".alert p, .warning p"; +} + +#{selector-nest(".alert", "&:hover")} { + content: ".alert:hover"; +} + +#{selector-nest(".accordion", "&__copy")} { + content: ".accordion__copy"; +} diff --git a/tests/outputs/selector_functions.css b/tests/outputs/selector_functions.css index b3f7fc8b..daf4c91f 100644 --- a/tests/outputs/selector_functions.css +++ b/tests/outputs/selector_functions.css @@ -68,3 +68,15 @@ a.disabled { .guide .info, .guide .content nav.sidebar, .content .guide nav.sidebar { content: ".guide .info, .guide .content nav.sidebar, .content .guide nav.sidebar"; } + +ul li { + content: "ul li"; } + +.alert p, .warning p { + content: ".alert p, .warning p"; } + +.alert:hover { + content: ".alert:hover"; } + +.accordion__copy { + content: ".accordion__copy"; } diff --git a/tests/outputs_numbered/selector_functions.css b/tests/outputs_numbered/selector_functions.css index edf19ca9..e5b5f390 100644 --- a/tests/outputs_numbered/selector_functions.css +++ b/tests/outputs_numbered/selector_functions.css @@ -69,3 +69,15 @@ a.disabled { /* line 74, inputs/selector_functions.scss */ .guide .info, .guide .content nav.sidebar, .content .guide nav.sidebar { content: ".guide .info, .guide .content nav.sidebar, .content .guide nav.sidebar"; } +/* line 78, inputs/selector_functions.scss */ +ul li { + content: "ul li"; } +/* line 82, inputs/selector_functions.scss */ +.alert p, .warning p { + content: ".alert p, .warning p"; } +/* line 86, inputs/selector_functions.scss */ +.alert:hover { + content: ".alert:hover"; } +/* line 90, inputs/selector_functions.scss */ +.accordion__copy { + content: ".accordion__copy"; } From 5a4e328cb563adf9fb9a61a59e086897649ef7b5 Mon Sep 17 00:00:00 2001 From: Cerdic Date: Tue, 4 Jun 2019 19:35:16 +0200 Subject: [PATCH 33/48] Implement selector-nest() using the internal compiler functions https://github.com/leafo/scssphp/issues/310 --- src/Compiler.php | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/Compiler.php b/src/Compiler.php index 46878f4e..6919606a 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -6151,7 +6151,7 @@ protected function isSuperPart($superParts, $subParts) return true; } - protected static $libSelectorAppend = ['selector...']; + //protected static $libSelectorAppend = ['selector...']; protected function libSelectorAppend($args) { if (count($args) < 1) { @@ -6237,4 +6237,27 @@ protected function libSelectorExtend($args) return $this->formatOutputSelector($extended); } + + //protected static $libSelectorNest = ['selector...']; + protected function libSelectorNest($args) + { + if (count($args) < 1) { + $this->throwError("selector-nest() needs at least 1 argument"); + } + + $selectorss = array_map([$this, 'getSelectorArg'], $args); + + $envs = []; + foreach ($selectorss as $selectors) { + $env = new Environment(); + $env->selectors = $selectors; + $envs[] = $env; + } + + $envs = array_reverse($envs); + $env = $this->extractEnv($envs); + $outputSelectors = $this->multiplySelectors($env); + + return $this->formatOutputSelector($outputSelectors); + } } From 5eba5b28dbc5e1180ecd80a86c88105f9eb0a9b6 Mon Sep 17 00:00:00 2001 From: Cerdic Date: Tue, 4 Jun 2019 19:44:18 +0200 Subject: [PATCH 34/48] Test case for selector-parse() function https://github.com/leafo/scssphp/issues/310 --- tests/inputs/selector_functions.scss | 8 ++++++++ tests/outputs/selector_functions.css | 6 ++++++ tests/outputs_numbered/selector_functions.css | 8 ++++++++ 3 files changed, 22 insertions(+) diff --git a/tests/inputs/selector_functions.scss b/tests/inputs/selector_functions.scss index e2476e3b..b9a0ebf4 100644 --- a/tests/inputs/selector_functions.scss +++ b/tests/inputs/selector_functions.scss @@ -90,3 +90,11 @@ #{selector-nest(".accordion", "&__copy")} { content: ".accordion__copy"; } + +@each $selector in selector-parse(".main aside:hover, .sidebar p") { + #{$selector} { + @each $part in $selector { + content: "#{$part}"; + } + } +} diff --git a/tests/outputs/selector_functions.css b/tests/outputs/selector_functions.css index daf4c91f..e07c918e 100644 --- a/tests/outputs/selector_functions.css +++ b/tests/outputs/selector_functions.css @@ -80,3 +80,9 @@ ul li { .accordion__copy { content: ".accordion__copy"; } + .main aside:hover { + content: ".main"; + content: "aside:hover"; } + .sidebar p { + content: ".sidebar"; + content: "p"; } diff --git a/tests/outputs_numbered/selector_functions.css b/tests/outputs_numbered/selector_functions.css index e5b5f390..87674525 100644 --- a/tests/outputs_numbered/selector_functions.css +++ b/tests/outputs_numbered/selector_functions.css @@ -81,3 +81,11 @@ ul li { /* line 90, inputs/selector_functions.scss */ .accordion__copy { content: ".accordion__copy"; } +/* line 95, inputs/selector_functions.scss */ +.main aside:hover { + content: ".main"; + content: "aside:hover"; } +/* line 95, inputs/selector_functions.scss */ +.sidebar p { + content: ".sidebar"; + content: "p"; } From e8d7c1dfbf53e1ae4850f36c62365dbc953721ae Mon Sep 17 00:00:00 2001 From: Cerdic Date: Tue, 4 Jun 2019 19:44:53 +0200 Subject: [PATCH 35/48] Implementation of selector-parse() function https://github.com/leafo/scssphp/issues/310 --- src/Compiler.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Compiler.php b/src/Compiler.php index 6919606a..8e876677 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -6260,4 +6260,13 @@ protected function libSelectorNest($args) return $this->formatOutputSelector($outputSelectors); } + + protected static $libSelectorParse = ['selectors']; + protected function libSelectorParse($args) + { + $selectors = reset($args); + $selectors = $this->getSelectorArg($selectors); + + return $this->formatOutputSelector($selectors); + } } From 191f794542ec98a0c983e2be35782579076d86be Mon Sep 17 00:00:00 2001 From: Cerdic Date: Tue, 4 Jun 2019 20:00:19 +0200 Subject: [PATCH 36/48] Test case for selector-replace() function https://github.com/leafo/scssphp/issues/310 --- tests/inputs/selector_functions.scss | 11 +++++++++++ tests/outputs/selector_functions.css | 9 +++++++++ tests/outputs_numbered/selector_functions.css | 9 +++++++++ 3 files changed, 29 insertions(+) diff --git a/tests/inputs/selector_functions.scss b/tests/inputs/selector_functions.scss index b9a0ebf4..c8da4a6a 100644 --- a/tests/inputs/selector_functions.scss +++ b/tests/inputs/selector_functions.scss @@ -98,3 +98,14 @@ } } } + +#{selector-replace("a.disabled", "a", ".link")} { + content: ".link.disabled"; +} +#{selector-replace("a.disabled", "h1", "h2")} { + content: "a.disabled"; +} + +#{selector-replace(".guide .info", ".info", ".content nav.sidebar")} { + content: ".guide .content nav.sidebar, .content .guide nav.sidebar"; +} diff --git a/tests/outputs/selector_functions.css b/tests/outputs/selector_functions.css index e07c918e..7cfe510d 100644 --- a/tests/outputs/selector_functions.css +++ b/tests/outputs/selector_functions.css @@ -86,3 +86,12 @@ ul li { .sidebar p { content: ".sidebar"; content: "p"; } + +.link.disabled { + content: ".link.disabled"; } + +a.disabled { + content: "a.disabled"; } + +.guide .content nav.sidebar, .content .guide nav.sidebar { + content: ".guide .content nav.sidebar, .content .guide nav.sidebar"; } diff --git a/tests/outputs_numbered/selector_functions.css b/tests/outputs_numbered/selector_functions.css index 87674525..e5b9a56e 100644 --- a/tests/outputs_numbered/selector_functions.css +++ b/tests/outputs_numbered/selector_functions.css @@ -89,3 +89,12 @@ ul li { .sidebar p { content: ".sidebar"; content: "p"; } +/* line 102, inputs/selector_functions.scss */ +.link.disabled { + content: ".link.disabled"; } +/* line 105, inputs/selector_functions.scss */ +a.disabled { + content: "a.disabled"; } +/* line 109, inputs/selector_functions.scss */ +.guide .content nav.sidebar, .content .guide nav.sidebar { + content: ".guide .content nav.sidebar, .content .guide nav.sidebar"; } From dfcfd6837123a4835a0462b32556beb454959e68 Mon Sep 17 00:00:00 2001 From: Cerdic Date: Tue, 4 Jun 2019 20:01:29 +0200 Subject: [PATCH 37/48] Implementation of selector-replace() that use a common function with selector-extend() as the logic is the same https://github.com/leafo/scssphp/issues/310 --- src/Compiler.php | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/src/Compiler.php b/src/Compiler.php index 8e876677..88972c36 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -6215,6 +6215,38 @@ protected function libSelectorExtend($args) $this->throwError("selector-extend() invalid arguments"); } + $extended = $this->extendOrReplaceSelectors($selectors, $extendee, $extender); + return $this->formatOutputSelector($extended); + } + + protected static $libSelectorReplace = ['selectors', 'original', 'replacement']; + protected function libSelectorReplace($args) + { + list($selectors, $original, $replacement) = $args; + $selectors = $this->getSelectorArg($selectors); + $original = $this->getSelectorArg($original); + $replacement = $this->getSelectorArg($replacement); + + if (! $selectors || ! $original || ! $replacement) { + $this->throwError("selector-replace() invalid arguments"); + } + + $replaced = $this->extendOrReplaceSelectors($selectors, $original, $replacement, true); + return $this->formatOutputSelector($replaced); + } + + /** + * Extend/replace in selectors + * used by selector-extend and selector-replace that use the same logic + * + * @param $selectors + * @param $extendee + * @param $extender + * @param bool $replace + * @return array + */ + protected function extendOrReplaceSelectors($selectors, $extendee, $extender, $replace = false) + { $saveExtends = $this->extends; $saveExtendsMap = $this->extendsMap; @@ -6228,14 +6260,21 @@ protected function libSelectorExtend($args) $extended = []; foreach ($selectors as $selector) { - $extended[] = $selector; + if (!$replace) { + $extended[] = $selector; + } + $n = count($extended); $this->matchExtends($selector, $extended); + // if didnt match, keep the original selector if we are in a replace operation + if ($replace and count($extended) === $n) { + $extended[] = $selector; + } } $this->extends = $saveExtends; $this->extendsMap = $saveExtendsMap; - return $this->formatOutputSelector($extended); + return $extended; } //protected static $libSelectorNest = ['selector...']; From e84f40faa9a466137970d2147946c29726a843ae Mon Sep 17 00:00:00 2001 From: Cerdic Date: Tue, 4 Jun 2019 20:34:35 +0200 Subject: [PATCH 38/48] Test case for simple-selectors() function https://github.com/leafo/scssphp/issues/310 --- tests/inputs/selector_functions.scss | 13 +++++++++++++ tests/outputs/selector_functions.css | 11 +++++++++++ tests/outputs_numbered/selector_functions.css | 11 +++++++++++ 3 files changed, 35 insertions(+) diff --git a/tests/inputs/selector_functions.scss b/tests/inputs/selector_functions.scss index c8da4a6a..d4f39502 100644 --- a/tests/inputs/selector_functions.scss +++ b/tests/inputs/selector_functions.scss @@ -109,3 +109,16 @@ #{selector-replace(".guide .info", ".info", ".content nav.sidebar")} { content: ".guide .content nav.sidebar, .content .guide nav.sidebar"; } + +#{simple-selectors("a.disabled")} { + /* a, .disabled */ + @each $part in simple-selectors("a.disabled") { + content:"#{$part}"; + } +} +#{simple-selectors("main.blog:after")} { + /* main, .blog, :after */ + @each $part in simple-selectors("main.blog:after") { + content:"#{$part}"; + } +} diff --git a/tests/outputs/selector_functions.css b/tests/outputs/selector_functions.css index 7cfe510d..dff37089 100644 --- a/tests/outputs/selector_functions.css +++ b/tests/outputs/selector_functions.css @@ -95,3 +95,14 @@ a.disabled { .guide .content nav.sidebar, .content .guide nav.sidebar { content: ".guide .content nav.sidebar, .content .guide nav.sidebar"; } + +a, .disabled { + /* a, .disabled */ + content: "a"; + content: ".disabled"; } + +main, .blog, :after { + /* main, .blog, :after */ + content: "main"; + content: ".blog"; + content: ":after"; } diff --git a/tests/outputs_numbered/selector_functions.css b/tests/outputs_numbered/selector_functions.css index e5b9a56e..d6564e62 100644 --- a/tests/outputs_numbered/selector_functions.css +++ b/tests/outputs_numbered/selector_functions.css @@ -98,3 +98,14 @@ a.disabled { /* line 109, inputs/selector_functions.scss */ .guide .content nav.sidebar, .content .guide nav.sidebar { content: ".guide .content nav.sidebar, .content .guide nav.sidebar"; } +/* line 113, inputs/selector_functions.scss */ +a, .disabled { + /* a, .disabled */ + content: "a"; + content: ".disabled"; } +/* line 119, inputs/selector_functions.scss */ +main, .blog, :after { + /* main, .blog, :after */ + content: "main"; + content: ".blog"; + content: ":after"; } From ccf99512dc2a8286e6fea0db3326faa34faf5a04 Mon Sep 17 00:00:00 2001 From: Cerdic Date: Tue, 4 Jun 2019 20:35:06 +0200 Subject: [PATCH 39/48] Implementation of simple-selectors() function https://github.com/leafo/scssphp/issues/310 --- src/Compiler.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/Compiler.php b/src/Compiler.php index 88972c36..aed31dcd 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -6308,4 +6308,23 @@ protected function libSelectorParse($args) return $this->formatOutputSelector($selectors); } + + protected static $libSimpleSelectors = ['selector']; + protected function libSimpleSelectors($args) + { + $selector = reset($args); + $selector = $this->getSelectorArg($selector); + + // remove selectors list layer, keeping the first one + $selector = reset($selector); + // remove parts list layer, keeping the first part + $part = reset($selector); + + $listParts = []; + foreach ($part as $p) { + $listParts[] = [Type::T_STRING, '', [$p]]; + } + + return [Type::T_LIST, ',', $listParts]; + } } From f12e4b753f53f5d30a184030dd0eee6cf18a8fd3 Mon Sep 17 00:00:00 2001 From: Anthon Pang Date: Tue, 4 Jun 2019 17:13:48 -0400 Subject: [PATCH 40/48] Improve "and"/"or" compatibility with reference implementation --- src/Compiler.php | 19 ++++++++++++---- tests/inputs/operators.scss | 34 ++++++++++++++++++++++++++++ tests/outputs/operators.css | 10 ++++++++ tests/outputs_numbered/operators.css | 10 ++++++++ 4 files changed, 69 insertions(+), 4 deletions(-) diff --git a/src/Compiler.php b/src/Compiler.php index 4b801256..ba6c8462 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -1151,8 +1151,7 @@ protected function filterWithout($envs, $without) $ec->block = null; $ec->selectors = []; $filtered[] = $ec; - } - else { + } else { $filtered[] = $e; } } @@ -2937,8 +2936,14 @@ protected function opAdd($left, $right) */ protected function opAnd($left, $right, $shouldEval) { + $truthy = ($left === static::$null || $right === static::$null) || + ($left === static::$false || $left === static::$true) && + ($right === static::$false || $right === static::$true); + if (! $shouldEval) { - return null; + if (! $truthy) { + return null; + } } if ($left !== static::$false && $left !== static::$null) { @@ -2959,8 +2964,14 @@ protected function opAnd($left, $right, $shouldEval) */ protected function opOr($left, $right, $shouldEval) { + $truthy = ($left === static::$null || $right === static::$null) || + ($left === static::$false || $left === static::$true) && + ($right === static::$false || $right === static::$true); + if (! $shouldEval) { - return null; + if (! $truthy) { + return null; + } } if ($left !== static::$false && $left !== static::$null) { diff --git a/tests/inputs/operators.scss b/tests/inputs/operators.scss index 31e65b17..096aeb37 100644 --- a/tests/inputs/operators.scss +++ b/tests/inputs/operators.scss @@ -191,3 +191,37 @@ $gridRowWidth: 20px; { width: (2.5 / $gridRowWidth * 100px * 1% ); } + +$and: true and false; +$or: false or true; + +$and1: null and true; // (null) +$and2: true and null; // (null) +$and3: null and false; // (null) +$and4: false and null; // false +$and5: one and null; // (null) +$and6: null and one; // (null) + +$or1: null or true; // true +$or2: true or null; // true +$or3: null or false; // false +$or4: false or null; // (null) +$or5: one or null; // one +$or6: null or one; // one + +#bools-test { + a: $and; + b: $or; + c: $and1; + d: $and2; + e: $and3; + f: $and4; + g: $and5; + h: $and6; + n: $or1; + o: $or2; + p: $or3; + q: $or4; + r: $or5; + s: $or6; +} diff --git a/tests/outputs/operators.css b/tests/outputs/operators.css index 50aaa8c0..1ec77ea0 100644 --- a/tests/outputs/operators.css +++ b/tests/outputs/operators.css @@ -173,3 +173,13 @@ div { .foo { width: 12.5%; } + +#bools-test { + a: false; + b: true; + f: false; + n: true; + o: true; + p: false; + r: one; + s: one; } diff --git a/tests/outputs_numbered/operators.css b/tests/outputs_numbered/operators.css index ad4d678e..9a956a2c 100644 --- a/tests/outputs_numbered/operators.css +++ b/tests/outputs_numbered/operators.css @@ -174,3 +174,13 @@ div { /* line 190, inputs/operators.scss */ .foo { width: 12.5%; } +/* line 212, inputs/operators.scss */ +#bools-test { + a: false; + b: true; + f: false; + n: true; + o: true; + p: false; + r: one; + s: one; } From ae7d9f69bb6107fc4e991b00b6246ea77670dfca Mon Sep 17 00:00:00 2001 From: Cerdic Date: Tue, 4 Jun 2019 23:33:11 +0200 Subject: [PATCH 41/48] Test case for selector-unify() function https://github.com/leafo/scssphp/issues/310 --- tests/inputs/selector_functions.scss | 24 +++++++++++++++++++ tests/outputs/selector_functions.css | 18 ++++++++++++++ tests/outputs_numbered/selector_functions.css | 18 ++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/tests/inputs/selector_functions.scss b/tests/inputs/selector_functions.scss index d4f39502..258aa8f9 100644 --- a/tests/inputs/selector_functions.scss +++ b/tests/inputs/selector_functions.scss @@ -122,3 +122,27 @@ content:"#{$part}"; } } + +#{selector-unify("a", ".disabled")} { + content:"a.disabled"; +} + +#{selector-unify("a.disabled", "a.outgoing")} { + content: "a.disabled.outgoing"; +} + +#{selector-unify("a", "h1")} { + content: "null"; +} + +#{selector-unify(".warning a", "main a")} { + content: ".warning main a, main .warning a"; +} + +#{selector-unify("main.warning a", "main a.disabled")} { + content: "main.warning a.disabled"; +} + +#{selector-unify("main .warning a", "main a")} { + content: "main .warning a"; +} diff --git a/tests/outputs/selector_functions.css b/tests/outputs/selector_functions.css index dff37089..b071ef6b 100644 --- a/tests/outputs/selector_functions.css +++ b/tests/outputs/selector_functions.css @@ -106,3 +106,21 @@ main, .blog, :after { content: "main"; content: ".blog"; content: ":after"; } + +a.disabled { + content: "a.disabled"; } + +a.disabled.outgoing { + content: "a.disabled.outgoing"; } + + { + content: "null"; } + +.warning main a, main .warning a { + content: ".warning main a, main .warning a"; } + +main.warning a.disabled { + content: "main.warning a.disabled"; } + +main .warning a { + content: "main .warning a"; } diff --git a/tests/outputs_numbered/selector_functions.css b/tests/outputs_numbered/selector_functions.css index d6564e62..c036813c 100644 --- a/tests/outputs_numbered/selector_functions.css +++ b/tests/outputs_numbered/selector_functions.css @@ -109,3 +109,21 @@ main, .blog, :after { content: "main"; content: ".blog"; content: ":after"; } +/* line 126, inputs/selector_functions.scss */ +a.disabled { + content: "a.disabled"; } +/* line 130, inputs/selector_functions.scss */ +a.disabled.outgoing { + content: "a.disabled.outgoing"; } +/* line 134, inputs/selector_functions.scss */ + { + content: "null"; } +/* line 138, inputs/selector_functions.scss */ +.warning main a, main .warning a { + content: ".warning main a, main .warning a"; } +/* line 142, inputs/selector_functions.scss */ +main.warning a.disabled { + content: "main.warning a.disabled"; } +/* line 146, inputs/selector_functions.scss */ +main .warning a { + content: "main .warning a"; } From 9591bdc28c4d482caf1e0bcfdb053f16752ebde1 Mon Sep 17 00:00:00 2001 From: Cerdic Date: Tue, 4 Jun 2019 23:42:10 +0200 Subject: [PATCH 42/48] selector-unify() implementation trying to get the spirit of the reference implementation https://github.com/leafo/scssphp/issues/310 --- src/Compiler.php | 230 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) diff --git a/src/Compiler.php b/src/Compiler.php index aed31dcd..d56f9181 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -6309,6 +6309,236 @@ protected function libSelectorParse($args) return $this->formatOutputSelector($selectors); } + protected static $libSelectorUnify = ['selectors1', 'selectors2']; + protected function libSelectorUnify($args) + { + list($selectors1, $selectors2) = $args; + $selectors1 = $this->getSelectorArg($selectors1); + $selectors2 = $this->getSelectorArg($selectors2); + + if (! $selectors1 || ! $selectors2) { + $this->throwError("selector-unify() invalid arguments"); + } + + // only consider the first compound of each + $compound1 = reset($selectors1); + $compound2 = reset($selectors2); + + // unify them and that's it + $unified = $this->unifyCompoundSelectors($compound1, $compound2); + + return $this->formatOutputSelector($unified); + } + + /** + * The selector-unify magic as its best + * (at least works as expected on test cases) + * + * @param array $compound1 + * @param array $compound2 + * @return array|mixed + */ + protected function unifyCompoundSelectors($compound1, $compound2) + { + + if (!count($compound1)) { + return $compound2; + } + if (!count($compound2)) { + return $compound1; + } + + // check that last part are compatible + $lastPart1 = array_pop($compound1); + $lastPart2 = array_pop($compound2); + $last = $this->mergeParts($lastPart1, $lastPart2); + if (!$last) { + return [[]]; + } + $unifiedCompound = [$last]; + $unifiedSelectors = [$unifiedCompound]; + + // do the rest + while (count($compound1) || count($compound2)) { + $part1 = end($compound1); + $part2 = end($compound2); + if ($part1 && ($match2 = $this->matchPartInCompound($part1, $compound2))) { + list($compound2, $part2, $after2) = $match2; + if ($after2) { + $unifiedSelectors = $this->prependSelectors($unifiedSelectors, $after2); + } + $c = $this->mergeParts($part1, $part2); + $unifiedSelectors = $this->prependSelectors($unifiedSelectors, [$c]); + $part1 = $part2 = null; + array_pop($compound1); + } + if ($part2 && ($match1 = $this->matchPartInCompound($part2, $compound1))) { + list($compound1, $part1, $after1) = $match1; + if ($after1) { + $unifiedSelectors = $this->prependSelectors($unifiedSelectors, $after1); + } + $c = $this->mergeParts($part2, $part1); + $unifiedSelectors = $this->prependSelectors($unifiedSelectors, [$c]); + $part1 = $part2 = null; + array_pop($compound2); + } + $new = []; + if ($part1 && $part2) { + array_pop($compound1); + array_pop($compound2); + $s = $this->prependSelectors($unifiedSelectors, [$part2]); + $new = array_merge($new, $this->prependSelectors($s, [$part1])); + $s = $this->prependSelectors($unifiedSelectors, [$part1]); + $new = array_merge($new, $this->prependSelectors($s, [$part2])); + } elseif ($part1) { + array_pop($compound1); + $new = array_merge($new, $this->prependSelectors($unifiedSelectors, [$part1])); + } elseif ($part2) { + array_pop($compound2); + $new = array_merge($new, $this->prependSelectors($unifiedSelectors, [$part2])); + } + if ($new) { + $unifiedSelectors = $new; + } + } + + return $unifiedSelectors; + } + + /** + * Prepend each selector from $selectors with $parts + * @param $selectors + * @param $parts + * @return array + */ + protected function prependSelectors($selectors, $parts) + { + $new = []; + foreach ($selectors as $compoundSelector) { + array_unshift($compoundSelector, $parts); + $new[] = $compoundSelector; + } + return $new; + } + + /** + * Try to find a matching part in a compound: + * - with same html tag name + * - with some class or id or something in common + * @param array $part + * @param array $compound + * @return array|bool + */ + protected function matchPartInCompound($part, $compound) + { + $partTag = $this->findTagName($part); + $before = $compound; + $after = []; + // try to find a match by tag name first + while (count($before)) { + $p = array_pop($before); + if ($partTag && $partTag !== '*' && $partTag == $this->findTagName($p)) { + return [$before, $p, $after]; + } + $after[] = $p; + } + // try again matching a non empty intersection and a compatible tagname + $before = $compound; + $after = []; + while (count($before)) { + $p = array_pop($before); + if ($this->checkCompatibleTags($partTag, $this->findTagName($p))) { + if (count(array_intersect($part, $p))) { + return [$before, $p, $after]; + } + } + $after[] = $p; + } + + return false; + } + + /** + * Merge two part list taking care that + * - the html tag is coming first - if any + * - the :something are coming last + * + * @param $parts1 + * @param $parts2 + * @return array + */ + protected function mergeParts($parts1, $parts2) + { + $tag1 = $this->findTagName($parts1); + $tag2 = $this->findTagName($parts2); + $tag = $this->checkCompatibleTags($tag1, $tag2); + // not compatible tags + if ($tag === false) { + return []; + } + + if ($tag) { + if ($tag1) { + $parts1 = array_diff($parts1, [$tag1]); + } + if ($tag2) { + $parts2 = array_diff($parts2, [$tag2]); + } + } + + $mergedParts = array_merge($parts1, $parts2); + $mergedOrderedParts = []; + foreach ($mergedParts as $part) { + if (strpos($part, ':') === 0) { + $mergedOrderedParts[] = $part; + } + } + $mergedParts = array_diff($mergedParts, $mergedOrderedParts); + $mergedParts = array_merge($mergedParts, $mergedOrderedParts); + if ($tag) { + array_unshift($mergedParts, $tag); + } + return $mergedParts; + } + + /** + * Check the compatibility between two tag names: + * if both are defined they should be identical or one has to be '*' + * + * @param $tag1 + * @param $tag2 + * @return array|bool + */ + protected function checkCompatibleTags($tag1, $tag2) + { + $tags = [$tag1, $tag2]; + $tags = array_unique($tags); + $tags = array_filter($tags); + if (count($tags)>1) { + $tags = array_diff($tags, ['*']); + } + // not compatible nodes + if (count($tags)>1) { + return false; + } + return $tags; + } + + /** + * Find the html tag name in a selector parts list + * @param array $parts + * @return mixed|string + */ + protected function findTagName($parts) + { + foreach ($parts as $part) { + if (! preg_match('/^[\[.:#%_-]/', $part)) { + return $part; + } + } + return ''; + } + protected static $libSimpleSelectors = ['selector']; protected function libSimpleSelectors($args) { From 0fb06259c3901fec20acbb86f88aed1f231eea44 Mon Sep 17 00:00:00 2001 From: Anthon Pang Date: Tue, 4 Jun 2019 18:36:38 -0400 Subject: [PATCH 43/48] formatting --- src/Compiler.php | 155 ++++++++++++++++++++++++++++++++++++----------- src/Parser.php | 14 ++++- 2 files changed, 133 insertions(+), 36 deletions(-) diff --git a/src/Compiler.php b/src/Compiler.php index d15b8fd8..d22d0a35 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -1352,11 +1352,7 @@ protected function compileBlock(Block $block) protected function compileComment($block) { $out = $this->makeOutputBlock(Type::T_COMMENT); - if (is_string($block[1])) { - $out->lines[] = $block[1]; - } else { - $out->lines[] = $this->compileValue($block[1]); - } + $out->lines[] = is_string($block[1]) ? $block[1] : $this->compileValue($block[1]); $this->scope->children[] = $out; } @@ -6043,18 +6039,24 @@ protected function libInspect($args) /** * Preprocess selector args - * @param $arg - * @return array|bool + * + * @param array $arg + * + * @return array|boolean */ protected function getSelectorArg($arg) { static $parser = null; + if (is_null($parser)) { $parser = $this->parserFactory(__METHOD__); } + $arg = $this->libUnquote([$arg]); $arg = $this->compileValue($arg); + $parsedSelector = []; + if ($parser->parseSelector($arg, $parsedSelector)) { $selector = $this->evalSelectors($parsedSelector); $gluedSelector = $this->glueFunctionSelectors($selector); @@ -6067,12 +6069,15 @@ protected function getSelectorArg($arg) /** * Postprocess selector to output in right format - * @param $selectors + * + * @param array $selectors + * * @return string */ protected function formatOutputSelector($selectors) { $selectors = $this->collapseSelectors($selectors, true); + return $selectors; } @@ -6080,6 +6085,7 @@ protected function formatOutputSelector($selectors) protected function libIsSuperselector($args) { list($super, $sub) = $args; + $super = $this->getSelectorArg($super); $sub = $this->getSelectorArg($sub); @@ -6088,9 +6094,11 @@ protected function libIsSuperselector($args) /** * Test a $super selector again $sub + * * @param array $super * @param array $sub - * @return bool + * + * @return boolean */ protected function isSuperSelector($super, $sub) { @@ -6098,15 +6106,17 @@ protected function isSuperSelector($super, $sub) if (! $super || count($super) !== 1) { $this->throwError("Invalid super selector for isSuperSelector()"); } + if (! $sub || count($sub) !== 1) { $this->throwError("Invalid sub selector for isSuperSelector()"); } + $super = reset($super); $sub = reset($sub); - $i = 0; $nextMustMatch = false; + foreach ($super as $node) { $compound = ''; @@ -6121,6 +6131,7 @@ function ($value, $key) use (&$compound) { if ($node !== $sub[$i]) { return false; } + $nextMustMatch = true; $i++; } else { @@ -6128,13 +6139,16 @@ function ($value, $key) use (&$compound) { if ($nextMustMatch) { return false; } + $i++; } + if ($i >= count($sub)) { return false; } - $i++; + $nextMustMatch = false; + $i++; } } @@ -6143,20 +6157,25 @@ function ($value, $key) use (&$compound) { /** * Test a part of super selector again a part of sub selector + * * @param array $superParts * @param array $subParts - * @return bool + * + * @return boolean */ protected function isSuperPart($superParts, $subParts) { $i = 0; + foreach ($superParts as $superPart) { while ($i < count($subParts) && $subParts[$i] !== $superPart) { $i++; } + if ($i >= count($subParts)) { return false; } + $i++; } @@ -6171,32 +6190,40 @@ protected function libSelectorAppend($args) } $selectors = array_map([$this, 'getSelectorArg'], $args); + return $this->formatOutputSelector($this->selectorAppend($selectors)); } /** * Append parts of the last selector in the list to the previous, recursively + * * @param array $selectors + * * @return array - * @throws CompilerException + * + * @throws \Leafo\ScssPhp\Exception\CompilerException */ protected function selectorAppend($selectors) { $lastSelectors = array_pop($selectors); - if (!$lastSelectors) { + + if (! $lastSelectors) { $this->throwError("Invalid selector list in selector-append()"); } while (count($selectors)) { $previousSelectors = array_pop($selectors); - if (!$previousSelectors) { + + if (! $previousSelectors) { $this->throwError("Invalid selector list in selector-append()"); } // do the trick, happening $lastSelector to $previousSelector $appended = []; + foreach ($lastSelectors as $lastSelector) { $previous = $previousSelectors; + foreach ($lastSelector as $lastSelectorParts) { foreach ($lastSelectorParts as $lastSelectorPart) { foreach ($previous as $i => $previousSelector) { @@ -6206,10 +6233,12 @@ protected function selectorAppend($selectors) } } } + foreach ($previous as $ps) { $appended[] = $ps; } } + $lastSelectors = $appended; } @@ -6220,14 +6249,17 @@ protected function selectorAppend($selectors) protected function libSelectorExtend($args) { list($selectors, $extendee, $extender) = $args; + $selectors = $this->getSelectorArg($selectors); $extendee = $this->getSelectorArg($extendee); $extender = $this->getSelectorArg($extender); + if (! $selectors || ! $extendee || ! $extender) { $this->throwError("selector-extend() invalid arguments"); } $extended = $this->extendOrReplaceSelectors($selectors, $extendee, $extender); + return $this->formatOutputSelector($extended); } @@ -6235,6 +6267,7 @@ protected function libSelectorExtend($args) protected function libSelectorReplace($args) { list($selectors, $original, $replacement) = $args; + $selectors = $this->getSelectorArg($selectors); $original = $this->getSelectorArg($original); $replacement = $this->getSelectorArg($replacement); @@ -6244,6 +6277,7 @@ protected function libSelectorReplace($args) } $replaced = $this->extendOrReplaceSelectors($selectors, $original, $replacement, true); + return $this->formatOutputSelector($replaced); } @@ -6251,10 +6285,11 @@ protected function libSelectorReplace($args) * Extend/replace in selectors * used by selector-extend and selector-replace that use the same logic * - * @param $selectors - * @param $extendee - * @param $extender - * @param bool $replace + * @param array $selectors + * @param array $extendee + * @param array $extender + * @param boolean $replace + * * @return array */ protected function extendOrReplaceSelectors($selectors, $extendee, $extender, $replace = false) @@ -6271,12 +6306,16 @@ protected function extendOrReplaceSelectors($selectors, $extendee, $extender, $r } $extended = []; + foreach ($selectors as $selector) { - if (!$replace) { + if (! $replace) { $extended[] = $selector; } + $n = count($extended); + $this->matchExtends($selector, $extended); + // if didnt match, keep the original selector if we are in a replace operation if ($replace and count($extended) === $n) { $extended[] = $selector; @@ -6296,12 +6335,13 @@ protected function libSelectorNest($args) $this->throwError("selector-nest() needs at least 1 argument"); } - $selectorss = array_map([$this, 'getSelectorArg'], $args); + $selectorsMap = array_map([$this, 'getSelectorArg'], $args); $envs = []; - foreach ($selectorss as $selectors) { + foreach ($selectorsMap as $selectors) { $env = new Environment(); $env->selectors = $selectors; + $envs[] = $env; } @@ -6325,6 +6365,7 @@ protected function libSelectorParse($args) protected function libSelectorUnify($args) { list($selectors1, $selectors2) = $args; + $selectors1 = $this->getSelectorArg($selectors1); $selectors2 = $this->getSelectorArg($selectors2); @@ -6352,11 +6393,11 @@ protected function libSelectorUnify($args) */ protected function unifyCompoundSelectors($compound1, $compound2) { - - if (!count($compound1)) { + if (! count($compound1)) { return $compound2; } - if (!count($compound2)) { + + if (! count($compound2)) { return $compound1; } @@ -6364,9 +6405,11 @@ protected function unifyCompoundSelectors($compound1, $compound2) $lastPart1 = array_pop($compound1); $lastPart2 = array_pop($compound2); $last = $this->mergeParts($lastPart1, $lastPart2); - if (!$last) { + + if (! $last) { return [[]]; } + $unifiedCompound = [$last]; $unifiedSelectors = [$unifiedCompound]; @@ -6374,41 +6417,55 @@ protected function unifyCompoundSelectors($compound1, $compound2) while (count($compound1) || count($compound2)) { $part1 = end($compound1); $part2 = end($compound2); + if ($part1 && ($match2 = $this->matchPartInCompound($part1, $compound2))) { list($compound2, $part2, $after2) = $match2; + if ($after2) { $unifiedSelectors = $this->prependSelectors($unifiedSelectors, $after2); } + $c = $this->mergeParts($part1, $part2); $unifiedSelectors = $this->prependSelectors($unifiedSelectors, [$c]); $part1 = $part2 = null; + array_pop($compound1); } + if ($part2 && ($match1 = $this->matchPartInCompound($part2, $compound1))) { list($compound1, $part1, $after1) = $match1; + if ($after1) { $unifiedSelectors = $this->prependSelectors($unifiedSelectors, $after1); } + $c = $this->mergeParts($part2, $part1); $unifiedSelectors = $this->prependSelectors($unifiedSelectors, [$c]); $part1 = $part2 = null; + array_pop($compound2); } + $new = []; + if ($part1 && $part2) { array_pop($compound1); array_pop($compound2); + $s = $this->prependSelectors($unifiedSelectors, [$part2]); $new = array_merge($new, $this->prependSelectors($s, [$part1])); $s = $this->prependSelectors($unifiedSelectors, [$part1]); $new = array_merge($new, $this->prependSelectors($s, [$part2])); } elseif ($part1) { array_pop($compound1); + $new = array_merge($new, $this->prependSelectors($unifiedSelectors, [$part1])); } elseif ($part2) { array_pop($compound2); + $new = array_merge($new, $this->prependSelectors($unifiedSelectors, [$part2])); } + if ($new) { $unifiedSelectors = $new; } @@ -6419,17 +6476,22 @@ protected function unifyCompoundSelectors($compound1, $compound2) /** * Prepend each selector from $selectors with $parts - * @param $selectors - * @param $parts + * + * @param array $selectors + * @param array $parts + * * @return array */ protected function prependSelectors($selectors, $parts) { $new = []; + foreach ($selectors as $compoundSelector) { array_unshift($compoundSelector, $parts); + $new[] = $compoundSelector; } + return $new; } @@ -6437,33 +6499,42 @@ protected function prependSelectors($selectors, $parts) * Try to find a matching part in a compound: * - with same html tag name * - with some class or id or something in common + * * @param array $part * @param array $compound - * @return array|bool + * + * @return array|boolean */ protected function matchPartInCompound($part, $compound) { $partTag = $this->findTagName($part); $before = $compound; $after = []; + // try to find a match by tag name first while (count($before)) { $p = array_pop($before); + if ($partTag && $partTag !== '*' && $partTag == $this->findTagName($p)) { return [$before, $p, $after]; } + $after[] = $p; } + // try again matching a non empty intersection and a compatible tagname $before = $compound; $after = []; + while (count($before)) { $p = array_pop($before); + if ($this->checkCompatibleTags($partTag, $this->findTagName($p))) { if (count(array_intersect($part, $p))) { return [$before, $p, $after]; } } + $after[] = $p; } @@ -6475,8 +6546,9 @@ protected function matchPartInCompound($part, $compound) * - the html tag is coming first - if any * - the :something are coming last * - * @param $parts1 - * @param $parts2 + * @param array $parts1 + * @param array $parts2 + * * @return array */ protected function mergeParts($parts1, $parts2) @@ -6484,6 +6556,7 @@ protected function mergeParts($parts1, $parts2) $tag1 = $this->findTagName($parts1); $tag2 = $this->findTagName($parts2); $tag = $this->checkCompatibleTags($tag1, $tag2); + // not compatible tags if ($tag === false) { return []; @@ -6493,6 +6566,7 @@ protected function mergeParts($parts1, $parts2) if ($tag1) { $parts1 = array_diff($parts1, [$tag1]); } + if ($tag2) { $parts2 = array_diff($parts2, [$tag2]); } @@ -6500,16 +6574,20 @@ protected function mergeParts($parts1, $parts2) $mergedParts = array_merge($parts1, $parts2); $mergedOrderedParts = []; + foreach ($mergedParts as $part) { if (strpos($part, ':') === 0) { $mergedOrderedParts[] = $part; } } + $mergedParts = array_diff($mergedParts, $mergedOrderedParts); $mergedParts = array_merge($mergedParts, $mergedOrderedParts); + if ($tag) { array_unshift($mergedParts, $tag); } + return $mergedParts; } @@ -6517,28 +6595,34 @@ protected function mergeParts($parts1, $parts2) * Check the compatibility between two tag names: * if both are defined they should be identical or one has to be '*' * - * @param $tag1 - * @param $tag2 - * @return array|bool + * @param string $tag1 + * @param string $tag2 + * + * @return array|boolean */ protected function checkCompatibleTags($tag1, $tag2) { $tags = [$tag1, $tag2]; $tags = array_unique($tags); $tags = array_filter($tags); + if (count($tags)>1) { $tags = array_diff($tags, ['*']); } + // not compatible nodes if (count($tags)>1) { return false; } + return $tags; } /** * Find the html tag name in a selector parts list + * * @param array $parts + * * @return mixed|string */ protected function findTagName($parts) @@ -6548,6 +6632,7 @@ protected function findTagName($parts) return $part; } } + return ''; } @@ -6559,10 +6644,12 @@ protected function libSimpleSelectors($args) // remove selectors list layer, keeping the first one $selector = reset($selector); + // remove parts list layer, keeping the first part $part = reset($selector); $listParts = []; + foreach ($part as $p) { $listParts[] = [Type::T_STRING, '', [$p]]; } diff --git a/src/Parser.php b/src/Parser.php index d39ebcbd..c05e4ee8 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -664,10 +664,12 @@ protected function parseChunk() // opening css block if ($this->selectors($selectors) && $this->matchChar('{', false)) { $this->pushBlock($selectors, $s); + if ($this->eatWhiteDefault) { $this->whitespace(); $this->append(null); // collect comments at the begining if needed } + return true; } @@ -986,46 +988,54 @@ protected function whitespace() // comment that are kept in the output CSS $comment = []; $endCommentCount = $this->count + strlen($m[1]); + // find interpolations in comment $p = strpos($this->buffer, '#{', $this->count); + while ($p !== false && $p < $endCommentCount) { $c = substr($this->buffer, $this->count, $p - $this->count); $comment[] = $c; $this->count = $p; - $out = null; + if ($this->interpolation($out)) { // keep right spaces in the following string part if ($out[3]) { while ($this->buffer[$this->count-1] !== '}') { $this->count--; } + $out[3] = ''; } + $comment[] = $out; } else { $comment[] = substr($this->buffer, $this->count, 2); + $this->count += 2; } $p = strpos($this->buffer, '#{', $this->count); } + // remaining part $c = substr($this->buffer, $this->count, $endCommentCount - $this->count); - if (!$comment) { + if (! $comment) { // single part static comment $this->appendComment([Type::T_COMMENT, $c]); } else { $comment[] = $c; $this->appendComment([Type::T_COMMENT, [Type::T_STRING, '', $comment]]); } + $this->commentsSeen[$this->count] = true; $this->count = $endCommentCount; } else { // comment that are ignored and not kept in the output css $this->count += strlen($m[0]); } + $gotWhite = true; } From 2e5e474b0495fbd3b7e44781460cfdd53b3142f2 Mon Sep 17 00:00:00 2001 From: Anthon Pang Date: Tue, 4 Jun 2019 18:44:35 -0400 Subject: [PATCH 44/48] Update ruby sass reference tests (from archived repo) --- tests/scss_test.rb | 151 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 121 insertions(+), 30 deletions(-) diff --git a/tests/scss_test.rb b/tests/scss_test.rb index 3d849d8a..41fa8e75 100644 --- a/tests/scss_test.rb +++ b/tests/scss_test.rb @@ -1,4 +1,3 @@ -#!/usr/bin/env ruby # -*- coding: utf-8 -*- require File.dirname(__FILE__) + '/test_helper' @@ -1015,7 +1014,8 @@ def test_function_args end def test_disallowed_function_names - assert_warning(<; } SCSS assert(false, "Expected syntax error") rescue Sass::SyntaxError => e - assert_equal 'Invalid CSS after " *bar:baz ": expected ";", was "[fail]; }"', e.message + assert_equal 'Invalid CSS after " *bar:baz ": expected expression (e.g. 1px, bold), was "; }"', e.message assert_equal 2, e.sass_line end def test_uses_property_exception_with_colon_hack render <; } SCSS assert(false, "Expected syntax error") rescue Sass::SyntaxError => e - assert_equal 'Invalid CSS after " :bar:baz ": expected ";", was "[fail]; }"', e.message + assert_equal 'Invalid CSS after " :bar:baz ": expected expression (e.g. 1px, bold), was "; }"', e.message assert_equal 2, e.sass_line end @@ -3420,22 +3421,22 @@ def test_uses_rule_exception_with_dot_hack def test_uses_property_exception_with_space_after_name render <; } SCSS assert(false, "Expected syntax error") rescue Sass::SyntaxError => e - assert_equal 'Invalid CSS after " bar: baz ": expected ";", was "[fail]; }"', e.message + assert_equal 'Invalid CSS after " bar: baz ": expected expression (e.g. 1px, bold), was "; }"', e.message assert_equal 2, e.sass_line end def test_uses_property_exception_with_non_identifier_after_name render <; } SCSS assert(false, "Expected syntax error") rescue Sass::SyntaxError => e - assert_equal 'Invalid CSS after " bar:1px ": expected ";", was "[fail]; }"', e.message + assert_equal 'Invalid CSS after " bar:1px ": expected expression (e.g. 1px, bold), was "; }"', e.message assert_equal 2, e.sass_line end @@ -3649,6 +3650,40 @@ def test_raw_newline_warning # Regression + # Regression test for #2031. + def test_no_interpolation_warning_in_nested_selector + assert_no_warning {assert_equal(< Date: Tue, 4 Jun 2019 23:13:53 -0400 Subject: [PATCH 45/48] Abandoned https://getcomposer.org/doc/04-schema.md#abandoned --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 62153e7b..a60e7a2c 100644 --- a/composer.json +++ b/composer.json @@ -39,5 +39,6 @@ "/phpunit.xml.dist", "/tests" ] - } + }, + "abandoned": "scssphp/scssphp" } From 6cb8e1afd2c17d4a3cd849be3f56536b0e06a415 Mon Sep 17 00:00:00 2001 From: Cerdic Date: Wed, 5 Jun 2019 11:11:13 +0200 Subject: [PATCH 46/48] Archive information and giving the link to the new repo. (also removing travis and packagist linked that are outdated) --- README.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 53a4c7a4..f5b8be1b 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,20 @@ -# scssphp -### +# This repo has been archived -[![Build](https://travis-ci.org/leafo/scssphp.svg?branch=master)](http://travis-ci.org/leafo/scssphp) -[![License](https://poser.pugx.org/leafo/scssphp/license.svg)](https://packagist.org/packages/leafo/scssphp) + +#### Please go to https://github.com/scssphp/scssphp + +---- + +## scssphp + + +![License](https://poser.pugx.org/leafo/scssphp/license.svg) `scssphp` is a compiler for SCSS written in PHP. Checkout the homepage, , for directions on how to use. -## Running Tests +### Running Tests `scssphp` uses [PHPUnit](https://github.com/sebastianbergmann/phpunit) for testing. @@ -38,7 +44,7 @@ To enable the `scss` compatibility tests: TEST_SCSS_COMPAT=1 vendor/bin/phpunit tests -## Coding Standard +### Coding Standard `scssphp` source conforms to [PSR2](http://www.php-fig.org/psr/psr-2/). From b74bbdd5c58def0f9712694eee03bf6fe2d2f17d Mon Sep 17 00:00:00 2001 From: Anthon Pang Date: Tue, 18 Jun 2019 16:23:31 -0400 Subject: [PATCH 47/48] Remove links to the old github pages --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index f5b8be1b..d3fbf154 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,11 @@ ---- ## scssphp - ![License](https://poser.pugx.org/leafo/scssphp/license.svg) `scssphp` is a compiler for SCSS written in PHP. -Checkout the homepage, , for directions on how to use. - ### Running Tests `scssphp` uses [PHPUnit](https://github.com/sebastianbergmann/phpunit) for testing. From b9cdea3e42c3bcb1a9faafd04ccce4e8ec860ad9 Mon Sep 17 00:00:00 2001 From: Anthon Pang Date: Tue, 18 Jun 2019 16:46:17 -0400 Subject: [PATCH 48/48] Last php 5.4 compatible version --- composer.json | 2 +- scss.inc.php | 4 ++-- src/Version.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index a60e7a2c..eaa8e87b 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "psr-4": { "Leafo\\ScssPhp\\Test\\": "tests/" } }, "require": { - "php": "^5.6.0 || ^7" + "php": "^5.4.0 || ^7" }, "require-dev": { "squizlabs/php_codesniffer": "~2.5", diff --git a/scss.inc.php b/scss.inc.php index 8a3cfb83..2a5f0774 100644 --- a/scss.inc.php +++ b/scss.inc.php @@ -1,6 +1,6 @@