diff --git a/Contrib/scssphp/bin/pscss b/Contrib/scssphp/bin/pscss old mode 100644 new mode 100755 index e62239830..0f009d6bd --- a/Contrib/scssphp/bin/pscss +++ b/Contrib/scssphp/bin/pscss @@ -38,8 +38,8 @@ $embedSourceMap = false; /** * Parse argument * - * @param integer $i - * @param array $options + * @param int $i + * @param string[] $options * * @return string|null */ diff --git a/Contrib/scssphp/composer.json b/Contrib/scssphp/composer.json index 86cd396bf..f81203dd7 100644 --- a/Contrib/scssphp/composer.json +++ b/Contrib/scssphp/composer.json @@ -42,7 +42,7 @@ "symfony/phpunit-bridge": "^5.1", "thoughtbot/bourbon": "^7.0", "twbs/bootstrap": "~5.0", - "twbs/bootstrap4": "4.6.0", + "twbs/bootstrap4": "4.6.1", "zurb/foundation": "~6.5" }, "repositories": [ @@ -50,16 +50,16 @@ "type": "package", "package": { "name": "sass/sass-spec", - "version": "2021.09.15", + "version": "2022.08.19", "source": { "type": "git", "url": "https://github.com/sass/sass-spec.git", - "reference": "eb2d7a0865c1faf0b55a39ff962b24aca9b4c955" + "reference": "2bdc199723a3445d5badac3ac774105698f08861" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sass/sass-spec/zipball/eb2d7a0865c1faf0b55a39ff962b24aca9b4c955", - "reference": "eb2d7a0865c1faf0b55a39ff962b24aca9b4c955", + "url": "https://api.github.com/repos/sass/sass-spec/zipball/2bdc199723a3445d5badac3ac774105698f08861", + "reference": "2bdc199723a3445d5badac3ac774105698f08861", "shasum": "" } } @@ -86,16 +86,16 @@ "type": "package", "package": { "name": "twbs/bootstrap4", - "version": "v4.6.0", + "version": "v4.6.1", "source": { "type": "git", "url": "https://github.com/twbs/bootstrap.git", - "reference": "6ffb0b48e455430f8a5359ed689ad64c1143fac2" + "reference": "043a03c95a2ad6738f85b65e53b9dbdfb03b8d10" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twbs/bootstrap/zipball/6ffb0b48e455430f8a5359ed689ad64c1143fac2", - "reference": "6ffb0b48e455430f8a5359ed689ad64c1143fac2", + "url": "https://api.github.com/repos/twbs/bootstrap/zipball/043a03c95a2ad6738f85b65e53b9dbdfb03b8d10", + "reference": "043a03c95a2ad6738f85b65e53b9dbdfb03b8d10", "shasum": "" } } @@ -103,6 +103,15 @@ ], "bin": ["bin/pscss"], "config": { - "sort-packages": true + "sort-packages": true, + "allow-plugins": { + "bamarni/composer-bin-plugin": true + } + }, + "extra": { + "bamarni-bin": { + "forward-command": false, + "bin-links": false + } } } diff --git a/Contrib/scssphp/phpcs.xml.dist b/Contrib/scssphp/phpcs.xml.dist deleted file mode 100644 index b162dbd6b..000000000 --- a/Contrib/scssphp/phpcs.xml.dist +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - diff --git a/Contrib/scssphp/src/Base/Range.php b/Contrib/scssphp/src/Base/Range.php index 0c5f58409..31d5ec565 100644 --- a/Contrib/scssphp/src/Base/Range.php +++ b/Contrib/scssphp/src/Base/Range.php @@ -34,8 +34,8 @@ class Range /** * Initialize range * - * @param integer|float $first - * @param integer|float $last + * @param int|float $first + * @param int|float $last */ public function __construct($first, $last) { @@ -46,9 +46,9 @@ public function __construct($first, $last) /** * Test for inclusion in range * - * @param integer|float $value + * @param int|float $value * - * @return boolean + * @return bool */ public function includes($value) { diff --git a/Contrib/scssphp/src/Block.php b/Contrib/scssphp/src/Block.php index 3ae49d00c..96668dc66 100644 --- a/Contrib/scssphp/src/Block.php +++ b/Contrib/scssphp/src/Block.php @@ -22,12 +22,12 @@ class Block { /** - * @var string + * @var string|null */ public $type; /** - * @var \ScssPhp\ScssPhp\Block + * @var Block|null */ public $parent; @@ -37,17 +37,17 @@ class Block public $sourceName; /** - * @var integer + * @var int */ public $sourceIndex; /** - * @var integer + * @var int */ public $sourceLine; /** - * @var integer + * @var int */ public $sourceColumn; @@ -67,7 +67,7 @@ class Block public $children; /** - * @var \ScssPhp\ScssPhp\Block|null + * @var Block|null */ public $selfParent; } diff --git a/Contrib/scssphp/src/Block/AtRootBlock.php b/Contrib/scssphp/src/Block/AtRootBlock.php new file mode 100644 index 000000000..41842c269 --- /dev/null +++ b/Contrib/scssphp/src/Block/AtRootBlock.php @@ -0,0 +1,37 @@ +type = Type::T_AT_ROOT; + } +} diff --git a/Contrib/scssphp/src/Block/CallableBlock.php b/Contrib/scssphp/src/Block/CallableBlock.php new file mode 100644 index 000000000..a18a87c2a --- /dev/null +++ b/Contrib/scssphp/src/Block/CallableBlock.php @@ -0,0 +1,45 @@ +type = $type; + } +} diff --git a/Contrib/scssphp/src/Block/ContentBlock.php b/Contrib/scssphp/src/Block/ContentBlock.php new file mode 100644 index 000000000..870849800 --- /dev/null +++ b/Contrib/scssphp/src/Block/ContentBlock.php @@ -0,0 +1,38 @@ +type = Type::T_INCLUDE; + } +} diff --git a/Contrib/scssphp/src/Block/DirectiveBlock.php b/Contrib/scssphp/src/Block/DirectiveBlock.php new file mode 100644 index 000000000..b1d3d1a81 --- /dev/null +++ b/Contrib/scssphp/src/Block/DirectiveBlock.php @@ -0,0 +1,37 @@ +type = Type::T_DIRECTIVE; + } +} diff --git a/Contrib/scssphp/src/Block/EachBlock.php b/Contrib/scssphp/src/Block/EachBlock.php new file mode 100644 index 000000000..b3289579d --- /dev/null +++ b/Contrib/scssphp/src/Block/EachBlock.php @@ -0,0 +1,37 @@ +type = Type::T_EACH; + } +} diff --git a/Contrib/scssphp/src/Block/ElseBlock.php b/Contrib/scssphp/src/Block/ElseBlock.php new file mode 100644 index 000000000..6abb4d775 --- /dev/null +++ b/Contrib/scssphp/src/Block/ElseBlock.php @@ -0,0 +1,27 @@ +type = Type::T_ELSE; + } +} diff --git a/Contrib/scssphp/src/Block/ElseifBlock.php b/Contrib/scssphp/src/Block/ElseifBlock.php new file mode 100644 index 000000000..4622bca79 --- /dev/null +++ b/Contrib/scssphp/src/Block/ElseifBlock.php @@ -0,0 +1,32 @@ +type = Type::T_ELSEIF; + } +} diff --git a/Contrib/scssphp/src/Block/ForBlock.php b/Contrib/scssphp/src/Block/ForBlock.php new file mode 100644 index 000000000..a9cf6733b --- /dev/null +++ b/Contrib/scssphp/src/Block/ForBlock.php @@ -0,0 +1,47 @@ +type = Type::T_FOR; + } +} diff --git a/Contrib/scssphp/src/Block/IfBlock.php b/Contrib/scssphp/src/Block/IfBlock.php new file mode 100644 index 000000000..9f21bf88a --- /dev/null +++ b/Contrib/scssphp/src/Block/IfBlock.php @@ -0,0 +1,37 @@ + + */ + public $cases = []; + + public function __construct() + { + $this->type = Type::T_IF; + } +} diff --git a/Contrib/scssphp/src/Block/MediaBlock.php b/Contrib/scssphp/src/Block/MediaBlock.php new file mode 100644 index 000000000..c49ee1b2b --- /dev/null +++ b/Contrib/scssphp/src/Block/MediaBlock.php @@ -0,0 +1,37 @@ +type = Type::T_MEDIA; + } +} diff --git a/Contrib/scssphp/src/Block/NestedPropertyBlock.php b/Contrib/scssphp/src/Block/NestedPropertyBlock.php new file mode 100644 index 000000000..1ea4a6c8a --- /dev/null +++ b/Contrib/scssphp/src/Block/NestedPropertyBlock.php @@ -0,0 +1,37 @@ +type = Type::T_NESTED_PROPERTY; + } +} diff --git a/Contrib/scssphp/src/Block/WhileBlock.php b/Contrib/scssphp/src/Block/WhileBlock.php new file mode 100644 index 000000000..ac18d4e02 --- /dev/null +++ b/Contrib/scssphp/src/Block/WhileBlock.php @@ -0,0 +1,32 @@ +type = Type::T_WHILE; + } +} diff --git a/Contrib/scssphp/src/Colors.php b/Contrib/scssphp/src/Colors.php index e836e3f4e..2df39992b 100644 --- a/Contrib/scssphp/src/Colors.php +++ b/Contrib/scssphp/src/Colors.php @@ -204,10 +204,10 @@ public static function colorNameToRGBa($colorName) /** * Reverse conversion : from RGBA to a color name if possible * - * @param integer $r - * @param integer $g - * @param integer $b - * @param integer|float $a + * @param int $r + * @param int $g + * @param int $b + * @param int|float $a * * @return string|null */ diff --git a/Contrib/scssphp/src/Compiler.php b/Contrib/scssphp/src/Compiler.php index 58ba795ab..ecafc8cb9 100644 --- a/Contrib/scssphp/src/Compiler.php +++ b/Contrib/scssphp/src/Compiler.php @@ -13,6 +13,17 @@ namespace ScssPhp\ScssPhp; use ScssPhp\ScssPhp\Base\Range; +use ScssPhp\ScssPhp\Block\AtRootBlock; +use ScssPhp\ScssPhp\Block\CallableBlock; +use ScssPhp\ScssPhp\Block\DirectiveBlock; +use ScssPhp\ScssPhp\Block\EachBlock; +use ScssPhp\ScssPhp\Block\ElseBlock; +use ScssPhp\ScssPhp\Block\ElseifBlock; +use ScssPhp\ScssPhp\Block\ForBlock; +use ScssPhp\ScssPhp\Block\IfBlock; +use ScssPhp\ScssPhp\Block\MediaBlock; +use ScssPhp\ScssPhp\Block\NestedPropertyBlock; +use ScssPhp\ScssPhp\Block\WhileBlock; use ScssPhp\ScssPhp\Compiler\CachedResult; use ScssPhp\ScssPhp\Compiler\Environment; use ScssPhp\ScssPhp\Exception\CompilerException; @@ -137,6 +148,7 @@ class Compiler public static $emptyString = [Type::T_STRING, '"', []]; public static $with = [Type::T_KEYWORD, 'with']; public static $without = [Type::T_KEYWORD, 'without']; + private static $emptyArgumentList = [Type::T_LIST, '', [], []]; /** * @var array @@ -154,7 +166,7 @@ class Compiler /** * @var array - * @phpstan-var array + * @phpstan-var array */ protected $userFunctions = []; /** @@ -199,9 +211,15 @@ class Compiler private $charset = true; /** - * @var string|\ScssPhp\ScssPhp\Formatter + * @var Formatter */ - protected $formatter = Expanded::class; + protected $formatter; + + /** + * @var string + * @phpstan-var class-string + */ + private $configuredFormatter = Expanded::class; /** * @var Environment @@ -369,7 +387,7 @@ public function getCompileOptions() 'encoding' => $this->encoding, 'sourceMap' => serialize($this->sourceMap), 'sourceMapOptions' => $this->sourceMapOptions, - 'formatter' => $this->formatter, + 'formatter' => $this->configuredFormatter, 'legacyImportPath' => $this->legacyCwdImportPath, ]; @@ -490,7 +508,7 @@ public function compileString($source, $path = null) $tree = $this->parser->parse($source); $this->parser = null; - $this->formatter = new $this->formatter(); + $this->formatter = new $this->configuredFormatter(); $this->rootBlock = null; $this->rootEnv = $this->pushEnv($tree); @@ -517,6 +535,7 @@ public function compileString($source, $path = null) $sourceMapGenerator = new SourceMapGenerator($this->sourceMapOptions); } } + assert($this->scope !== null); $out = $this->formatter->format($this->scope, $sourceMapGenerator); @@ -530,6 +549,7 @@ public function compileString($source, $path = null) $sourceMap = null; if (! empty($out) && $this->sourceMap && $this->sourceMap !== self::SOURCE_MAP_NONE) { + assert($sourceMapGenerator !== null); $sourceMap = $sourceMapGenerator->generateJson($prefix); $sourceMapUrl = null; @@ -643,7 +663,7 @@ protected function parserFactory($path) * @param array $target * @param array $origin * - * @return boolean + * @return bool */ protected function isSelfExtend($target, $origin) { @@ -659,7 +679,7 @@ protected function isSelfExtend($target, $origin) /** * Push extends * - * @param array $target + * @param string[] $target * @param array $origin * @param array|null $block * @@ -702,9 +722,9 @@ protected function makeOutputBlock($type, $selectors = null) $out->sourceLine = $this->env->block->sourceLine; $out->sourceColumn = $this->env->block->sourceColumn; } else { - $out->sourceName = null; - $out->sourceLine = null; - $out->sourceColumn = null; + $out->sourceName = isset($this->sourceNames[$this->sourceIndex]) ? $this->sourceNames[$this->sourceIndex] : '(stdin)'; + $out->sourceLine = $this->sourceLine; + $out->sourceColumn = $this->sourceColumn; } return $out; @@ -722,6 +742,7 @@ protected function compileRoot(Block $rootBlock) $this->rootBlock = $this->scope = $this->makeOutputBlock(Type::T_ROOT); $this->compileChildrenNoReturn($rootBlock->children, $this->scope); + assert($this->scope !== null); $this->flattenSelectors($this->scope); $this->missingSelectors(); } @@ -803,6 +824,7 @@ protected function flattenSelectors(OutputBlock $block, $parentKey = null) } if ($placeholderSelector && 0 === \count($block->selectors) && null !== $parentKey) { + assert($block->parent !== null); unset($block->parent->children[$parentKey]); return; @@ -852,10 +874,10 @@ protected function glueFunctionSelectors($parts) /** * Match extends * - * @param array $selector - * @param array $out - * @param integer $from - * @param boolean $initial + * @param array $selector + * @param array $out + * @param int $from + * @param bool $initial * * @return void */ @@ -988,7 +1010,7 @@ protected function matchExtends($selector, &$out, $from = 0, $initial = true) * @param string $part * @param array $matches * - * @return boolean + * @return bool */ protected function isPseudoSelector($part, &$matches) { @@ -1050,11 +1072,11 @@ protected function pushOrMergeExtentedSelector(&$out, $extended) /** * Match extends single * - * @param array $rawSingle - * @param array $outOrigin - * @param boolean $initial + * @param array $rawSingle + * @param array $outOrigin + * @param bool $initial * - * @return boolean + * @return bool */ protected function matchExtendsSingle($rawSingle, &$outOrigin, $initial = true) { @@ -1269,11 +1291,13 @@ protected function combineSelectorSingle($base, $other) */ protected function compileMedia(Block $media) { + assert($media instanceof MediaBlock); $this->pushEnv($media); $mediaQueries = $this->compileMediaQuery($this->multiplyMedia($this->env)); if (! empty($mediaQueries)) { + assert($this->scope !== null); $previousScope = $this->scope; $parentScope = $this->mediaParent($this->scope); @@ -1346,7 +1370,7 @@ protected function mediaParent(OutputBlock $scope) /** * Compile directive * - * @param \ScssPhp\ScssPhp\Block|array $directive + * @param DirectiveBlock|array $directive * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out * * @return void @@ -1412,6 +1436,7 @@ protected function compileDirectiveName($directiveName) */ protected function compileAtRoot(Block $block) { + assert($block instanceof AtRootBlock); $env = $this->pushEnv($block); $envs = $this->compactEnv($env); list($with, $without) = $this->compileWith(isset($block->with) ? $block->with : null); @@ -1438,7 +1463,7 @@ protected function compileAtRoot(Block $block) if ( ! $selfParent->selectors && - isset($block->parent) && $block->parent && + isset($block->parent) && isset($block->parent->selectors) && $block->parent->selectors ) { $selfParent = $block->parent; @@ -1446,13 +1471,15 @@ protected function compileAtRoot(Block $block) $this->env = $this->filterWithWithout($envs, $with, $without); + assert($this->scope !== null); $saveScope = $this->scope; $this->scope = $this->filterScopeWithWithout($saveScope, $with, $without); // propagate selfParent to the children where they still can be useful $this->compileChildrenNoReturn($block->children, $this->scope, $selfParent); - $this->scope = $this->completeScope($this->scope, $saveScope); + assert($this->scope !== null); + $this->completeScope($this->scope, $saveScope); $this->scope = $saveScope; $this->env = $this->extractEnv($envs); @@ -1460,7 +1487,7 @@ protected function compileAtRoot(Block $block) } /** - * Filter at-root scope depending of with/without option + * Filter at-root scope depending on with/without option * * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $scope * @param array $with @@ -1476,6 +1503,7 @@ protected function filterScopeWithWithout($scope, $with, $without) if ($scope->type === Type::T_ROOT) { return $scope; } + assert($this->rootBlock !== null); // start from the root while ($scope->parent && $scope->parent->type !== Type::T_ROOT) { @@ -1543,7 +1571,7 @@ protected function filterScopeWithWithout($scope, $with, $without) */ protected function completeScope($scope, $previousScope) { - if (! $scope->type && (! $scope->selectors || ! \count($scope->selectors)) && \count($scope->lines)) { + if (! $scope->type && ! $scope->selectors && \count($scope->lines)) { $scope->selectors = $this->findScopeSelectors($previousScope, $scope->depth); } @@ -1560,7 +1588,7 @@ protected function completeScope($scope, $previousScope) * Find a selector by the depth node in the scope * * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $scope - * @param integer $depth + * @param int $depth * * @return array */ @@ -1584,9 +1612,11 @@ protected function findScopeSelectors($scope, $depth) /** * Compile @at-root's with: inclusion / without: exclusion into 2 lists uses to filter scope/env later * - * @param array $withCondition + * @param array|null $withCondition * * @return array + * + * @phpstan-return array{array, array} */ protected function compileWith($withCondition) { @@ -1606,9 +1636,10 @@ protected function compileWith($withCondition) } } - if ($this->mapHasKey($withCondition, static::$with)) { + $withConfig = $this->mapGet($withCondition, static::$with); + if ($withConfig !== null) { $without = []; // cancel the default - $list = $this->coerceList($this->libMapGet([$withCondition, static::$with])); + $list = $this->coerceList($withConfig); foreach ($list[2] as $item) { $keyword = $this->compileStringContent($this->coerceString($item)); @@ -1617,9 +1648,10 @@ protected function compileWith($withCondition) } } - if ($this->mapHasKey($withCondition, static::$without)) { + $withoutConfig = $this->mapGet($withCondition, static::$without); + if ($withoutConfig !== null) { $without = []; // cancel the default - $list = $this->coerceList($this->libMapGet([$withCondition, static::$without])); + $list = $this->coerceList($withoutConfig); foreach ($list[2] as $item) { $keyword = $this->compileStringContent($this->coerceString($item)); @@ -1669,7 +1701,7 @@ protected function filterWithWithout($envs, $with, $without) * @param array $with * @param array $without * - * @return boolean + * @return bool */ protected function isWith($block, $with, $without) { @@ -1679,6 +1711,7 @@ protected function isWith($block, $with, $without) } if ($block->type === Type::T_DIRECTIVE) { + assert($block instanceof DirectiveBlock || $block instanceof OutputBlock); if (isset($block->name)) { return $this->testWithWithout($this->compileDirectiveName($block->name), $with, $without); } elseif (isset($block->selectors) && preg_match(',@(\w+),ims', json_encode($block->selectors), $m)) { @@ -1714,7 +1747,7 @@ protected function isWith($block, $with, $without) * @param array $with * @param array $without * - * @return boolean + * @return bool * true if the block should be kept, false to reject */ protected function testWithWithout($what, $with, $without) @@ -1749,10 +1782,12 @@ protected function compileKeyframeBlock(Block $block, $selectors) $this->scope = $this->makeOutputBlock($block->type, $selectors); $this->scope->depth = 1; + assert($this->scope->parent !== null); $this->scope->parent->children[] = $this->scope; $this->compileChildrenNoReturn($block->children, $this->scope); + assert($this->scope !== null); $this->scope = $this->scope->parent; $this->env = $this->extractEnv($envs); @@ -1769,6 +1804,7 @@ protected function compileKeyframeBlock(Block $block, $selectors) */ protected function compileNestedPropertiesBlock(Block $block, OutputBlock $out) { + assert($block instanceof NestedPropertyBlock); $prefix = $this->compileValue($block->prefix) . '-'; $nested = $this->makeOutputBlock($block->type); @@ -1787,6 +1823,7 @@ protected function compileNestedPropertiesBlock(Block $block, OutputBlock $out) break; case Type::T_NESTED_PROPERTY: + assert($child[1] instanceof NestedPropertyBlock); array_unshift($child[1]->prefix[2], $prefix); break; } @@ -1808,11 +1845,12 @@ protected function compileNestedBlock(Block $block, $selectors) $this->pushEnv($block); $this->scope = $this->makeOutputBlock($block->type, $selectors); + assert($this->scope->parent !== null); $this->scope->parent->children[] = $this->scope; // wrap assign children in a block // except for @font-face - if ($block->type !== Type::T_DIRECTIVE || $this->compileDirectiveName($block->name) !== 'font-face') { + if (!$block instanceof DirectiveBlock || $this->compileDirectiveName($block->name) !== 'font-face') { // need wrapping? $needWrapping = false; @@ -1841,6 +1879,7 @@ protected function compileNestedBlock(Block $block, $selectors) $this->compileChildrenNoReturn($block->children, $this->scope); + assert($this->scope !== null); $this->scope = $this->scope->parent; $this->popEnv(); @@ -1869,10 +1908,12 @@ protected function compileNestedBlock(Block $block, $selectors) protected function compileBlock(Block $block) { $env = $this->pushEnv($block); + assert($block->selectors !== null); $env->selectors = $this->evalSelectors($block->selectors); $out = $this->makeOutputBlock(null); + assert($this->scope !== null); $this->scope->children[] = $out; if (\count($block->children)) { @@ -1890,6 +1931,7 @@ protected function compileBlock(Block $block) // and revert for the following children of the same block if ($selfParentSelectors) { + assert($block->selfParent !== null); $block->selfParent->selectors = $selfParentSelectors; } } @@ -1901,8 +1943,8 @@ protected function compileBlock(Block $block) /** * Compile the value of a comment that can have interpolation * - * @param array $value - * @param boolean $pushEnv + * @param array $value + * @param bool $pushEnv * * @return string */ @@ -1945,6 +1987,7 @@ protected function compileComment($block) $out = $this->makeOutputBlock(Type::T_COMMENT); $out->lines[] = $this->compileCommentValue($block, true); + assert($this->scope !== null); $this->scope->children[] = $out; } @@ -1959,7 +2002,11 @@ protected function evalSelectors($selectors) { $this->shouldEvaluate = false; - $selectors = array_map([$this, 'evalSelector'], $selectors); + $evaluatedSelectors = []; + foreach ($selectors as $selector) { + $evaluatedSelectors[] = $this->evalSelector($selector); + } + $selectors = $evaluatedSelectors; // after evaluating interpolates, we might need a second pass if ($this->shouldEvaluate) { @@ -1987,6 +2034,8 @@ protected function evalSelectors($selectors) * @param array $selector * * @return array + * + * @phpstan-impure */ protected function evalSelector($selector) { @@ -1999,6 +2048,8 @@ protected function evalSelector($selector) * @param array $part * * @return array + * + * @phpstan-impure */ protected function evalSelectorPart($part) { @@ -2012,8 +2063,8 @@ protected function evalSelectorPart($part) } } elseif ( \is_string($p) && \strlen($p) >= 2 && - ($first = $p[0]) && ($first === '"' || $first === "'") && - substr($p, -1) === $first + ($p[0] === '"' || $p[0] === "'") && + substr($p, -1) === $p[0] ) { $p = substr($p, 1, -1); } @@ -2219,7 +2270,7 @@ protected function compileSelectorPart($piece) * * @param array $selector * - * @return boolean + * @return bool */ protected function hasSelectorPlaceholder($selector) { @@ -2299,9 +2350,9 @@ protected function compileChildren($stms, OutputBlock $out, $traceName = '') } /** - * Compile children and throw exception if unexpected `@return` + * Compile children and throw exception if unexpected at-return * - * @param array $stms + * @param array[] $stms * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out * @param \ScssPhp\ScssPhp\Block $selfParent * @param string $traceName @@ -2316,13 +2367,13 @@ protected function compileChildrenNoReturn($stms, OutputBlock $out, $selfParent foreach ($stms as $stm) { if ($selfParent && isset($stm[1]) && \is_object($stm[1]) && $stm[1] instanceof Block) { + $oldSelfParent = $stm[1]->selfParent; $stm[1]->selfParent = $selfParent; $ret = $this->compileChild($stm, $out); - $stm[1]->selfParent = null; + $stm[1]->selfParent = $oldSelfParent; } elseif ($selfParent && \in_array($stm[0], [Type::T_INCLUDE, Type::T_EXTEND])) { $stm['selfParent'] = $selfParent; $ret = $this->compileChild($stm, $out); - unset($stm['selfParent']); } else { $ret = $this->compileChild($stm, $out); } @@ -2380,7 +2431,7 @@ protected function evaluateMediaQuery($queryList) $queryString = $this->compileMediaQuery([$queryList[$kql]]); $queryString = reset($queryString); - if (strpos($queryString, '@media ') === 0) { + if ($queryString !== false && strpos($queryString, '@media ') === 0) { $queryString = substr($queryString, 7); $queries = []; @@ -2636,9 +2687,9 @@ protected function mergeMediaTypes($type1, $type2) * * @param array $rawPath * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out - * @param boolean $once + * @param bool $once * - * @return boolean + * @return bool */ protected function compileImport($rawPath, OutputBlock $out, $once = false) { @@ -2773,7 +2824,7 @@ protected function appendRootDirective($line, $out, $allowed = [Type::T_COMMENT] // insert the directive as a comment $child = $this->makeOutputBlock(Type::T_COMMENT); $child->lines[] = $line; - $child->sourceName = $this->sourceNames[$this->sourceIndex]; + $child->sourceName = $this->sourceNames[$this->sourceIndex] ?: '(stdin)'; $child->sourceLine = $this->sourceLine; $child->sourceColumn = $this->sourceColumn; @@ -2836,7 +2887,7 @@ 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) && isset($child[1]->sourceLine)) { + } elseif (\is_array($child) && isset($child[1]->sourceLine) && $child[1] instanceof Block) { $this->sourceIndex = $child[1]->sourceIndex; $this->sourceLine = $child[1]->sourceLine; $this->sourceColumn = $child[1]->sourceColumn; @@ -3044,6 +3095,7 @@ protected function compileChild($child, OutputBlock $out) case Type::T_MIXIN: case Type::T_FUNCTION: list(, $block) = $child; + assert($block instanceof CallableBlock); // the block need to be able to go up to it's parent env to resolve vars $block->parentEnv = $this->getStoreEnv(); $this->set(static::$namespaces[$block->type] . $block->name, $block, true); @@ -3071,6 +3123,7 @@ protected function compileChild($child, OutputBlock $out) if (! $selectors && isset($child['selfParent'])) { $selectors = $this->multiplySelectors($this->env, $child['selfParent']); } + assert($selectors !== null); if (\count($result) > 1) { $replacement = implode(', ', $result); @@ -3094,6 +3147,7 @@ protected function compileChild($child, OutputBlock $out) case Type::T_IF: list(, $if) = $child; + assert($if instanceof IfBlock); if ($this->isTruthy($this->reduce($if->cond, true))) { return $this->compileChildren($if->children, $out); @@ -3101,8 +3155,8 @@ protected function compileChild($child, OutputBlock $out) foreach ($if->cases as $case) { if ( - $case->type === Type::T_ELSE || - $case->type === Type::T_ELSEIF && $this->isTruthy($this->reduce($case->cond)) + $case instanceof ElseBlock || + $case instanceof ElseifBlock && $this->isTruthy($this->reduce($case->cond)) ) { return $this->compileChildren($case->children, $out); } @@ -3111,6 +3165,7 @@ protected function compileChild($child, OutputBlock $out) case Type::T_EACH: list(, $each) = $child; + assert($each instanceof EachBlock); $list = $this->coerceList($this->reduce($each->list), ',', true); @@ -3145,6 +3200,7 @@ protected function compileChild($child, OutputBlock $out) case Type::T_WHILE: list(, $while) = $child; + assert($while instanceof WhileBlock); while ($this->isTruthy($this->reduce($while->cond, true))) { $ret = $this->compileChildren($while->children, $out); @@ -3157,6 +3213,7 @@ protected function compileChild($child, OutputBlock $out) case Type::T_FOR: list(, $for) = $child; + assert($for instanceof ForBlock); $startNumber = $this->assertNumber($this->reduce($for->start, true)); $endNumber = $this->assertNumber($this->reduce($for->end, true)); @@ -3217,6 +3274,8 @@ protected function compileChild($child, OutputBlock $out) throw $this->error("Undefined mixin $name"); } + assert($mixin instanceof CallableBlock); + $callingScope = $this->getStoreEnv(); // push scope, apply args @@ -3227,7 +3286,7 @@ protected function compileChild($child, OutputBlock $out) // and assign this fake parent to childs $selfParent = null; - if (isset($child['selfParent']) && isset($child['selfParent']->selectors)) { + if (isset($child['selfParent']) && $child['selfParent'] instanceof Block && isset($child['selfParent']->selectors)) { $selfParent = $child['selfParent']; } else { $parentSelectors = $this->multiplySelectors($this->env); @@ -3237,7 +3296,7 @@ protected function compileChild($child, OutputBlock $out) $parent->selectors = $parentSelectors; foreach ($mixin->children as $k => $child) { - if (isset($child[1]) && \is_object($child[1]) && $child[1] instanceof Block) { + if (isset($child[1]) && $child[1] instanceof Block) { $mixin->children[$k][1]->parent = $parent; } } @@ -3343,6 +3402,8 @@ protected function compileChild($child, OutputBlock $out) default: throw $this->error("unknown child type: $child[0]"); } + + return null; } /** @@ -3389,7 +3450,7 @@ protected function expToString($exp, $keepParens = false) * * @param array|Number $value * - * @return boolean + * @return bool */ public function isTruthy($value) { @@ -3401,7 +3462,7 @@ public function isTruthy($value) * * @param string $value * - * @return boolean + * @return bool */ protected function isImmediateRelationshipCombinator($value) { @@ -3413,7 +3474,7 @@ protected function isImmediateRelationshipCombinator($value) * * @param array $value * - * @return boolean + * @return bool */ protected function shouldEval($value) { @@ -3436,7 +3497,7 @@ protected function shouldEval($value) * Reduce value * * @param array|Number $value - * @param boolean $inExp + * @param bool $inExp * * @return array|Number */ @@ -3477,32 +3538,24 @@ protected function reduce($value, $inExp = false) $ucLType = ucfirst($ltype); $ucRType = ucfirst($rtype); + $shouldEval = $inParens || $inExp; + // this tries: // 1. op[op name][left type][right type] - // 2. op[left type][right type] (passing the op as first arg + // 2. op[left type][right type] (passing the op as first arg) // 3. op[op name] - $fn = "op${ucOpName}${ucLType}${ucRType}"; - - if ( - \is_callable([$this, $fn]) || - (($fn = "op${ucLType}${ucRType}") && - \is_callable([$this, $fn]) && - $passOp = true) || - (($fn = "op${ucOpName}") && - \is_callable([$this, $fn]) && - $genOp = true) - ) { - $shouldEval = $inParens || $inExp; - - if (isset($passOp)) { - $out = $this->$fn($op, $left, $right, $shouldEval); - } else { - $out = $this->$fn($left, $right, $shouldEval); - } + if (\is_callable([$this, $fn = "op{$ucOpName}{$ucLType}{$ucRType}"])) { + $out = $this->$fn($left, $right, $shouldEval); + } elseif (\is_callable([$this, $fn = "op{$ucLType}{$ucRType}"])) { + $out = $this->$fn($op, $left, $right, $shouldEval); + } elseif (\is_callable([$this, $fn = "op{$ucOpName}"])) { + $out = $this->$fn($left, $right, $shouldEval); + } else { + $out = null; + } - if (isset($out)) { - return $out; - } + if (isset($out)) { + return $out; } return $this->expToString($value); @@ -3839,11 +3892,12 @@ protected function getFunctionReference($name, $safeCopy = false) // Special functions overriding a CSS function are case-insensitive. We normalize them as lowercase // to avoid the deprecation warning about the wrong case being used. - if ($lowercasedName === 'min' || $lowercasedName === 'max') { + if ($lowercasedName === 'min' || $lowercasedName === 'max' || $lowercasedName === 'rgb' || $lowercasedName === 'rgba' || $lowercasedName === 'hsl' || $lowercasedName === 'hsla') { $normalizedName = $lowercasedName; } if (($f = $this->getBuiltinFunction($normalizedName)) && \is_callable($f)) { + /** @var string $libName */ $libName = $f[1]; $prototype = isset(static::$$libName) ? static::$$libName : null; @@ -3941,6 +3995,10 @@ public function normalizeValue($value) unset($value['enclosing']); } + if ($value[1] === '' && count($value[2]) > 1) { + $value[1] = ' '; + } + return $value; case Type::T_STRING: @@ -4056,8 +4114,8 @@ protected function opAdd($left, $right) * Boolean and * * @param array|Number $left - * @param array|Number $right - * @param boolean $shouldEval + * @param array|Number $right + * @param bool $shouldEval * * @return array|Number|null */ @@ -4085,7 +4143,7 @@ protected function opAnd($left, $right, $shouldEval) * * @param array|Number $left * @param array|Number $right - * @param boolean $shouldEval + * @param bool $shouldEval * * @return array|Number|null */ @@ -4511,8 +4569,7 @@ public function compileValue($value, $quote = true) // force double quote as string quote for the output in certain cases if ( $value[1] === "'" && - (strpos($content, '"') === false or strpos($content, "'") !== false) && - strpbrk($content, '{}\\\'') !== false + (strpos($content, '"') === false or strpos($content, "'") !== false) ) { $value[1] = '"'; } elseif ( @@ -4565,6 +4622,8 @@ public function compileValue($value, $quote = true) } } + $separator = $delim === '/' ? ' /' : $delim; + $prefix_value = ''; if ($delim !== ' ') { @@ -4603,7 +4662,7 @@ public function compileValue($value, $quote = true) $filtered[] = $compiled; } - return $pre . substr(implode("$delim", $filtered), \strlen($prefix_value)) . $post; + return $pre . substr(implode($separator, $filtered), \strlen($prefix_value)) . $post; case Type::T_MAP: $keys = $value[1]; @@ -4875,10 +4934,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 boolean $stillHasSelf - * @param array $selfParentSelectors + * @param array $parent + * @param array $child + * @param bool $stillHasSelf + * @param array $selfParentSelectors * @return array */ @@ -4956,6 +5015,8 @@ protected function multiplyMedia(Environment $env = null, $childQueries = null) return $this->multiplyMedia($env->parent, $childQueries); } + assert($env->block instanceof MediaBlock); + $parentQueries = isset($env->block->queryList) ? $env->block->queryList : [[[Type::T_MEDIA_VALUE, $env->block->value]]]; @@ -5090,7 +5151,7 @@ protected function getStoreEnv() * * @param string $name * @param mixed $value - * @param boolean $shadow + * @param bool $shadow * @param \ScssPhp\ScssPhp\Compiler\Environment $env * @param mixed $valueUnreduced * @@ -5196,9 +5257,9 @@ protected function setRaw($name, $value, Environment $env, $valueUnreduced = nul * @internal * * @param string $name - * @param boolean $shouldThrow + * @param bool $shouldThrow * @param \ScssPhp\ScssPhp\Compiler\Environment $env - * @param boolean $unreduced + * @param bool $unreduced * * @return mixed|null */ @@ -5265,7 +5326,7 @@ public function get($name, $shouldThrow = true, Environment $env = null, $unredu * @param string $name * @param \ScssPhp\ScssPhp\Compiler\Environment $env * - * @return boolean + * @return bool */ protected function has($name, Environment $env = null) { @@ -5455,7 +5516,7 @@ public function setImportPaths($path) * * @api * - * @param integer $numberPrecision + * @param int $numberPrecision * * @return void * @@ -5482,11 +5543,11 @@ public function setOutputStyle($style) { switch ($style) { case OutputStyle::EXPANDED: - $this->formatter = Expanded::class; + $this->configuredFormatter = Expanded::class; break; case OutputStyle::COMPRESSED: - $this->formatter = Compressed::class; + $this->configuredFormatter = Compressed::class; break; default: @@ -5504,6 +5565,8 @@ public function setOutputStyle($style) * @return void * * @deprecated Use {@see setOutputStyle} instead. + * + * @phpstan-param class-string $formatterName */ public function setFormatter($formatterName) { @@ -5512,7 +5575,7 @@ public function setFormatter($formatterName) } @trigger_error('The method "setFormatter" is deprecated. Use "setOutputStyle" instead.', E_USER_DEPRECATED); - $this->formatter = $formatterName; + $this->configuredFormatter = $formatterName; } /** @@ -5556,7 +5619,7 @@ public function setCharset($charset) * * @api * - * @param integer $sourceMap + * @param int $sourceMap * * @return void * @@ -5653,6 +5716,10 @@ protected function importFile($path, OutputBlock $out) // see if tree is cached $realPath = realpath($path); + if ($realPath === false) { + $realPath = $path; + } + if (substr($path, -5) === '.sass') { $this->sourceIndex = \count($this->sourceNames); $this->sourceNames[] = $path; @@ -5958,7 +6025,7 @@ public function setEncoding($encoding) * * @api * - * @param boolean $ignoreErrors + * @param bool $ignoreErrors * * @return \ScssPhp\ScssPhp\Compiler * @@ -5996,6 +6063,8 @@ public function getSourcePosition() * * @param string $msg Message with optional sprintf()-style vararg parameters * + * @return never + * * @throws \ScssPhp\ScssPhp\Exception\CompilerException * * @deprecated use "error" and throw the exception in the caller instead. @@ -6015,7 +6084,8 @@ public function throwError($msg) * * @internal * - * @param string $msg Message with optional sprintf()-style vararg parameters + * @param string $msg Message with optional sprintf()-style vararg parameters + * @param bool|float|int|string|null ...$args * * @return CompilerException */ @@ -6097,7 +6167,7 @@ public function errorArgsNumber($functionName, $ExpectedArgs, $nbActual) /** * Beautify call stack for output * - * @param boolean $all + * @param bool $all * @param int|null $limit * * @return string @@ -6133,6 +6203,8 @@ protected function callStackMessage($all = false, $limit = null) * * @param string $name * + * @return void + * * @throws \Exception */ protected function handleImportLoop($name) @@ -6157,8 +6229,8 @@ protected function handleImportLoop($name) /** * Call SCSS @function * - * @param Object $func - * @param array $argValues + * @param CallableBlock|null $func + * @param array $argValues * * @return array|Number */ @@ -6327,6 +6399,9 @@ protected function sortNativeFunctionArgs($functionName, $prototypes, $args) if (\in_array($functionName, ['libRgb', 'libRgba', 'libHsl', 'libHsla'])) { // notation 100 127 255 / 0 is in fact a simple list of 4 values foreach ($args as $k => $arg) { + if (!isset($arg[1])) { + continue; // This happens when using a trailing comma + } if ($arg[1][0] === Type::T_LIST && \count($arg[1][2]) === 3) { $args[$k][1][2] = $this->extractSlashAlphaInColorFunction($arg[1][2]); } @@ -6732,9 +6807,8 @@ private function maybeReduce($reduce, $value) * * @param array[] $argDef * @param array|null $argValues - * @param boolean $storeInEnv - * @param boolean $reduce - * only used if $storeInEnv = false + * @param bool $storeInEnv + * @param bool $reduce only used if $storeInEnv = false * * @return array * @@ -6760,7 +6834,7 @@ protected function applyArguments($argDef, $argValues, $storeInEnv = true, $redu $prototype = ['arguments' => [], 'rest_argument' => null]; $originalRestArgumentName = null; - foreach ($argDef as $i => $arg) { + foreach ($argDef as $arg) { list($name, $default, $isVariable) = $arg; $normalizedName = str_replace('_', '-', $name); @@ -6929,14 +7003,18 @@ protected function coerceValue($value) } /** - * Coerce something to map + * Tries to convert an item to a Sass map * - * @param array|Number $item + * @param Number|array $item * - * @return array|Number + * @return array|null */ - protected function coerceMap($item) + private function tryMap($item) { + if ($item instanceof Number) { + return null; + } + if ($item[0] === Type::T_MAP) { return $item; } @@ -6948,6 +7026,24 @@ protected function coerceMap($item) return static::$emptyMap; } + return null; + } + + /** + * Coerce something to map + * + * @param array|Number $item + * + * @return array|Number + */ + protected function coerceMap($item) + { + $map = $this->tryMap($item); + + if ($map !== null) { + return $map; + } + return $item; } @@ -6956,14 +7052,14 @@ protected function coerceMap($item) * * @param array|Number $item * @param string $delim - * @param boolean $removeTrailingNull + * @param bool $removeTrailingNull * * @return array */ protected function coerceList($item, $delim = ',', $removeTrailingNull = false) { if ($item instanceof Number) { - return [Type::T_LIST, $delim, [$item]]; + return [Type::T_LIST, '', [$item]]; } if ($item[0] === Type::T_LIST) { @@ -6984,29 +7080,17 @@ protected function coerceList($item, $delim = ',', $removeTrailingNull = false) $key = $keys[$i]; $value = $values[$i]; - switch ($key[0]) { - case Type::T_LIST: - case Type::T_MAP: - case Type::T_STRING: - case Type::T_NULL: - break; - - default: - $key = [Type::T_KEYWORD, $this->compileStringContent($this->coerceString($key))]; - break; - } - $list[] = [ Type::T_LIST, - '', + ' ', [$key, $value] ]; } - return [Type::T_LIST, ',', $list]; + return [Type::T_LIST, $list ? ',' : '', $list]; } - return [Type::T_LIST, $delim, [$item]]; + return [Type::T_LIST, '', [$item]]; } /** @@ -7147,10 +7231,10 @@ protected function coerceColor($value, $inRGBFunction = false) } /** - * @param integer|Number $value - * @param boolean $isAlpha + * @param int|Number $value + * @param bool $isAlpha * - * @return integer|mixed + * @return int|mixed */ protected function compileRGBAValue($value, $isAlpha = false) { @@ -7162,12 +7246,12 @@ protected function compileRGBAValue($value, $isAlpha = false) } /** - * @param mixed $value - * @param integer|float $min - * @param integer|float $max - * @param boolean $isInt + * @param mixed $value + * @param int|float $min + * @param int|float $max + * @param bool $isInt * - * @return integer|mixed + * @return int|mixed */ protected function compileColorPartValue($value, $min, $max, $isInt = true) { @@ -7218,6 +7302,8 @@ protected function compileColorPartValue($value, $min, $max, $isInt = true) protected function coerceString($value) { if ($value[0] === Type::T_STRING) { + assert(\is_array($value)); + return $value; } @@ -7261,7 +7347,7 @@ public function assertString($value, $varName = null) * * @param array|Number $value * - * @return integer|float + * @return int|float * * @deprecated */ @@ -7294,15 +7380,15 @@ protected function coercePercent($value) */ public function assertMap($value, $varName = null) { - $value = $this->coerceMap($value); + $map = $this->tryMap($value); - if ($value[0] !== Type::T_MAP) { + if ($map === null) { $value = $this->compileValue($value); throw SassScriptException::forArgument("$value is not a map.", $varName); } - return $value; + return $map; } /** @@ -7321,6 +7407,7 @@ public function assertList($value) if ($value[0] !== Type::T_LIST) { throw $this->error('expecting list, %s received', $value[0]); } + assert(\is_array($value)); return $value; } @@ -7399,7 +7486,7 @@ public function assertNumber($value, $varName = null) * @param array|Number $value * @param string|null $varName * - * @return integer + * @return int * * @throws SassScriptException */ @@ -7463,9 +7550,9 @@ protected function fixColor($c) * * @internal * - * @param integer $red - * @param integer $green - * @param integer $blue + * @param int $red + * @param int $green + * @param int $blue * * @return array */ @@ -7490,7 +7577,7 @@ public function toHSL($red, $green, $blue) $h = 60 * ($green - $blue) / $d; } elseif ($green == $max) { $h = 60 * ($blue - $red) / $d + 120; - } elseif ($blue == $max) { + } else { $h = 60 * ($red - $green) / $d + 240; } } @@ -7569,9 +7656,9 @@ public function toRGB($hue, $saturation, $lightness) * * @api * - * @param integer $hue H from 0 to 360 - * @param integer $whiteness W from 0 to 100 - * @param integer $blackness B from 0 to 100 + * @param int|float $hue H from 0 to 360 + * @param int|float $whiteness W from 0 to 100 + * @param int|float $blackness B from 0 to 100 * * @return array */ @@ -7601,9 +7688,9 @@ private function HWBtoRGB($hue, $whiteness, $blackness) * * @api * - * @param integer $red - * @param integer $green - * @param integer $blue + * @param int $red + * @param int $green + * @param int $blue * * @return array */ @@ -7622,7 +7709,7 @@ private function RGBtoHWB($red, $green, $blue) $h = 60 * ($green - $blue) / $d; } elseif ($green == $max) { $h = 60 * ($blue - $red) / $d + 120; - } elseif ($blue == $max) { + } else { $h = 60 * ($red - $green) / $d + 240; } } @@ -7731,7 +7818,6 @@ protected function libIndex($args) $values = []; - foreach ($list[2] as $item) { $values[] = $this->normalizeValue($item); } @@ -7747,6 +7833,14 @@ protected function libIndex($args) ['channels'], ['red', 'green', 'blue'], ['red', 'green', 'blue', 'alpha'] ]; + + /** + * @param array $args + * @param array $kwargs + * @param string $funcName + * + * @return array + */ protected function libRgb($args, $kwargs, $funcName = 'rgb') { switch (\count($args)) { @@ -7829,14 +7923,7 @@ protected function alterColor(array $args, $operation, $fn) $scale = $operation === 'scale'; $change = $operation === 'change'; - /** - * @param string $name - * @param float|int $max - * @param bool $checkPercent - * @param bool $assertPercent - * - * @return float|int|null - */ + /** @phpstan-var callable(string, float|int, bool=, bool=): (float|int|null) $getParam */ $getParam = function ($name, $max, $checkPercent = false, $assertPercent = false) use (&$kwargs, $scale, $change) { if (!isset($kwargs[$name])) { return null; @@ -7860,7 +7947,11 @@ protected function alterColor(array $args, $operation, $fn) $max = 100; } - return $number->valueInRange($change ? 0 : -$max, $max, $name); + if ($scale || $assertPercent) { + return $number->valueInRange($change ? 0 : -$max, $max, $name); + } + + return $number->valueInRangeWithUnit($change ? 0 : -$max, $max, $name, $checkPercent ? '%' : ''); }; $alpha = $getParam('alpha', 1); @@ -7895,7 +7986,6 @@ protected function alterColor(array $args, $operation, $fn) $hasRgb = $red !== null || $green !== null || $blue !== null; $hasSL = $saturation !== null || $lightness !== null; $hasWB = $whiteness !== null || $blackness !== null; - $found = false; if ($hasRgb && ($hasSL || $hasWB || $hue !== null)) { throw new SassScriptException(sprintf('RGB parameters may not be passed along with %s parameters.', $hasWB ? 'HWB' : 'HSL')); @@ -8116,6 +8206,14 @@ protected function libMix($args) ['hue', 'saturation'], ['hue', 'saturation', 'lightness'], ['hue', 'saturation', 'lightness', 'alpha'] ]; + + /** + * @param array $args + * @param array $kwargs + * @param string $funcName + * + * @return array|null + */ protected function libHsl($args, $kwargs, $funcName = 'hsl') { $args_to_check = $args; @@ -8140,7 +8238,7 @@ protected function libHsl($args, $kwargs, $funcName = 'hsl') throw new SassScriptException('Missing argument $lightness.'); } - foreach ($kwargs as $k => $arg) { + foreach ($kwargs as $arg) { if (in_array($arg[0], [Type::T_FUNCTION_CALL, Type::T_FUNCTION]) && in_array($arg[1], ['min', 'max'])) { return null; } @@ -8360,6 +8458,13 @@ protected function libBlackness($args, $kwargs, $funcName = 'blackness') { } */ + /** + * @param array $color + * @param int $idx + * @param int|float $amount + * + * @return array + */ protected function adjustHsl($color, $idx, $amount) { $hsl = $this->toHSL($color[1], $color[2], $color[3]); @@ -8481,7 +8586,7 @@ protected function libOpacify($args) $color = $this->assertColor($args[0], 'color'); $amount = $this->assertNumber($args[1], 'amount'); - $color[4] = (isset($color[4]) ? $color[4] : 1) + $amount->valueInRange(0, 1, 'amount'); + $color[4] = (isset($color[4]) ? $color[4] : 1) + $amount->valueInRangeWithUnit(0, 1, 'amount', ''); $color[4] = min(1, max(0, $color[4])); return $color; @@ -8500,7 +8605,7 @@ protected function libTransparentize($args) $color = $this->assertColor($args[0], 'color'); $amount = $this->assertNumber($args[1], 'amount'); - $color[4] = (isset($color[4]) ? $color[4] : 1) - $amount->valueInRange(0, 1, 'amount'); + $color[4] = (isset($color[4]) ? $color[4] : 1) - $amount->valueInRangeWithUnit(0, 1, 'amount', ''); $color[4] = min(1, max(0, $color[4])); return $color; @@ -8649,7 +8754,7 @@ protected function libListSeparator($args) $list = $this->coerceList($args[0]); - if (\count($list[2]) <= 1 && empty($list['enclosing'])) { + if ($list[1] === '' && \count($list[2]) <= 1 && empty($list['enclosing'])) { return [Type::T_KEYWORD, 'space']; } @@ -8657,6 +8762,10 @@ protected function libListSeparator($args) return [Type::T_KEYWORD, 'comma']; } + if ($list[1] === '/') { + return [Type::T_KEYWORD, 'slash']; + } + return [Type::T_KEYWORD, 'space']; } @@ -8664,7 +8773,7 @@ protected function libListSeparator($args) protected function libNth($args) { $list = $this->coerceList($args[0], ',', false); - $n = $this->assertNumber($args[1])->getDimension(); + $n = $this->assertInteger($args[1]); if ($n > 0) { $n--; @@ -8679,7 +8788,7 @@ protected function libNth($args) protected function libSetNth($args) { $list = $this->coerceList($args[0]); - $n = $this->assertNumber($args[1])->getDimension(); + $n = $this->assertInteger($args[1]); if ($n > 0) { $n--; @@ -8696,23 +8805,72 @@ protected function libSetNth($args) return $list; } - protected static $libMapGet = ['map', 'key']; + protected static $libMapGet = ['map', 'key', 'keys...']; protected function libMapGet($args) { $map = $this->assertMap($args[0], 'map'); - $key = $args[1]; + if (!isset($args[2])) { + // BC layer for usages of the function from PHP code rather than from the Sass function + $args[2] = self::$emptyArgumentList; + } + $keys = array_merge([$args[1]], $args[2][2]); + $value = static::$null; + + foreach ($keys as $key) { + if (!\is_array($map) || $map[0] !== Type::T_MAP) { + return static::$null; + } - if (! \is_null($key)) { - $key = $this->compileStringContent($this->coerceString($key)); + $map = $this->mapGet($map, $key); - for ($i = \count($map[1]) - 1; $i >= 0; $i--) { - if ($key === $this->compileStringContent($this->coerceString($map[1][$i]))) { - return $map[2][$i]; - } + if ($map === null) { + return static::$null; } + + $value = $map; } - return static::$null; + return $value; + } + + /** + * Gets the value corresponding to that key in the map + * + * @param array $map + * @param Number|array $key + * + * @return Number|array|null + */ + private function mapGet(array $map, $key) + { + $index = $this->mapGetEntryIndex($map, $key); + + if ($index !== null) { + return $map[2][$index]; + } + + return null; + } + + /** + * Gets the index corresponding to that key in the map entries + * + * @param array $map + * @param Number|array $key + * + * @return int|null + */ + private function mapGetEntryIndex(array $map, $key) + { + $key = $this->compileStringContent($this->coerceString($key)); + + for ($i = \count($map[1]) - 1; $i >= 0; $i--) { + if ($key === $this->compileStringContent($this->coerceString($map[1][$i]))) { + return $i; + } + } + + return null; } protected static $libMapKeys = ['map']; @@ -8762,12 +8920,28 @@ protected function libMapRemove($args) return $map; } - protected static $libMapHasKey = ['map', 'key']; + protected static $libMapHasKey = ['map', 'key', 'keys...']; protected function libMapHasKey($args) { $map = $this->assertMap($args[0], 'map'); + if (!isset($args[2])) { + // BC layer for usages of the function from PHP code rather than from the Sass function + $args[2] = self::$emptyArgumentList; + } + $keys = array_merge([$args[1]], $args[2][2]); + $lastKey = array_pop($keys); + + foreach ($keys as $key) { + $value = $this->mapGet($map, $key); + + if ($value === null || $value instanceof Number || $value[0] !== Type::T_MAP) { + return self::$false; + } + + $map = $value; + } - return $this->toBool($this->mapHasKey($map, $args[1])); + return $this->toBool($this->mapHasKey($map, $lastKey)); } /** @@ -8790,24 +8964,127 @@ private function mapHasKey(array $map, $keyValue) protected static $libMapMerge = [ ['map1', 'map2'], - ['map-1', 'map-2'] + ['map-1', 'map-2'], + ['map1', 'args...'] ]; protected function libMapMerge($args) { $map1 = $this->assertMap($args[0], 'map1'); - $map2 = $this->assertMap($args[1], 'map2'); + $map2 = $args[1]; + $keys = []; + if ($map2[0] === Type::T_LIST && isset($map2[3]) && \is_array($map2[3])) { + // This is an argument list for the variadic signature + if (\count($map2[2]) === 0) { + throw new SassScriptException('Expected $args to contain a key.'); + } + if (\count($map2[2]) === 1) { + throw new SassScriptException('Expected $args to contain a value.'); + } + $keys = $map2[2]; + $map2 = array_pop($keys); + } + $map2 = $this->assertMap($map2, 'map2'); + + return $this->modifyMap($map1, $keys, function ($oldValue) use ($map2) { + $nestedMap = $this->tryMap($oldValue); + + if ($nestedMap === null) { + return $map2; + } + + return $this->mergeMaps($nestedMap, $map2); + }); + } + + /** + * @param array $map + * @param array $keys + * @param callable $modify + * @param bool $addNesting + * + * @return Number|array + * + * @phpstan-param array $keys + * @phpstan-param callable(Number|array): (Number|array) $modify + */ + private function modifyMap(array $map, array $keys, callable $modify, $addNesting = true) + { + if ($keys === []) { + return $modify($map); + } + + return $this->modifyNestedMap($map, $keys, $modify, $addNesting); + } + + /** + * @param array $map + * @param array $keys + * @param callable $modify + * @param bool $addNesting + * + * @return array + * + * @phpstan-param non-empty-array $keys + * @phpstan-param callable(Number|array): (Number|array) $modify + */ + private function modifyNestedMap(array $map, array $keys, callable $modify, $addNesting) + { + $key = array_shift($keys); + + $nestedValueIndex = $this->mapGetEntryIndex($map, $key); + + if ($keys === []) { + if ($nestedValueIndex !== null) { + $map[2][$nestedValueIndex] = $modify($map[2][$nestedValueIndex]); + } else { + $map[1][] = $key; + $map[2][] = $modify(self::$null); + } + + return $map; + } + + $nestedMap = $nestedValueIndex !== null ? $this->tryMap($map[2][$nestedValueIndex]) : null; + + if ($nestedMap === null && !$addNesting) { + return $map; + } + + if ($nestedMap === null) { + $nestedMap = self::$emptyMap; + } + + $newNestedMap = $this->modifyNestedMap($nestedMap, $keys, $modify, $addNesting); + if ($nestedValueIndex !== null) { + $map[2][$nestedValueIndex] = $newNestedMap; + } else { + $map[1][] = $key; + $map[2][] = $newNestedMap; + } + + return $map; + } + + /** + * Merges 2 Sass maps together + * + * @param array $map1 + * @param array $map2 + * + * @return array + */ + private function mergeMaps(array $map1, array $map2) + { foreach ($map2[1] as $i2 => $key2) { - $key = $this->compileStringContent($this->coerceString($key2)); + $map1EntryIndex = $this->mapGetEntryIndex($map1, $key2); - foreach ($map1[1] as $i1 => $key1) { - if ($key === $this->compileStringContent($this->coerceString($key1))) { - $map1[2][$i1] = $map2[2][$i2]; - continue 2; - } + if ($map1EntryIndex !== null) { + $map1[2][$map1EntryIndex] = $map2[2][$i2]; + continue; } - $map1[1][] = $map2[1][$i2]; + $map1[1][] = $key2; $map1[2][] = $map2[2][$i2]; } @@ -8855,9 +9132,13 @@ protected function libIsBracketed($args) * * @return string * @throws CompilerException + * + * @deprecated */ protected function listSeparatorForJoin($list1, $sep) { + @trigger_error(sprintf('The "%s" method is deprecated.', __METHOD__), E_USER_DEPRECATED); + if (! isset($sep)) { return $list1[1]; } @@ -8874,14 +9155,40 @@ protected function listSeparatorForJoin($list1, $sep) } } - protected static $libJoin = ['list1', 'list2', 'separator:null', 'bracketed:auto']; + protected static $libJoin = ['list1', 'list2', 'separator:auto', 'bracketed:auto']; protected function libJoin($args) { list($list1, $list2, $sep, $bracketed) = $args; $list1 = $this->coerceList($list1, ' ', true); $list2 = $this->coerceList($list2, ' ', true); - $sep = $this->listSeparatorForJoin($list1, $sep); + + switch ($this->compileStringContent($this->assertString($sep, 'separator'))) { + case 'comma': + $separator = ','; + break; + + case 'space': + $separator = ' '; + break; + + case 'slash': + $separator = '/'; + break; + + case 'auto': + if ($list1[1] !== '' || count($list1[2]) > 1 || !empty($list1['enclosing']) && $list1['enclosing'] !== 'parent') { + $separator = $list1[1] ?: ' '; + } elseif ($list2[1] !== '' || count($list2[2]) > 1 || !empty($list2['enclosing']) && $list2['enclosing'] !== 'parent') { + $separator = $list2[1] ?: ' '; + } else { + $separator = ' '; + } + break; + + default: + throw SassScriptException::forArgument('Must be "space", "comma", "slash", or "auto".', 'separator'); + } if ($bracketed === static::$true) { $bracketed = true; @@ -8908,11 +9215,7 @@ protected function libJoin($args) } } - $res = [Type::T_LIST, $sep, array_merge($list1[2], $list2[2])]; - - if (isset($list1['enclosing'])) { - $res['enlcosing'] = $list1['enclosing']; - } + $res = [Type::T_LIST, $separator, array_merge($list1[2], $list2[2])]; if ($bracketed) { $res['enclosing'] = 'bracket'; @@ -8921,14 +9224,35 @@ protected function libJoin($args) return $res; } - protected static $libAppend = ['list', 'val', 'separator:null']; + protected static $libAppend = ['list', 'val', 'separator:auto']; protected function libAppend($args) { list($list1, $value, $sep) = $args; $list1 = $this->coerceList($list1, ' ', true); - $sep = $this->listSeparatorForJoin($list1, $sep); - $res = [Type::T_LIST, $sep, array_merge($list1[2], [$value])]; + + switch ($this->compileStringContent($this->assertString($sep, 'separator'))) { + case 'comma': + $separator = ','; + break; + + case 'space': + $separator = ' '; + break; + + case 'slash': + $separator = '/'; + break; + + case 'auto': + $separator = $list1[1] === '' && \count($list1[2]) <= 1 && (empty($list1['enclosing']) || $list1['enclosing'] === 'parent') ? ' ' : $list1[1]; + break; + + default: + throw SassScriptException::forArgument('Must be "space", "comma", "slash", or "auto".', 'separator'); + } + + $res = [Type::T_LIST, $separator, array_merge($list1[2], [$value])]; if (isset($list1['enclosing'])) { $res['enclosing'] = $list1['enclosing']; @@ -8951,7 +9275,7 @@ protected function libZip($args) $result = [Type::T_LIST, ',', $lists]; if (! \is_null($firstList)) { foreach ($firstList[2] as $key => $item) { - $list = [Type::T_LIST, '', [$item]]; + $list = [Type::T_LIST, ' ', [$item]]; foreach ($argLists as $arg) { if (isset($arg[2][$key])) { @@ -9081,7 +9405,7 @@ protected function libStrInsert($args) $index = $index - 1; } if ($index < 0) { - $index = Util::mbStrlen($stringContent) + 1 + $index; + $index = max(Util::mbStrlen($stringContent) + 1 + $index, 0); } $string[2] = [ @@ -9269,7 +9593,25 @@ protected function libCounter($args) protected function libRandom($args) { if (isset($args[0]) && $args[0] !== static::$null) { - $n = $this->assertInteger($args[0], 'limit'); + $limit = $this->assertNumber($args[0], 'limit'); + + if ($limit->hasUnits()) { + $unitString = $limit->unitStr(); + $message = <<addLocationToMessage($message)); + } + + $n = $this->assertInteger($limit, 'limit'); if ($n < 1) { throw new SassScriptException("\$limit: Must be greater than 0, was $n."); @@ -9331,6 +9673,8 @@ protected function inspectFormatValue($value, $force_enclosing_display = false) ) { $value['enclosing'] = 'forced_' . $value['enclosing']; $force_enclosing_display = true; + } elseif (! \count($value[2])) { + $value['enclosing'] = 'forced_parent'; } foreach ($value[2] as $k => $listelement) { @@ -9454,7 +9798,7 @@ protected function libIsSuperselector($args) * @param array $super * @param array $sub * - * @return boolean + * @return bool */ protected function isSuperSelector($super, $sub) { @@ -9535,7 +9879,7 @@ function ($value, $key) use (&$compound) { * @param array $superParts * @param array $subParts * - * @return boolean + * @return bool */ protected function isSuperPart($superParts, $subParts) { @@ -9602,21 +9946,18 @@ protected function selectorAppend($selectors) // 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 ($previousSelectors as $previousSelector) { + foreach ($lastSelectors as $lastSelector) { + $previous = $previousSelector; + foreach ($previousSelector as $j => $previousSelectorParts) { + foreach ($lastSelector as $lastSelectorParts) { + foreach ($lastSelectorParts as $lastSelectorPart) { + $previous[$j][] = $lastSelectorPart; } } } - } - foreach ($previous as $ps) { - $appended[] = $ps; + $appended[] = $previous; } } @@ -9672,10 +10013,10 @@ protected function libSelectorReplace($args) * Extend/replace in selectors * used by selector-extend and selector-replace that use the same logic * - * @param array $selectors - * @param array $extendee - * @param array $extender - * @param boolean $replace + * @param array $selectors + * @param array $extendee + * @param array $extender + * @param bool $replace * * @return array */ @@ -9735,6 +10076,8 @@ protected function libSelectorNest($args) $selectorsMap[] = $this->getSelectorArg($arg, 'selector', true); } + assert(!empty($selectorsMap)); + $envs = []; foreach ($selectorsMap as $selectors) { diff --git a/Contrib/scssphp/src/Compiler/Environment.php b/Contrib/scssphp/src/Compiler/Environment.php index 306b15a67..b205a077f 100644 --- a/Contrib/scssphp/src/Compiler/Environment.php +++ b/Contrib/scssphp/src/Compiler/Environment.php @@ -31,6 +31,26 @@ class Environment */ public $parent; + /** + * @var Environment|null + */ + public $declarationScopeParent; + + /** + * @var Environment|null + */ + public $parentStore; + + /** + * @var array|null + */ + public $selectors; + + /** + * @var string|null + */ + public $marker; + /** * @var array */ @@ -42,7 +62,7 @@ class Environment public $storeUnreduced; /** - * @var integer + * @var int */ public $depth; } diff --git a/Contrib/scssphp/src/Exception/ParserException.php b/Contrib/scssphp/src/Exception/ParserException.php index 00d77ec98..f0726698f 100644 --- a/Contrib/scssphp/src/Exception/ParserException.php +++ b/Contrib/scssphp/src/Exception/ParserException.php @@ -22,7 +22,8 @@ class ParserException extends \Exception implements SassException { /** - * @var array + * @var array|null + * @phpstan-var array{string, int, int}|null */ private $sourcePosition; @@ -30,6 +31,9 @@ class ParserException extends \Exception implements SassException * Get source position * * @api + * + * @return array|null + * @phpstan-return array{string, int, int}|null */ public function getSourcePosition() { @@ -42,6 +46,10 @@ public function getSourcePosition() * @api * * @param array $sourcePosition + * + * @return void + * + * @phpstan-param array{string, int, int} $sourcePosition */ public function setSourcePosition($sourcePosition) { diff --git a/Contrib/scssphp/src/Formatter.php b/Contrib/scssphp/src/Formatter.php index cc42ae805..6137dc650 100644 --- a/Contrib/scssphp/src/Formatter.php +++ b/Contrib/scssphp/src/Formatter.php @@ -25,7 +25,7 @@ abstract class Formatter { /** - * @var integer + * @var int */ public $indentLevel; @@ -60,7 +60,7 @@ abstract class Formatter public $assignSeparator; /** - * @var boolean + * @var bool */ public $keepSemicolons; @@ -70,12 +70,12 @@ abstract class Formatter protected $currentBlock; /** - * @var integer + * @var int */ protected $currentLine; /** - * @var integer + * @var int */ protected $currentColumn; @@ -239,7 +239,7 @@ protected function block(OutputBlock $block) * * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block * - * @return boolean + * @return bool */ protected function testEmptyChildren($block) { @@ -286,9 +286,18 @@ public function format(OutputBlock $block, SourceMapGenerator $sourceMapGenerato ob_start(); - $this->block($block); + try { + $this->block($block); + } catch (\Exception $e) { + ob_end_clean(); + throw $e; + } catch (\Throwable $e) { + ob_end_clean(); + throw $e; + } $out = ob_get_clean(); + assert($out !== false); return $out; } @@ -331,6 +340,8 @@ protected function write($str) // If the written line starts is empty, adding a mapping would add it for // a non-existent column as we are at the end of the line if ($line !== '') { + assert($this->currentBlock->sourceLine !== null); + assert($this->currentBlock->sourceName !== null); $this->sourceMapGenerator->addMapping( $this->currentLine, $this->currentColumn, @@ -346,6 +357,8 @@ protected function write($str) } if ($lastLine !== '') { + assert($this->currentBlock->sourceLine !== null); + assert($this->currentBlock->sourceName !== null); $this->sourceMapGenerator->addMapping( $this->currentLine, $this->currentColumn, diff --git a/Contrib/scssphp/src/Formatter/Compressed.php b/Contrib/scssphp/src/Formatter/Compressed.php index de13c188a..58ebe3f11 100644 --- a/Contrib/scssphp/src/Formatter/Compressed.php +++ b/Contrib/scssphp/src/Formatter/Compressed.php @@ -50,8 +50,6 @@ public function blockLines(OutputBlock $block) foreach ($block->lines as $index => $line) { if (substr($line, 0, 2) === '/*' && substr($line, 2, 1) !== '!') { unset($block->lines[$index]); - } elseif (substr($line, 0, 3) === '/*!') { - $block->lines[$index] = '/*' . substr($line, 3); } } diff --git a/Contrib/scssphp/src/Formatter/Expanded.php b/Contrib/scssphp/src/Formatter/Expanded.php index a280416de..6eb4a0cb7 100644 --- a/Contrib/scssphp/src/Formatter/Expanded.php +++ b/Contrib/scssphp/src/Formatter/Expanded.php @@ -57,7 +57,9 @@ protected function blockLines(OutputBlock $block) foreach ($block->lines as $index => $line) { if (substr($line, 0, 2) === '/*') { - $block->lines[$index] = preg_replace('/\r\n?|\n|\f/', $this->break, $line); + $replacedLine = preg_replace('/\r\n?|\n|\f/', $this->break, $line); + assert($replacedLine !== null); + $block->lines[$index] = $replacedLine; } } diff --git a/Contrib/scssphp/src/Formatter/Nested.php b/Contrib/scssphp/src/Formatter/Nested.php index 9e7295684..d5ed85cc2 100644 --- a/Contrib/scssphp/src/Formatter/Nested.php +++ b/Contrib/scssphp/src/Formatter/Nested.php @@ -27,7 +27,7 @@ class Nested extends Formatter { /** - * @var integer + * @var int */ private $depth; @@ -68,7 +68,9 @@ protected function blockLines(OutputBlock $block) foreach ($block->lines as $index => $line) { if (substr($line, 0, 2) === '/*') { - $block->lines[$index] = preg_replace('/\r\n?|\n|\f/', $this->break, $line); + $replacedLine = preg_replace('/\r\n?|\n|\f/', $this->break, $line); + assert($replacedLine !== null); + $block->lines[$index] = $replacedLine; } } @@ -221,7 +223,7 @@ protected function block(OutputBlock $block) * * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block * - * @return boolean + * @return bool */ private function hasFlatChild($block) { diff --git a/Contrib/scssphp/src/Formatter/OutputBlock.php b/Contrib/scssphp/src/Formatter/OutputBlock.php index 88deb2d37..2799656a4 100644 --- a/Contrib/scssphp/src/Formatter/OutputBlock.php +++ b/Contrib/scssphp/src/Formatter/OutputBlock.php @@ -22,12 +22,12 @@ class OutputBlock { /** - * @var string + * @var string|null */ public $type; /** - * @var integer + * @var int */ public $depth; @@ -57,12 +57,12 @@ class OutputBlock public $sourceName; /** - * @var integer|null + * @var int|null */ public $sourceLine; /** - * @var integer|null + * @var int|null */ public $sourceColumn; } diff --git a/Contrib/scssphp/src/Logger/QuietLogger.php b/Contrib/scssphp/src/Logger/QuietLogger.php index 0f358c646..ad7c07537 100644 --- a/Contrib/scssphp/src/Logger/QuietLogger.php +++ b/Contrib/scssphp/src/Logger/QuietLogger.php @@ -14,6 +14,8 @@ /** * A logger that silently ignores all messages. + * + * @final */ class QuietLogger implements LoggerInterface { diff --git a/Contrib/scssphp/src/Logger/StreamLogger.php b/Contrib/scssphp/src/Logger/StreamLogger.php index f5da6c9cf..7db7cc189 100644 --- a/Contrib/scssphp/src/Logger/StreamLogger.php +++ b/Contrib/scssphp/src/Logger/StreamLogger.php @@ -14,6 +14,8 @@ /** * A logger that prints to a PHP stream (for instance stderr) + * + * @final */ class StreamLogger implements LoggerInterface { diff --git a/Contrib/scssphp/src/Node.php b/Contrib/scssphp/src/Node.php index 53019375a..fcaf8a95f 100644 --- a/Contrib/scssphp/src/Node.php +++ b/Contrib/scssphp/src/Node.php @@ -27,7 +27,7 @@ abstract class Node public $type; /** - * @var integer + * @var int */ public $sourceIndex; diff --git a/Contrib/scssphp/src/Node/Number.php b/Contrib/scssphp/src/Node/Number.php index b326906b5..48e711f08 100644 --- a/Contrib/scssphp/src/Node/Number.php +++ b/Contrib/scssphp/src/Node/Number.php @@ -38,7 +38,7 @@ class Number extends Node implements \ArrayAccess const PRECISION = 10; /** - * @var integer + * @var int * @deprecated use {Number::PRECISION} instead to read the precision. Configuring it is not supported anymore. */ public static $precision = self::PRECISION; @@ -81,7 +81,7 @@ class Number extends Node implements \ArrayAccess ]; /** - * @var integer|float + * @var int|float */ private $dimension; @@ -100,7 +100,7 @@ class Number extends Node implements \ArrayAccess /** * Initialize number * - * @param integer|float $dimension + * @param int|float $dimension * @param string[]|string $numeratorUnits * @param string[] $denominatorUnits * @@ -147,7 +147,7 @@ public function getDenominatorUnits() } /** - * {@inheritdoc} + * @return bool */ #[\ReturnTypeWillChange] public function offsetExists($offset) @@ -173,7 +173,7 @@ public function offsetExists($offset) } /** - * {@inheritdoc} + * @return mixed */ #[\ReturnTypeWillChange] public function offsetGet($offset) @@ -200,7 +200,7 @@ public function offsetGet($offset) } /** - * {@inheritdoc} + * @return void */ #[\ReturnTypeWillChange] public function offsetSet($offset, $value) @@ -209,7 +209,7 @@ public function offsetSet($offset, $value) } /** - * {@inheritdoc} + * @return void */ #[\ReturnTypeWillChange] public function offsetUnset($offset) @@ -220,13 +220,23 @@ public function offsetUnset($offset) /** * Returns true if the number is unitless * - * @return boolean + * @return bool */ public function unitless() { return \count($this->numeratorUnits) === 0 && \count($this->denominatorUnits) === 0; } + /** + * Returns true if the number has any units + * + * @return bool + */ + public function hasUnits() + { + return !$this->unitless(); + } + /** * Checks whether the number has exactly this unit * @@ -266,7 +276,27 @@ public function valueInRange($min, $max, $name = null) try { return Util::checkRange('', new Range($min, $max), $this); } catch (RangeException $e) { - throw SassScriptException::forArgument(sprintf('Expected %s to be within %s%s and %s%3$s', $this, $min, $this->unitStr(), $max), $name); + throw SassScriptException::forArgument(sprintf('Expected %s to be within %s%s and %s%3$s.', $this, $min, $this->unitStr(), $max), $name); + } + } + + /** + * @param float|int $min + * @param float|int $max + * @param string $name + * @param string $unit + * + * @return float|int + * @throws SassScriptException + * + * @internal + */ + public function valueInRangeWithUnit($min, $max, $name, $unit) + { + try { + return Util::checkRange('', new Range($min, $max), $this); + } catch (RangeException $e) { + throw SassScriptException::forArgument(sprintf('Expected %s to be within %s%s and %s%3$s.', $this, $min, $unit, $max), $name); } } diff --git a/Contrib/scssphp/src/Parser.php b/Contrib/scssphp/src/Parser.php index 3ba2f67f9..1c76e7c6b 100644 --- a/Contrib/scssphp/src/Parser.php +++ b/Contrib/scssphp/src/Parser.php @@ -12,9 +12,22 @@ namespace ScssPhp\ScssPhp; +use ScssPhp\ScssPhp\Block\AtRootBlock; +use ScssPhp\ScssPhp\Block\CallableBlock; +use ScssPhp\ScssPhp\Block\ContentBlock; +use ScssPhp\ScssPhp\Block\DirectiveBlock; +use ScssPhp\ScssPhp\Block\EachBlock; +use ScssPhp\ScssPhp\Block\ElseBlock; +use ScssPhp\ScssPhp\Block\ElseifBlock; +use ScssPhp\ScssPhp\Block\ForBlock; +use ScssPhp\ScssPhp\Block\IfBlock; +use ScssPhp\ScssPhp\Block\MediaBlock; +use ScssPhp\ScssPhp\Block\NestedPropertyBlock; +use ScssPhp\ScssPhp\Block\WhileBlock; use ScssPhp\ScssPhp\Exception\ParserException; use ScssPhp\ScssPhp\Logger\LoggerInterface; use ScssPhp\ScssPhp\Logger\QuietLogger; +use ScssPhp\ScssPhp\Node\Number; /** * Parser @@ -73,10 +86,6 @@ class Parser * @var array */ private $sourcePositions; - /** - * @var array|null - */ - private $charset; /** * The current offset in the buffer * @@ -125,7 +134,7 @@ class Parser * @api * * @param string|null $sourceName - * @param integer $sourceIndex + * @param int $sourceIndex * @param string|null $encoding * @param Cache|null $cache * @param bool $cssOnly @@ -135,11 +144,9 @@ public function __construct($sourceName, $sourceIndex = 0, $encoding = 'utf-8', { $this->sourceName = $sourceName ?: '(stdin)'; $this->sourceIndex = $sourceIndex; - $this->charset = null; $this->utf8 = ! $encoding || strtolower($encoding) === 'utf-8'; $this->patternModifiers = $this->utf8 ? 'Aisu' : 'Ais'; $this->commentsSeen = []; - $this->commentsSeen = []; $this->allowVars = true; $this->cssOnly = $cssOnly; $this->logger = $logger ?: new QuietLogger(); @@ -243,7 +250,6 @@ public function parse($buffer) if ($this->cache) { $cacheKey = $this->sourceName . ':' . md5($buffer); $parseOptions = [ - 'charset' => $this->charset, 'utf8' => $this->utf8, ]; $v = $this->cache->getCache('parse', $cacheKey, $parseOptions); @@ -284,11 +290,8 @@ public function parse($buffer) throw $this->parseError('unclosed block'); } - if ($this->charset) { - array_unshift($this->env->children, $this->charset); - } - $this->restoreEncoding(); + assert($this->env !== null); if ($this->cache) { $this->cache->setCache('parse', $cacheKey, $this->env, $parseOptions); @@ -305,7 +308,7 @@ public function parse($buffer) * @param string $buffer * @param string|array $out * - * @return boolean + * @return bool */ public function parseValue($buffer, &$out) { @@ -334,7 +337,7 @@ public function parseValue($buffer, &$out) * @param string|array $out * @param bool $shouldValidate * - * @return boolean + * @return bool */ public function parseSelector($buffer, &$out, $shouldValidate = true) { @@ -368,10 +371,10 @@ public function parseSelector($buffer, &$out, $shouldValidate = true) * * @api * - * @param string $buffer - * @param string|array $out + * @param string $buffer + * @param array $out * - * @return boolean + * @return bool */ public function parseMediaQueryList($buffer, &$out) { @@ -428,7 +431,7 @@ public function parseMediaQueryList($buffer, &$out) * position into $s. Then if a chain fails, use $this->seek($s) to * go back where we started. * - * @return boolean + * @return bool */ protected function parseChunk() { @@ -447,7 +450,8 @@ protected function parseChunk() ) { ! $this->cssOnly || $this->assertPlainCssValid(false, $s); - $atRoot = $this->pushSpecialBlock(Type::T_AT_ROOT, $s); + $atRoot = new AtRootBlock(); + $this->registerPushedBlock($atRoot, $s); $atRoot->selector = $selector; $atRoot->with = $with; @@ -461,7 +465,8 @@ protected function parseChunk() $this->mediaQueryList($mediaQueryList) && $this->matchChar('{', false) ) { - $media = $this->pushSpecialBlock(Type::T_MEDIA, $s); + $media = new MediaBlock(); + $this->registerPushedBlock($media, $s); $media->queryList = $mediaQueryList[2]; return true; @@ -477,7 +482,8 @@ protected function parseChunk() ) { ! $this->cssOnly || $this->assertPlainCssValid(false, $s); - $mixin = $this->pushSpecialBlock(Type::T_MIXIN, $s); + $mixin = new CallableBlock(Type::T_MIXIN); + $this->registerPushedBlock($mixin, $s); $mixin->name = $mixinName; $mixin->args = $args; @@ -509,7 +515,8 @@ protected function parseChunk() ]; if (! empty($hasBlock)) { - $include = $this->pushSpecialBlock(Type::T_INCLUDE, $s); + $include = new ContentBlock(); + $this->registerPushedBlock($include, $s); $include->child = $child; } else { $this->append($child, $s); @@ -599,7 +606,8 @@ protected function parseChunk() ) { ! $this->cssOnly || $this->assertPlainCssValid(false, $s); - $func = $this->pushSpecialBlock(Type::T_FUNCTION, $s); + $func = new CallableBlock(Type::T_FUNCTION); + $this->registerPushedBlock($func, $s); $func->name = $fnName; $func->args = $args; @@ -631,7 +639,8 @@ protected function parseChunk() ) { ! $this->cssOnly || $this->assertPlainCssValid(false, $s); - $each = $this->pushSpecialBlock(Type::T_EACH, $s); + $each = new EachBlock(); + $this->registerPushedBlock($each, $s); foreach ($varNames[2] as $varName) { $each->vars[] = $varName[1]; @@ -660,7 +669,8 @@ protected function parseChunk() $cond = reset($cond[2]); } - $while = $this->pushSpecialBlock(Type::T_WHILE, $s); + $while = new WhileBlock(); + $this->registerPushedBlock($while, $s); $while->cond = $cond; return true; @@ -680,7 +690,8 @@ protected function parseChunk() ) { ! $this->cssOnly || $this->assertPlainCssValid(false, $s); - $for = $this->pushSpecialBlock(Type::T_FOR, $s); + $for = new ForBlock(); + $this->registerPushedBlock($for, $s); $for->var = $varName[1]; $for->start = $start; $for->end = $end; @@ -697,7 +708,8 @@ protected function parseChunk() ) { ! $this->cssOnly || $this->assertPlainCssValid(false, $s); - $if = $this->pushSpecialBlock(Type::T_IF, $s); + $if = new IfBlock(); + $this->registerPushedBlock($if, $s); while ( $cond[0] === Type::T_LIST && @@ -776,20 +788,21 @@ protected function parseChunk() if (isset($last) && $last[0] === Type::T_IF) { list(, $if) = $last; + assert($if instanceof IfBlock); if ($this->literal('@else', 5)) { if ($this->matchChar('{', false)) { - $else = $this->pushSpecialBlock(Type::T_ELSE, $s); + $else = new ElseBlock(); } elseif ( $this->literal('if', 2) && $this->functionCallArgumentsList($cond, false, '{', false) ) { - $else = $this->pushSpecialBlock(Type::T_ELSEIF, $s); + $else = new ElseifBlock(); $else->cond = $cond; } if (isset($else)) { - $else->dontAppend = true; + $this->registerPushedBlock($else, $s); $if->cases[] = $else; return true; @@ -805,18 +818,6 @@ protected function parseChunk() $this->valueList($charset) && $this->end() ) { - if (! isset($this->charset)) { - $statement = [Type::T_CHARSET, $charset]; - - list($line, $column) = $this->getSourcePosition($s); - - $statement[static::SOURCE_LINE] = $line; - $statement[static::SOURCE_COLUMN] = $column; - $statement[static::SOURCE_INDEX] = $this->sourceIndex; - - $this->charset = $statement; - } - return true; } @@ -827,7 +828,8 @@ protected function parseChunk() ($t1 = $this->supportsQuery($supportQuery)) && ($t2 = $this->matchChar('{', false)) ) { - $directive = $this->pushSpecialBlock(Type::T_DIRECTIVE, $s); + $directive = new DirectiveBlock(); + $this->registerPushedBlock($directive, $s); $directive->name = 'supports'; $directive->value = $supportQuery; @@ -848,11 +850,12 @@ protected function parseChunk() $dirName = [Type::T_STRING, '', $dirName]; } if ($dirName === 'media') { - $directive = $this->pushSpecialBlock(Type::T_MEDIA, $s); + $directive = new MediaBlock(); } else { - $directive = $this->pushSpecialBlock(Type::T_DIRECTIVE, $s); + $directive = new DirectiveBlock(); $directive->name = $dirName; } + $this->registerPushedBlock($directive, $s); if (isset($dirValue)) { ! $this->cssOnly || ($dirValue = $this->assertPlainCssValid($dirValue)); @@ -1028,7 +1031,8 @@ protected function parseChunk() if ($this->matchChar('{', false)) { ! $this->cssOnly || $this->assertPlainCssValid(false); - $propBlock = $this->pushSpecialBlock(Type::T_NESTED_PROPERTY, $s); + $propBlock = new NestedPropertyBlock(); + $this->registerPushedBlock($propBlock, $s); $propBlock->prefix = $name; $propBlock->hasValue = $foundSomething; @@ -1049,17 +1053,20 @@ protected function parseChunk() $block = $this->popBlock(); if (! isset($block->type) || $block->type !== Type::T_IF) { + assert($this->env !== null); + if ($this->env->parent) { $this->append(null); // collect comments before next statement if needed } } - if (isset($block->type) && $block->type === Type::T_INCLUDE) { + if ($block instanceof ContentBlock) { $include = $block->child; + assert(\is_array($include)); unset($block->child); $include[3] = $block; $this->append($include, $s); - } elseif (empty($block->dontAppend)) { + } elseif (!$block instanceof ElseBlock && !$block instanceof ElseifBlock) { $type = isset($block->type) ? $block->type : Type::T_BLOCK; $this->append([$type, $block], $s); } @@ -1067,6 +1074,7 @@ protected function parseChunk() // collect comments just after the block closing if needed if ($this->eatWhiteDefault) { $this->whitespace(); + assert($this->env !== null); if ($this->env->comments) { $this->append(null); @@ -1088,20 +1096,34 @@ protected function parseChunk() * Push block onto parse tree * * @param array|null $selectors - * @param integer $pos + * @param int $pos * * @return Block */ protected function pushBlock($selectors, $pos = 0) + { + $b = new Block(); + $b->selectors = $selectors; + + $this->registerPushedBlock($b, $pos); + + return $b; + } + + /** + * @param Block $b + * @param int $pos + * + * @return void + */ + private function registerPushedBlock(Block $b, $pos) { list($line, $column) = $this->getSourcePosition($pos); - $b = new Block(); $b->sourceName = $this->sourceName; $b->sourceLine = $line; $b->sourceColumn = $column; $b->sourceIndex = $this->sourceIndex; - $b->selectors = $selectors; $b->comments = []; $b->parent = $this->env; @@ -1121,20 +1143,21 @@ protected function pushBlock($selectors, $pos = 0) // collect comments at the beginning of a block if needed if ($this->eatWhiteDefault) { $this->whitespace(); + assert($this->env !== null); if ($this->env->comments) { $this->append(null); } } - - return $b; } /** * Push special (named) block onto parse tree * + * @deprecated + * * @param string $type - * @param integer $pos + * @param int $pos * * @return Block */ @@ -1155,6 +1178,7 @@ protected function pushSpecialBlock($type, $pos) */ protected function popBlock() { + assert($this->env !== null); // collect comments ending just before of a block closing if ($this->env->comments) { @@ -1183,11 +1207,11 @@ protected function popBlock() /** * Peek input stream * - * @param string $regex - * @param array $out - * @param integer $from + * @param string $regex + * @param array $out + * @param int $from * - * @return integer + * @return int */ protected function peek($regex, &$out, $from = null) { @@ -1204,7 +1228,9 @@ protected function peek($regex, &$out, $from = null) /** * Seek to position in input stream (or return current position in input stream) * - * @param integer $where + * @param int $where + * + * @return void */ protected function seek($where) { @@ -1215,7 +1241,10 @@ protected function seek($where) * Assert a parsed part is plain CSS Valid * * @param array|false $parsed - * @param int $startPos + * @param int $startPos + * + * @return array + * * @throws ParserException */ protected function assertPlainCssValid($parsed, $startPos = null) @@ -1243,8 +1272,11 @@ protected function assertPlainCssValid($parsed, $startPos = null) /** * Check a parsed element is plain CSS Valid + * * @param array $parsed - * @return bool|array + * @param bool $allowExpression + * + * @return array|false */ protected function isPlainCssValidElement($parsed, $allowExpression = false) { @@ -1423,7 +1455,9 @@ protected function isPlainCssValidElement($parsed, $allowExpression = false) * @param array $m Matches (passed by reference) * @param string $delim Delimiter * - * @return boolean True if match; false otherwise + * @return bool True if match; false otherwise + * + * @phpstan-impure */ protected function matchString(&$m, $delim) { @@ -1459,11 +1493,13 @@ protected function matchString(&$m, $delim) /** * Try to match something on head of buffer * - * @param string $regex - * @param array $out - * @param boolean $eatWhitespace + * @param string $regex + * @param array $out + * @param bool $eatWhitespace + * + * @return bool * - * @return boolean + * @phpstan-impure */ protected function match($regex, &$out, $eatWhitespace = null) { @@ -1489,10 +1525,12 @@ protected function match($regex, &$out, $eatWhitespace = null) /** * Match a single string * - * @param string $char - * @param boolean $eatWhitespace + * @param string $char + * @param bool $eatWhitespace + * + * @return bool * - * @return boolean + * @phpstan-impure */ protected function matchChar($char, $eatWhitespace = null) { @@ -1516,11 +1554,13 @@ protected function matchChar($char, $eatWhitespace = null) /** * Match literal string * - * @param string $what - * @param integer $len - * @param boolean $eatWhitespace + * @param string $what + * @param int $len + * @param bool $eatWhitespace * - * @return boolean + * @return bool + * + * @phpstan-impure */ protected function literal($what, $len, $eatWhitespace = null) { @@ -1544,7 +1584,9 @@ protected function literal($what, $len, $eatWhitespace = null) /** * Match some whitespace * - * @return boolean + * @return bool + * + * @phpstan-impure */ protected function whitespace() { @@ -1596,19 +1638,19 @@ protected function whitespace() if (! $comment) { // single part static comment - $this->appendComment([Type::T_COMMENT, $c]); + $commentStatement = [Type::T_COMMENT, $c]; } else { $comment[] = $c; $staticComment = substr($this->buffer, $startCommentCount, $endCommentCount - $startCommentCount); $commentStatement = [Type::T_COMMENT, $staticComment, [Type::T_STRING, '', $comment]]; + } - list($line, $column) = $this->getSourcePosition($startCommentCount); - $commentStatement[self::SOURCE_LINE] = $line; - $commentStatement[self::SOURCE_COLUMN] = $column; - $commentStatement[self::SOURCE_INDEX] = $this->sourceIndex; + list($line, $column) = $this->getSourcePosition($startCommentCount); + $commentStatement[self::SOURCE_LINE] = $line; + $commentStatement[self::SOURCE_COLUMN] = $column; + $commentStatement[self::SOURCE_INDEX] = $this->sourceIndex; - $this->appendComment($commentStatement); - } + $this->appendComment($commentStatement); $this->commentsSeen[$startCommentCount] = true; $this->count = $endCommentCount; @@ -1631,9 +1673,13 @@ protected function whitespace() * Append comment to current block * * @param array $comment + * + * @return void */ protected function appendComment($comment) { + assert($this->env !== null); + if (! $this->discardComments) { $this->env->comments[] = $comment; } @@ -1643,10 +1689,14 @@ protected function appendComment($comment) * Append statement to current block * * @param array|null $statement - * @param integer $pos + * @param int $pos + * + * @return void */ protected function append($statement, $pos = null) { + assert($this->env !== null); + if (! \is_null($statement)) { ! $this->cssOnly || ($statement = $this->assertPlainCssValid($statement, $pos)); @@ -1676,11 +1726,15 @@ protected function append($statement, $pos = null) */ protected function last() { + assert($this->env !== null); + $i = \count($this->env->children) - 1; if (isset($this->env->children[$i])) { return $this->env->children[$i]; } + + return null; } /** @@ -1688,7 +1742,7 @@ protected function last() * * @param array $out * - * @return boolean + * @return bool */ protected function mediaQueryList(&$out) { @@ -1700,7 +1754,7 @@ protected function mediaQueryList(&$out) * * @param array $out * - * @return boolean + * @return bool */ protected function mediaQuery(&$out) { @@ -1754,7 +1808,7 @@ protected function mediaQuery(&$out) * * @param array $out * - * @return boolean + * @return bool */ protected function supportsQuery(&$out) { @@ -1887,7 +1941,7 @@ protected function supportsQuery(&$out) * * @param array $out * - * @return boolean + * @return bool */ protected function mediaExpression(&$out) { @@ -1920,7 +1974,7 @@ protected function mediaExpression(&$out) * * @param array $out * - * @return boolean + * @return bool */ protected function argValues(&$out) { @@ -1945,7 +1999,7 @@ protected function argValues(&$out) * * @param array $out * - * @return boolean + * @return bool */ protected function argValue(&$out) { @@ -2025,10 +2079,12 @@ protected function isKnownGenericDirective($directiveName) /** * Parse directive value list that considers $vars as keyword * - * @param array $out - * @param boolean|string $endChar + * @param array $out + * @param string|false $endChar * - * @return boolean + * @return bool + * + * @phpstan-impure */ protected function directiveValue(&$out, $endChar = false) { @@ -2089,7 +2145,7 @@ protected function directiveValue(&$out, $endChar = false) * * @param array $out * - * @return boolean + * @return bool */ protected function valueList(&$out) { @@ -2105,10 +2161,11 @@ protected function valueList(&$out) * Parse a function call, where externals () are part of the call * and not of the value list * - * @param $out - * @param bool $mandatoryEnclos + * @param array $out + * @param bool $mandatoryEnclos * @param null|string $charAfter - * @param null|bool $eatWhiteSp + * @param null|bool $eatWhiteSp + * * @return bool */ protected function functionCallArgumentsList(&$out, $mandatoryEnclos = true, $charAfter = null, $eatWhiteSp = null) @@ -2145,7 +2202,7 @@ protected function functionCallArgumentsList(&$out, $mandatoryEnclos = true, $ch * * @param array $out * - * @return boolean + * @return bool */ protected function spaceList(&$out) { @@ -2155,17 +2212,18 @@ protected function spaceList(&$out) /** * Parse generic list * - * @param array $out - * @param string $parseItem The name of the method used to parse items - * @param string $delim - * @param boolean $flatten + * @param array $out + * @param string $parseItem The name of the method used to parse items + * @param string $delim + * @param bool $flatten * - * @return boolean + * @return bool */ protected function genericList(&$out, $parseItem, $delim = '', $flatten = true) { $s = $this->count; $items = []; + /** @var array|Number|null $value */ $value = null; while ($this->$parseItem($value)) { @@ -2179,9 +2237,12 @@ protected function genericList(&$out, $parseItem, $delim = '', $flatten = true) $trailing_delim = true; } else { + assert(\is_array($value) || $value instanceof Number); // if no delim watch that a keyword didn't eat the single/double quote // from the following starting string if ($value[0] === Type::T_KEYWORD) { + assert(\is_array($value)); + /** @var string $word */ $word = $value[1]; $last_char = substr($word, -1); @@ -2206,8 +2267,10 @@ protected function genericList(&$out, $parseItem, $delim = '', $flatten = true) $this->count--; } + /** @var array|Number|null $nextValue */ $nextValue = null; if ($this->$parseItem($nextValue)) { + assert(\is_array($nextValue) || $nextValue instanceof Number); if ($nextValue[0] === Type::T_KEYWORD && $nextValue[1] === $last_char) { // bad try, forget it $this->seek($currentCount); @@ -2256,11 +2319,13 @@ protected function genericList(&$out, $parseItem, $delim = '', $flatten = true) /** * Parse expression * - * @param array $out - * @param boolean $listOnly - * @param boolean $lookForExp + * @param array $out + * @param bool $listOnly + * @param bool $lookForExp * - * @return boolean + * @return bool + * + * @phpstan-impure */ protected function expression(&$out, $listOnly = false, $lookForExp = true) { @@ -2321,12 +2386,14 @@ protected function expression(&$out, $listOnly = false, $lookForExp = true) /** * Parse expression specifically checking for lists in parenthesis or brackets * - * @param array $out - * @param integer $s - * @param string $closingParen - * @param array $allowedTypes + * @param array $out + * @param int $s + * @param string $closingParen + * @param string[] $allowedTypes + * + * @return bool * - * @return boolean + * @phpstan-param array $allowedTypes */ protected function enclosedExpression(&$out, $s, $closingParen = ')', $allowedTypes = [Type::T_LIST, Type::T_MAP]) { @@ -2381,8 +2448,8 @@ protected function enclosedExpression(&$out, $s, $closingParen = ')', $allowedTy /** * Parse left-hand side of subexpression * - * @param array $lhs - * @param integer $minP + * @param array $lhs + * @param int $minP * * @return array */ @@ -2394,7 +2461,7 @@ protected function expHelper($lhs, $minP) $whiteBefore = isset($this->buffer[$this->count - 1]) && ctype_space($this->buffer[$this->count - 1]); - while ($this->match($operators, $m, false) && static::$precedence[$m[1]] >= $minP) { + while ($this->match($operators, $m, false) && static::$precedence[strtolower($m[1])] >= $minP) { $whiteAfter = isset($this->buffer[$this->count]) && ctype_space($this->buffer[$this->count]); $varAfter = isset($this->buffer[$this->count]) && @@ -2418,7 +2485,7 @@ protected function expHelper($lhs, $minP) } // consume higher-precedence operators on the right-hand side - $rhs = $this->expHelper($rhs, static::$precedence[$op] + 1); + $rhs = $this->expHelper($rhs, static::$precedence[strtolower($op)] + 1); $lhs = [Type::T_EXPRESSION, $op, $lhs, $rhs, $this->inParens, $whiteBefore, $whiteAfter]; @@ -2437,7 +2504,7 @@ protected function expHelper($lhs, $minP) * * @param array $out * - * @return boolean + * @return bool */ protected function value(&$out) { @@ -2645,7 +2712,7 @@ protected function value(&$out) * * @param array $out * - * @return boolean + * @return bool */ protected function parenValue(&$out) { @@ -2684,7 +2751,7 @@ protected function parenValue(&$out) * * @param array $out * - * @return boolean + * @return bool */ protected function progid(&$out) { @@ -2717,7 +2784,7 @@ protected function progid(&$out) * @param string $name * @param array $func * - * @return boolean + * @return bool */ protected function func($name, &$func) { @@ -2737,6 +2804,10 @@ protected function func($name, &$func) $this->argValues($args) && $this->matchChar(')') ) { + if (strtolower($name) === 'var' && \count($args) === 2 && $args[1][0] === Type::T_NULL) { + $args[1] = [null, [Type::T_STRING, '', [' ']], false]; + } + $func = [Type::T_FUNCTION_CALL, $name, $args]; return true; @@ -2771,7 +2842,7 @@ protected function func($name, &$func) * * @param array $out * - * @return boolean + * @return bool */ protected function argumentList(&$out) { @@ -2816,7 +2887,7 @@ protected function argumentList(&$out) * * @param array $out * - * @return boolean + * @return bool */ protected function argumentDef(&$out) { @@ -2878,7 +2949,7 @@ protected function argumentDef(&$out) * * @param array $out * - * @return boolean + * @return bool */ protected function map(&$out) { @@ -2920,7 +2991,7 @@ protected function map(&$out) * * @param array $out * - * @return boolean + * @return bool */ protected function color(&$out) { @@ -2946,7 +3017,7 @@ protected function color(&$out) * * @param array $unit * - * @return boolean + * @return bool */ protected function unit(&$unit) { @@ -2971,8 +3042,9 @@ protected function unit(&$unit) * Parse string * * @param array $out + * @param bool $keepDelimWithInterpolation * - * @return boolean + * @return bool */ protected function string(&$out, $keepDelimWithInterpolation = false) { @@ -3053,7 +3125,8 @@ protected function string(&$out, $keepDelimWithInterpolation = false) /** * @param string $out - * @param bool $inKeywords + * @param bool $inKeywords + * * @return bool */ protected function matchEscapeCharacter(&$out, $inKeywords = false) @@ -3103,10 +3176,10 @@ protected function matchEscapeCharacter(&$out, $inKeywords = false) /** * Parse keyword or interpolation * - * @param array $out - * @param boolean $restricted + * @param array $out + * @param bool $restricted * - * @return boolean + * @return bool */ protected function mixedKeyword(&$out, $restricted = false) { @@ -3147,14 +3220,14 @@ protected function mixedKeyword(&$out, $restricted = false) /** * Parse an unbounded string stopped by $end * - * @param string $end - * @param array $out - * @param string $nestOpen - * @param string $nestClose - * @param boolean $rtrim + * @param string $end + * @param array $out + * @param string $nestOpen + * @param string $nestClose + * @param bool $rtrim * @param string $disallow * - * @return boolean + * @return bool */ protected function openString($end, &$out, $nestOpen = null, $nestClose = null, $rtrim = true, $disallow = null) { @@ -3230,9 +3303,9 @@ protected function openString($end, &$out, $nestOpen = null, $nestClose = null, * Parser interpolation * * @param string|array $out - * @param boolean $lookWhite save information about whitespace before and after + * @param bool $lookWhite save information about whitespace before and after * - * @return boolean + * @return bool */ protected function interpolation(&$out, $lookWhite = true) { @@ -3287,7 +3360,7 @@ protected function interpolation(&$out, $lookWhite = true) * * @param array $out * - * @return boolean + * @return bool */ protected function propertyName(&$out) { @@ -3342,7 +3415,7 @@ protected function propertyName(&$out) * * @param array $out * - * @return boolean + * @return bool */ protected function customProperty(&$out) { @@ -3400,9 +3473,9 @@ protected function customProperty(&$out) * Parse comma separated selector list * * @param array $out - * @param string|boolean $subSelector + * @param string|bool $subSelector * - * @return boolean + * @return bool */ protected function selectors(&$out, $subSelector = false) { @@ -3436,9 +3509,9 @@ protected function selectors(&$out, $subSelector = false) * Parse whitespace separated selector list * * @param array $out - * @param string|boolean $subSelector + * @param string|bool $subSelector * - * @return boolean + * @return bool */ protected function selector(&$out, $subSelector = false) { @@ -3494,7 +3567,8 @@ protected function selector(&$out, $subSelector = false) * - but this require a better formal selector representation instead of the array we have now * * @param string $out - * @param bool $keepEscapedNumber + * @param bool $keepEscapedNumber + * * @return bool */ protected function matchEscapeCharacterInSelector(&$out, $keepEscapedNumber = false) @@ -3539,9 +3613,9 @@ protected function matchEscapeCharacterInSelector(&$out, $keepEscapedNumber = fa * }} * * @param array $out - * @param string|boolean $subSelector + * @param string|bool $subSelector * - * @return boolean + * @return bool */ protected function selectorSingle(&$out, $subSelector = false) { @@ -3765,7 +3839,7 @@ protected function selectorSingle(&$out, $subSelector = false) * * @param array $out * - * @return boolean + * @return bool */ protected function variable(&$out) { @@ -3792,11 +3866,11 @@ protected function variable(&$out) /** * Parse a keyword * - * @param string $word - * @param boolean $eatWhitespace - * @param boolean $inSelector + * @param string $word + * @param bool $eatWhitespace + * @param bool $inSelector * - * @return boolean + * @return bool */ protected function keyword(&$word, $eatWhitespace = null, $inSelector = false) { @@ -3859,11 +3933,11 @@ protected function keyword(&$word, $eatWhitespace = null, $inSelector = false) /** * Parse a keyword that should not start with a number * - * @param string $word - * @param boolean $eatWhitespace - * @param boolean $inSelector + * @param string $word + * @param bool $eatWhitespace + * @param bool $inSelector * - * @return boolean + * @return bool */ protected function restrictedKeyword(&$word, $eatWhitespace = null, $inSelector = false) { @@ -3883,7 +3957,7 @@ protected function restrictedKeyword(&$word, $eatWhitespace = null, $inSelector * * @param string|array $placeholder * - * @return boolean + * @return bool */ protected function placeholder(&$placeholder) { @@ -3912,7 +3986,7 @@ protected function placeholder(&$placeholder) * * @param array $out * - * @return boolean + * @return bool */ protected function url(&$out) { @@ -3947,7 +4021,7 @@ protected function url(&$out) * Consume an end of statement delimiter * @param bool $eatWhitespace * - * @return boolean + * @return bool */ protected function end($eatWhitespace = null) { @@ -3968,7 +4042,7 @@ protected function end($eatWhitespace = null) * * @param array $value * - * @return array + * @return string[] */ protected function stripAssignmentFlags(&$value) { @@ -3995,7 +4069,7 @@ protected function stripAssignmentFlags(&$value) * * @param array $selectors * - * @return string + * @return bool */ protected function stripOptionalFlag(&$selectors) { @@ -4044,6 +4118,8 @@ private function pregQuote($what) * Extract line numbers from buffer * * @param string $buffer + * + * @return void */ private function extractLineNumbers($buffer) { @@ -4065,9 +4141,10 @@ private function extractLineNumbers($buffer) /** * Get source line number and column (given character position in the buffer) * - * @param integer $pos + * @param int $pos * * @return array + * @phpstan-return array{int, int} */ private function getSourcePosition($pos) { diff --git a/Contrib/scssphp/src/SourceMap/Base64.php b/Contrib/scssphp/src/SourceMap/Base64.php index 4a5ed8bb0..00b6b4545 100644 --- a/Contrib/scssphp/src/SourceMap/Base64.php +++ b/Contrib/scssphp/src/SourceMap/Base64.php @@ -164,7 +164,7 @@ class Base64 /** * Convert to base64 * - * @param integer $value + * @param int $value * * @return string */ @@ -178,7 +178,7 @@ public static function encode($value) * * @param string $value * - * @return integer + * @return int */ public static function decode($value) { diff --git a/Contrib/scssphp/src/SourceMap/Base64VLQ.php b/Contrib/scssphp/src/SourceMap/Base64VLQ.php index d47b96a1c..2a5210c68 100644 --- a/Contrib/scssphp/src/SourceMap/Base64VLQ.php +++ b/Contrib/scssphp/src/SourceMap/Base64VLQ.php @@ -51,7 +51,7 @@ class Base64VLQ /** * Returns the VLQ encoded value. * - * @param integer $value + * @param int $value * * @return string */ @@ -80,9 +80,9 @@ public static function encode($value) * Decodes VLQValue. * * @param string $str - * @param integer $index + * @param int $index * - * @return integer + * @return int */ public static function decode($str, &$index) { @@ -107,9 +107,9 @@ public static function decode($str, &$index) * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary) * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary) * - * @param integer $value + * @param int $value * - * @return integer + * @return int */ private static function toVLQSigned($value) { @@ -126,9 +126,9 @@ private static function toVLQSigned($value) * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1 * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2 * - * @param integer $value + * @param int $value * - * @return integer + * @return int */ private static function fromVLQSigned($value) { diff --git a/Contrib/scssphp/src/SourceMap/SourceMapGenerator.php b/Contrib/scssphp/src/SourceMap/SourceMapGenerator.php index 4f14bdcef..ccd4f0261 100644 --- a/Contrib/scssphp/src/SourceMap/SourceMapGenerator.php +++ b/Contrib/scssphp/src/SourceMap/SourceMapGenerator.php @@ -107,18 +107,18 @@ class SourceMapGenerator */ public function __construct(array $options = []) { - $this->options = array_merge($this->defaultOptions, $options); + $this->options = array_replace($this->defaultOptions, $options); $this->encoder = new Base64VLQ(); } /** * Adds a mapping * - * @param integer $generatedLine The line number in generated file - * @param integer $generatedColumn The column number in generated file - * @param integer $originalLine The line number in original file - * @param integer $originalColumn The column number in original file - * @param string $sourceFile The original source file + * @param int $generatedLine The line number in generated file + * @param int $generatedColumn The column number in generated file + * @param int $originalLine The line number in original file + * @param int $originalColumn The column number in original file + * @param string $sourceFile The original source file * * @return void */ @@ -140,7 +140,7 @@ public function addMapping($generatedLine, $generatedColumn, $originalLine, $ori * * @param string $content The content to write * - * @return string + * @return string|null * * @throws \ScssPhp\ScssPhp\Exception\CompilerException If the file could not be saved * @deprecated @@ -148,6 +148,7 @@ public function addMapping($generatedLine, $generatedColumn, $originalLine, $ori public function saveMap($content) { $file = $this->options['sourceMapWriteTo']; + assert($file !== null); $dir = \dirname($file); // directory does not exist @@ -201,7 +202,7 @@ public function generateJson($prefix = '') // A list of original sources used by the 'mappings' entry. $sourceMap['sources'] = []; - foreach ($this->sources as $sourceUri => $sourceFilename) { + foreach ($this->sources as $sourceFilename) { $sourceMap['sources'][] = $this->normalizeFilename($sourceFilename); } @@ -223,7 +224,15 @@ public function generateJson($prefix = '') unset($sourceMap['sourceRoot']); } - return json_encode($sourceMap, JSON_UNESCAPED_SLASHES); + $jsonSourceMap = json_encode($sourceMap, JSON_UNESCAPED_SLASHES); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new \RuntimeException(json_last_error_msg()); + } + + assert($jsonSourceMap !== false); + + return $jsonSourceMap; } /** @@ -326,7 +335,7 @@ public function generateMappings($prefix = '') * * @param string $filename * - * @return integer|false + * @return int|false */ protected function findFileIndex($filename) { @@ -362,8 +371,8 @@ protected function normalizeFilename($filename) /** * Fix windows paths * - * @param string $path - * @param boolean $addEndSlash + * @param string $path + * @param bool $addEndSlash * * @return string */ diff --git a/Contrib/scssphp/src/Type.php b/Contrib/scssphp/src/Type.php index fb2a1d7fe..d43088785 100644 --- a/Contrib/scssphp/src/Type.php +++ b/Contrib/scssphp/src/Type.php @@ -19,58 +19,190 @@ */ class Type { + /** + * @internal + */ const T_ASSIGN = 'assign'; + /** + * @internal + */ const T_AT_ROOT = 'at-root'; + /** + * @internal + */ const T_BLOCK = 'block'; - /** @deprecated */ + /** + * @deprecated + * @internal + */ const T_BREAK = 'break'; + /** + * @internal + */ const T_CHARSET = 'charset'; const T_COLOR = 'color'; + /** + * @internal + */ const T_COMMENT = 'comment'; - /** @deprecated */ + /** + * @deprecated + * @internal + */ const T_CONTINUE = 'continue'; - /** @deprecated */ + /** + * @deprecated + * @internal + */ const T_CONTROL = 'control'; + /** + * @internal + */ const T_CUSTOM_PROPERTY = 'custom'; + /** + * @internal + */ const T_DEBUG = 'debug'; + /** + * @internal + */ const T_DIRECTIVE = 'directive'; + /** + * @internal + */ const T_EACH = 'each'; + /** + * @internal + */ const T_ELSE = 'else'; + /** + * @internal + */ const T_ELSEIF = 'elseif'; + /** + * @internal + */ const T_ERROR = 'error'; + /** + * @internal + */ const T_EXPRESSION = 'exp'; + /** + * @internal + */ const T_EXTEND = 'extend'; + /** + * @internal + */ const T_FOR = 'for'; const T_FUNCTION = 'function'; + /** + * @internal + */ const T_FUNCTION_REFERENCE = 'function-reference'; + /** + * @internal + */ const T_FUNCTION_CALL = 'fncall'; + /** + * @internal + */ const T_HSL = 'hsl'; + /** + * @internal + */ const T_HWB = 'hwb'; + /** + * @internal + */ const T_IF = 'if'; + /** + * @internal + */ const T_IMPORT = 'import'; + /** + * @internal + */ const T_INCLUDE = 'include'; + /** + * @internal + */ const T_INTERPOLATE = 'interpolate'; + /** + * @internal + */ const T_INTERPOLATED = 'interpolated'; + /** + * @internal + */ const T_KEYWORD = 'keyword'; const T_LIST = 'list'; const T_MAP = 'map'; + /** + * @internal + */ const T_MEDIA = 'media'; + /** + * @internal + */ const T_MEDIA_EXPRESSION = 'mediaExp'; + /** + * @internal + */ const T_MEDIA_TYPE = 'mediaType'; + /** + * @internal + */ const T_MEDIA_VALUE = 'mediaValue'; + /** + * @internal + */ const T_MIXIN = 'mixin'; + /** + * @internal + */ const T_MIXIN_CONTENT = 'mixin_content'; + /** + * @internal + */ const T_NESTED_PROPERTY = 'nestedprop'; + /** + * @internal + */ const T_NOT = 'not'; const T_NULL = 'null'; const T_NUMBER = 'number'; + /** + * @internal + */ const T_RETURN = 'return'; + /** + * @internal + */ const T_ROOT = 'root'; + /** + * @internal + */ const T_SCSSPHP_IMPORT_ONCE = 'scssphp-import-once'; + /** + * @internal + */ const T_SELF = 'self'; const T_STRING = 'string'; + /** + * @internal + */ const T_UNARY = 'unary'; + /** + * @internal + */ const T_VARIABLE = 'var'; + /** + * @internal + */ const T_WARN = 'warn'; + /** + * @internal + */ const T_WHILE = 'while'; } diff --git a/Contrib/scssphp/src/Util.php b/Contrib/scssphp/src/Util.php index 62cd2a20c..ad608ceb3 100644 --- a/Contrib/scssphp/src/Util.php +++ b/Contrib/scssphp/src/Util.php @@ -79,7 +79,7 @@ public static function encodeURIComponent($string) /** * mb_chr() wrapper * - * @param integer $code + * @param int $code * * @return string */ diff --git a/Contrib/scssphp/src/Version.php b/Contrib/scssphp/src/Version.php index 62c8006a1..d604a5057 100644 --- a/Contrib/scssphp/src/Version.php +++ b/Contrib/scssphp/src/Version.php @@ -19,5 +19,5 @@ */ class Version { - const VERSION = '1.8.1'; + const VERSION = '1.11.0'; }