diff --git a/lib/scssphp/Block/CallableBlock.php b/lib/scssphp/Block/CallableBlock.php index a18a87c2add..9b32d8ce757 100644 --- a/lib/scssphp/Block/CallableBlock.php +++ b/lib/scssphp/Block/CallableBlock.php @@ -14,6 +14,7 @@ namespace ScssPhp\ScssPhp\Block; use ScssPhp\ScssPhp\Block; use ScssPhp\ScssPhp\Compiler\Environment; +use ScssPhp\ScssPhp\Node\Number; /** * @internal @@ -26,7 +27,7 @@ class CallableBlock extends Block public $name; /** - * @var array|null + * @var list|null */ public $args; diff --git a/lib/scssphp/Compiler.php b/lib/scssphp/Compiler.php index ecafc8cb953..d4e7c68961a 100644 --- a/lib/scssphp/Compiler.php +++ b/lib/scssphp/Compiler.php @@ -458,10 +458,33 @@ class Compiler } /** - * Compile scss + * Compiles the provided scss file into CSS. + * + * @param string $path + * + * @return CompilationResult + * + * @throws SassException when the source fails to compile + */ + public function compileFile($path) + { + $source = file_get_contents($path); + + if ($source === false) { + throw new \RuntimeException('Could not read the file content'); + } + + return $this->compileString($source, $path); + } + + /** + * Compiles the provided scss source code into CSS. + * + * If provided, the path is considered to be the path from which the source code comes + * from, which will be used to resolve relative imports. * * @param string $source - * @param string|null $path + * @param string|null $path The path for the source, used to resolve relative imports * * @return CompilationResult * @@ -548,7 +571,7 @@ class Compiler $sourceMap = null; - if (! empty($out) && $this->sourceMap && $this->sourceMap !== self::SOURCE_MAP_NONE) { + if (! empty($out) && $this->sourceMap !== self::SOURCE_MAP_NONE && $this->sourceMap) { assert($sourceMapGenerator !== null); $sourceMap = $sourceMapGenerator->generateJson($prefix); $sourceMapUrl = null; @@ -1508,6 +1531,7 @@ class Compiler // start from the root while ($scope->parent && $scope->parent->type !== Type::T_ROOT) { array_unshift($childStash, $scope); + \assert($scope->parent !== null); $scope = $scope->parent; } @@ -2090,6 +2114,11 @@ class Compiler foreach ($selector as $node) { $compound = ''; + if (!is_array($node)) { + $output[] = $node; + continue; + } + array_walk_recursive( $node, function ($value, $key) use (&$compound) { @@ -2124,12 +2153,16 @@ class Compiler foreach ($selector as $node) { $compound = ''; - array_walk_recursive( - $node, - function ($value, $key) use (&$compound) { - $compound .= $value; - } - ); + if (!is_array($node)) { + $compound .= $node; + } else { + array_walk_recursive( + $node, + function ($value, $key) use (&$compound) { + $compound .= $value; + } + ); + } if ($this->isImmediateRelationshipCombinator($compound)) { if (\count($output)) { @@ -2885,7 +2918,7 @@ class Compiler { if (isset($child[Parser::SOURCE_LINE])) { $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->sourceLine = $child[Parser::SOURCE_LINE]; $this->sourceColumn = isset($child[Parser::SOURCE_COLUMN]) ? $child[Parser::SOURCE_COLUMN] : -1; } elseif (\is_array($child) && isset($child[1]->sourceLine) && $child[1] instanceof Block) { $this->sourceIndex = $child[1]->sourceIndex; @@ -4529,8 +4562,10 @@ EOL; return $colorName; } - if (is_numeric($alpha)) { + if (\is_int($alpha) || \is_float($alpha)) { $a = new Number($alpha, ''); + } elseif (is_numeric($alpha)) { + $a = new Number((float) $alpha, ''); } else { $a = $alpha; } @@ -5806,13 +5841,13 @@ EOL; if (! \is_null($file)) { if (\is_array($dir)) { - $callableDescription = (\is_object($dir[0]) ? \get_class($dir[0]) : $dir[0]).'::'.$dir[1]; + $callableDescription = (\is_object($dir[0]) ? \get_class($dir[0]) : $dir[0]) . '::' . $dir[1]; } elseif ($dir instanceof \Closure) { $r = new \ReflectionFunction($dir); if (false !== strpos($r->name, '{closure}')) { $callableDescription = sprintf('closure{%s:%s}', $r->getFileName(), $r->getStartLine()); } elseif ($class = $r->getClosureScopeClass()) { - $callableDescription = $class->name.'::'.$r->name; + $callableDescription = $class->name . '::' . $r->name; } else { $callableDescription = $r->name; } @@ -5925,15 +5960,15 @@ EOL; private function tryImportPathWithExtensions($path) { $result = array_merge( - $this->tryImportPath($path.'.sass'), - $this->tryImportPath($path.'.scss') + $this->tryImportPath($path . '.sass'), + $this->tryImportPath($path . '.scss') ); if ($result) { return $result; } - return $this->tryImportPath($path.'.css'); + return $this->tryImportPath($path . '.css'); } /** @@ -5943,7 +5978,7 @@ EOL; */ private function tryImportPath($path) { - $partial = dirname($path).'/_'.basename($path); + $partial = dirname($path) . '/_' . basename($path); $candidates = []; @@ -5969,7 +6004,7 @@ EOL; return null; } - return $this->checkImportPathConflicts($this->tryImportPathWithExtensions($path.'/index')); + return $this->checkImportPathConflicts($this->tryImportPathWithExtensions($path . '/index')); } /** @@ -5984,7 +6019,7 @@ EOL; } $normalizedPath = $path; - $normalizedRootDirectory = $this->rootDirectory.'/'; + $normalizedRootDirectory = $this->rootDirectory . '/'; if (\DIRECTORY_SEPARATOR === '\\') { $normalizedRootDirectory = str_replace('\\', '/', $normalizedRootDirectory); @@ -6371,8 +6406,6 @@ EOL; */ protected function sortNativeFunctionArgs($functionName, $prototypes, $args) { - static $parser = null; - if (! isset($prototypes)) { $keyArgs = []; $posArgs = []; @@ -6526,7 +6559,7 @@ EOL; * * @return array * - * @phpstan-param non-empty-list, rest_argument: string|null}> $prototypes + * @phpstan-param non-empty-array, rest_argument: string|null}> $prototypes * @phpstan-return array{arguments: list, rest_argument: string|null} */ private function selectFunctionPrototype(array $prototypes, $positional, array $names) @@ -6984,10 +7017,14 @@ EOL; return static::$null; } - if (is_numeric($value)) { + if (\is_int($value) || \is_float($value)) { return new Number($value, ''); } + if (is_numeric($value)) { + return new Number((float) $value, ''); + } + if ($value === '') { return static::$emptyString; } @@ -7675,9 +7712,9 @@ EOL; $b = min(1.0 - $w, $b); $rgb = $this->toRGB($hue, 100, 50); - for($i = 1; $i < 4; $i++) { - $rgb[$i] *= (1.0 - $w - $b); - $rgb[$i] = round($rgb[$i] + 255 * $w + 0.0001); + for ($i = 1; $i < 4; $i++) { + $rgb[$i] *= (1.0 - $w - $b); + $rgb[$i] = round($rgb[$i] + 255 * $w + 0.0001); } return $rgb; @@ -7704,7 +7741,6 @@ EOL; if ((int) $d === 0) { $h = 0; } else { - if ($red == $max) { $h = 60 * ($green - $blue) / $d; } elseif ($green == $max) { @@ -7714,7 +7750,7 @@ EOL; } } - return [Type::T_HWB, fmod($h, 360), $min / 255 * 100, 100 - $max / 255 *100]; + return [Type::T_HWB, fmod($h, 360), $min / 255 * 100, 100 - $max / 255 * 100]; } @@ -7923,7 +7959,13 @@ EOL; $scale = $operation === 'scale'; $change = $operation === 'change'; - /** @phpstan-var callable(string, float|int, bool=, bool=): (float|int|null) $getParam */ + /** + * @param string $name + * @param float|int $max + * @param bool $checkPercent + * @param bool $assertPercent + * @return float|int|null + */ $getParam = function ($name, $max, $checkPercent = false, $assertPercent = false) use (&$kwargs, $scale, $change) { if (!isset($kwargs[$name])) { return null; @@ -8065,7 +8107,7 @@ EOL; protected static $libChangeColor = ['color', 'kwargs...']; protected function libChangeColor($args) { - return $this->alterColor($args,'change', function ($base, $alter, $max) { + return $this->alterColor($args, 'change', function ($base, $alter, $max) { if ($alter === null) { return $base; } diff --git a/lib/scssphp/Node/Number.php b/lib/scssphp/Node/Number.php index 48e711f0855..6c044587644 100644 --- a/lib/scssphp/Node/Number.php +++ b/lib/scssphp/Node/Number.php @@ -33,7 +33,7 @@ use ScssPhp\ScssPhp\Util; * * @template-implements \ArrayAccess */ -class Number extends Node implements \ArrayAccess +class Number extends Node implements \ArrayAccess, \JsonSerializable { const PRECISION = 10; @@ -131,7 +131,7 @@ class Number extends Node implements \ArrayAccess } /** - * @return string[] + * @return list */ public function getNumeratorUnits() { @@ -139,13 +139,23 @@ class Number extends Node implements \ArrayAccess } /** - * @return string[] + * @return list */ public function getDenominatorUnits() { return $this->denominatorUnits; } + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + // Passing a compiler instance makes the method output a Sass representation instead of a CSS one, supporting full units. + return $this->output(new Compiler()); + } + /** * @return bool */ @@ -554,7 +564,7 @@ class Number extends Node implements \ArrayAccess try { return $this->coerceUnits($other, function ($num1, $num2) { - return round($num1,self::PRECISION) == round($num2, self::PRECISION); + return round($num1, self::PRECISION) == round($num2, self::PRECISION); }); } catch (SassScriptException $e) { return false; diff --git a/lib/scssphp/OutputStyle.php b/lib/scssphp/OutputStyle.php index c284639c13f..a1d8b425568 100644 --- a/lib/scssphp/OutputStyle.php +++ b/lib/scssphp/OutputStyle.php @@ -1,9 +1,62 @@ saveEncoding(); $this->extractLineNumbers($buffer); + if ($this->utf8 && !preg_match('//u', $buffer)) { + $message = $this->sourceName ? 'Invalid UTF-8 file: ' . $this->sourceName : 'Invalid UTF-8 file'; + throw new ParserException($message); + } + $this->pushBlock(null); // root block $this->whitespace(); $this->pushBlock(null); @@ -323,6 +328,13 @@ class Parser $list = $this->valueList($out); + if ($this->count !== \strlen($this->buffer)) { + $error = $this->parseError('Expected end of value'); + $message = 'Passing trailing content after the expression when parsing a value is deprecated since Scssphp 1.12.0 and will be an error in 2.0. ' . $error->getMessage(); + + @trigger_error($message, E_USER_DEPRECATED); + } + $this->restoreEncoding(); return $list; @@ -383,10 +395,13 @@ class Parser $this->inParens = false; $this->eatWhiteDefault = true; $this->buffer = (string) $buffer; + $this->discardComments = true; $this->saveEncoding(); $this->extractLineNumbers($this->buffer); + $this->whitespace(); + $isMediaQuery = $this->mediaQueryList($out); $this->restoreEncoding(); @@ -1678,9 +1693,9 @@ class Parser */ protected function appendComment($comment) { - assert($this->env !== null); - if (! $this->discardComments) { + assert($this->env !== null); + $this->env->comments[] = $comment; } } diff --git a/lib/scssphp/Version.php b/lib/scssphp/Version.php index d604a505764..89bd595265f 100644 --- a/lib/scssphp/Version.php +++ b/lib/scssphp/Version.php @@ -19,5 +19,5 @@ namespace ScssPhp\ScssPhp; */ class Version { - const VERSION = '1.11.0'; + const VERSION = '1.12.1'; } diff --git a/lib/thirdpartylibs.xml b/lib/thirdpartylibs.xml index 260b9a9719e..d7b69efb8ee 100644 --- a/lib/thirdpartylibs.xml +++ b/lib/thirdpartylibs.xml @@ -366,7 +366,7 @@ All rights reserved. scssphp scssphp scssphp is a compiler for SCSS written in PHP. - 1.11.0 + 1.12.1 MIT https://github.com/scssphp/scssphp/