MDL-75476 scssphp: Updated from 1.10.0 to 1.11.0

This commit is contained in:
David Woloszyn 2022-09-19 10:50:02 +10:00
parent c6211a68b4
commit 5247ff3da0
13 changed files with 301 additions and 129 deletions

View File

@ -166,7 +166,7 @@ class Compiler
/**
* @var array
* @phpstan-var array<string, array{0: callable, 1: array|null}>
* @phpstan-var array<string, array{0: callable, 1: string[]|null}>
*/
protected $userFunctions = [];
/**
@ -211,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<Formatter>
*/
private $configuredFormatter = Expanded::class;
/**
* @var Environment
@ -381,7 +387,7 @@ class Compiler
'encoding' => $this->encoding,
'sourceMap' => serialize($this->sourceMap),
'sourceMapOptions' => $this->sourceMapOptions,
'formatter' => $this->formatter,
'formatter' => $this->configuredFormatter,
'legacyImportPath' => $this->legacyCwdImportPath,
];
@ -502,7 +508,7 @@ class Compiler
$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);
@ -529,6 +535,7 @@ class Compiler
$sourceMapGenerator = new SourceMapGenerator($this->sourceMapOptions);
}
}
assert($this->scope !== null);
$out = $this->formatter->format($this->scope, $sourceMapGenerator);
@ -542,6 +549,7 @@ class Compiler
$sourceMap = null;
if (! empty($out) && $this->sourceMap && $this->sourceMap !== self::SOURCE_MAP_NONE) {
assert($sourceMapGenerator !== null);
$sourceMap = $sourceMapGenerator->generateJson($prefix);
$sourceMapUrl = null;
@ -671,7 +679,7 @@ class Compiler
/**
* Push extends
*
* @param array $target
* @param string[] $target
* @param array $origin
* @param array|null $block
*
@ -714,9 +722,9 @@ class Compiler
$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;
@ -734,6 +742,7 @@ class Compiler
$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();
}
@ -815,6 +824,7 @@ class Compiler
}
if ($placeholderSelector && 0 === \count($block->selectors) && null !== $parentKey) {
assert($block->parent !== null);
unset($block->parent->children[$parentKey]);
return;
@ -1287,6 +1297,7 @@ class Compiler
$mediaQueries = $this->compileMediaQuery($this->multiplyMedia($this->env));
if (! empty($mediaQueries)) {
assert($this->scope !== null);
$previousScope = $this->scope;
$parentScope = $this->mediaParent($this->scope);
@ -1452,7 +1463,7 @@ class Compiler
if (
! $selfParent->selectors &&
isset($block->parent) && $block->parent &&
isset($block->parent) &&
isset($block->parent->selectors) && $block->parent->selectors
) {
$selfParent = $block->parent;
@ -1460,13 +1471,15 @@ class Compiler
$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);
@ -1490,6 +1503,7 @@ class Compiler
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) {
@ -1557,7 +1571,7 @@ class Compiler
*/
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);
}
@ -1768,10 +1782,12 @@ class Compiler
$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);
@ -1829,6 +1845,7 @@ class Compiler
$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
@ -1862,6 +1879,7 @@ class Compiler
$this->compileChildrenNoReturn($block->children, $this->scope);
assert($this->scope !== null);
$this->scope = $this->scope->parent;
$this->popEnv();
@ -1890,10 +1908,12 @@ class Compiler
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)) {
@ -1911,6 +1931,7 @@ class Compiler
// and revert for the following children of the same block
if ($selfParentSelectors) {
assert($block->selfParent !== null);
$block->selfParent->selectors = $selfParentSelectors;
}
}
@ -1966,6 +1987,7 @@ class Compiler
$out = $this->makeOutputBlock(Type::T_COMMENT);
$out->lines[] = $this->compileCommentValue($block, true);
assert($this->scope !== null);
$this->scope->children[] = $out;
}
@ -1980,7 +2002,11 @@ class Compiler
{
$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) {
@ -2008,6 +2034,8 @@ class Compiler
* @param array $selector
*
* @return array
*
* @phpstan-impure
*/
protected function evalSelector($selector)
{
@ -2020,6 +2048,8 @@ class Compiler
* @param array $part
*
* @return array
*
* @phpstan-impure
*/
protected function evalSelectorPart($part)
{
@ -2033,8 +2063,8 @@ class Compiler
}
} 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);
}
@ -2320,9 +2350,9 @@ class Compiler
}
/**
* 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
@ -2337,13 +2367,13 @@ class Compiler
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);
}
@ -2401,7 +2431,7 @@ class Compiler
$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 = [];
@ -2857,7 +2887,7 @@ class Compiler
$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;
@ -3093,6 +3123,7 @@ class Compiler
if (! $selectors && isset($child['selfParent'])) {
$selectors = $this->multiplySelectors($this->env, $child['selfParent']);
}
assert($selectors !== null);
if (\count($result) > 1) {
$replacement = implode(', ', $result);
@ -3255,7 +3286,7 @@ EOL;
// 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);
@ -3265,7 +3296,7 @@ EOL;
$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;
}
}
@ -3371,6 +3402,8 @@ EOL;
default:
throw $this->error("unknown child type: $child[0]");
}
return null;
}
/**
@ -3505,32 +3538,24 @@ EOL;
$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 = "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 (
\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 (isset($out)) {
return $out;
}
if (isset($out)) {
return $out;
}
return $this->expToString($value);
@ -3867,11 +3892,12 @@ EOL;
// 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;
@ -5517,11 +5543,11 @@ EOL;
{
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:
@ -5539,6 +5565,8 @@ EOL;
* @return void
*
* @deprecated Use {@see setOutputStyle} instead.
*
* @phpstan-param class-string<Formatter> $formatterName
*/
public function setFormatter($formatterName)
{
@ -5547,7 +5575,7 @@ EOL;
}
@trigger_error('The method "setFormatter" is deprecated. Use "setOutputStyle" instead.', E_USER_DEPRECATED);
$this->formatter = $formatterName;
$this->configuredFormatter = $formatterName;
}
/**
@ -5688,6 +5716,10 @@ EOL;
// 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;
@ -6031,6 +6063,8 @@ EOL;
*
* @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.
@ -6050,7 +6084,8 @@ EOL;
*
* @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
*/
@ -6168,6 +6203,8 @@ EOL;
*
* @param string $name
*
* @return void
*
* @throws \Exception
*/
protected function handleImportLoop($name)
@ -6362,6 +6399,9 @@ EOL;
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]);
}
@ -6794,7 +6834,7 @@ EOL;
$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);
@ -7262,6 +7302,8 @@ EOL;
protected function coerceString($value)
{
if ($value[0] === Type::T_STRING) {
assert(\is_array($value));
return $value;
}
@ -7365,6 +7407,7 @@ EOL;
if ($value[0] !== Type::T_LIST) {
throw $this->error('expecting list, %s received', $value[0]);
}
assert(\is_array($value));
return $value;
}
@ -7534,7 +7577,7 @@ EOL;
$h = 60 * ($green - $blue) / $d;
} elseif ($green == $max) {
$h = 60 * ($blue - $red) / $d + 120;
} elseif ($blue == $max) {
} else {
$h = 60 * ($red - $green) / $d + 240;
}
}
@ -7613,9 +7656,9 @@ EOL;
*
* @api
*
* @param int $hue H from 0 to 360
* @param int $whiteness W from 0 to 100
* @param int $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
*/
@ -7666,7 +7709,7 @@ EOL;
$h = 60 * ($green - $blue) / $d;
} elseif ($green == $max) {
$h = 60 * ($blue - $red) / $d + 120;
} elseif ($blue == $max) {
} else {
$h = 60 * ($red - $green) / $d + 240;
}
}
@ -7790,6 +7833,14 @@ EOL;
['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)) {
@ -7872,14 +7923,7 @@ EOL;
$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;
@ -7903,7 +7947,11 @@ EOL;
$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);
@ -7938,7 +7986,6 @@ EOL;
$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'));
@ -8159,6 +8206,14 @@ EOL;
['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;
@ -8183,7 +8238,7 @@ EOL;
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;
}
@ -8531,7 +8586,7 @@ EOL;
$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;
@ -8550,7 +8605,7 @@ EOL;
$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;
@ -8718,7 +8773,7 @@ will be an error in future versions of Sass.\n on line $line of $fname";
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--;
@ -8733,7 +8788,7 @@ will be an error in future versions of Sass.\n on line $line of $fname";
protected function libSetNth($args)
{
$list = $this->coerceList($args[0]);
$n = $this->assertNumber($args[1])->getDimension();
$n = $this->assertInteger($args[1]);
if ($n > 0) {
$n--;
@ -9350,7 +9405,7 @@ will be an error in future versions of Sass.\n on line $line of $fname";
$index = $index - 1;
}
if ($index < 0) {
$index = Util::mbStrlen($stringContent) + 1 + $index;
$index = max(Util::mbStrlen($stringContent) + 1 + $index, 0);
}
$string[2] = [
@ -9538,7 +9593,25 @@ will be an error in future versions of Sass.\n on line $line of $fname";
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 = <<<TXT
random() will no longer ignore \$limit units ($limit) in a future release.
Recommendation: random(\$limit / 1$unitString) * 1$unitString
To preserve current behavior: random(\$limit / 1$unitString)
More info: https://sass-lang.com/d/random-with-units
TXT;
Warn::deprecation($this->addLocationToMessage($message));
}
$n = $this->assertInteger($limit, 'limit');
if ($n < 1) {
throw new SassScriptException("\$limit: Must be greater than 0, was $n.");
@ -10003,6 +10076,8 @@ will be an error in future versions of Sass.\n on line $line of $fname";
$selectorsMap[] = $this->getSelectorArg($arg, 'selector', true);
}
assert(!empty($selectorsMap));
$envs = [];
foreach ($selectorsMap as $selectors) {

View File

@ -22,7 +22,8 @@ namespace ScssPhp\ScssPhp\Exception;
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 @@ class ParserException extends \Exception implements SassException
* @api
*
* @param array $sourcePosition
*
* @return void
*
* @phpstan-param array{string, int, int} $sourcePosition
*/
public function setSourcePosition($sourcePosition)
{

View File

@ -286,9 +286,18 @@ abstract class Formatter
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 @@ abstract class Formatter
// 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 @@ abstract class Formatter
}
if ($lastLine !== '') {
assert($this->currentBlock->sourceLine !== null);
assert($this->currentBlock->sourceName !== null);
$this->sourceMapGenerator->addMapping(
$this->currentLine,
$this->currentColumn,

View File

@ -57,7 +57,9 @@ class Expanded extends Formatter
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;
}
}

View File

@ -68,7 +68,9 @@ class Nested extends Formatter
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;
}
}

View File

@ -22,7 +22,7 @@ namespace ScssPhp\ScssPhp\Formatter;
class OutputBlock
{
/**
* @var string
* @var string|null
*/
public $type;

View File

@ -14,6 +14,8 @@ namespace ScssPhp\ScssPhp\Logger;
/**
* A logger that silently ignores all messages.
*
* @final
*/
class QuietLogger implements LoggerInterface
{

View File

@ -14,6 +14,8 @@ namespace ScssPhp\ScssPhp\Logger;
/**
* A logger that prints to a PHP stream (for instance stderr)
*
* @final
*/
class StreamLogger implements LoggerInterface
{

View File

@ -227,6 +227,16 @@ class Number extends Node implements \ArrayAccess
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 @@ class Number extends Node implements \ArrayAccess
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);
}
}

View File

@ -27,6 +27,7 @@ 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
@ -85,10 +86,6 @@ class Parser
* @var array<int, int>
*/
private $sourcePositions;
/**
* @var array|null
*/
private $charset;
/**
* The current offset in the buffer
*
@ -147,11 +144,9 @@ class Parser
{
$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();
@ -255,7 +250,6 @@ class Parser
if ($this->cache) {
$cacheKey = $this->sourceName . ':' . md5($buffer);
$parseOptions = [
'charset' => $this->charset,
'utf8' => $this->utf8,
];
$v = $this->cache->getCache('parse', $cacheKey, $parseOptions);
@ -296,11 +290,8 @@ class Parser
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);
@ -380,8 +371,8 @@ class Parser
*
* @api
*
* @param string $buffer
* @param string|array $out
* @param string $buffer
* @param array $out
*
* @return bool
*/
@ -827,18 +818,6 @@ class Parser
$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;
}
@ -1074,6 +1053,8 @@ class Parser
$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
}
@ -1093,6 +1074,7 @@ class Parser
// 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);
@ -1161,6 +1143,7 @@ class Parser
// 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);
@ -1195,6 +1178,7 @@ class Parser
*/
protected function popBlock()
{
assert($this->env !== null);
// collect comments ending just before of a block closing
if ($this->env->comments) {
@ -1245,6 +1229,8 @@ class Parser
* Seek to position in input stream (or return current position in input stream)
*
* @param int $where
*
* @return void
*/
protected function seek($where)
{
@ -1257,6 +1243,8 @@ class Parser
* @param array|false $parsed
* @param int $startPos
*
* @return array
*
* @throws ParserException
*/
protected function assertPlainCssValid($parsed, $startPos = null)
@ -1288,7 +1276,7 @@ class Parser
* @param array $parsed
* @param bool $allowExpression
*
* @return bool|array
* @return array|false
*/
protected function isPlainCssValidElement($parsed, $allowExpression = false)
{
@ -1468,6 +1456,8 @@ class Parser
* @param string $delim Delimiter
*
* @return bool True if match; false otherwise
*
* @phpstan-impure
*/
protected function matchString(&$m, $delim)
{
@ -1508,6 +1498,8 @@ class Parser
* @param bool $eatWhitespace
*
* @return bool
*
* @phpstan-impure
*/
protected function match($regex, &$out, $eatWhitespace = null)
{
@ -1537,6 +1529,8 @@ class Parser
* @param bool $eatWhitespace
*
* @return bool
*
* @phpstan-impure
*/
protected function matchChar($char, $eatWhitespace = null)
{
@ -1565,6 +1559,8 @@ class Parser
* @param bool $eatWhitespace
*
* @return bool
*
* @phpstan-impure
*/
protected function literal($what, $len, $eatWhitespace = null)
{
@ -1589,6 +1585,8 @@ class Parser
* Match some whitespace
*
* @return bool
*
* @phpstan-impure
*/
protected function whitespace()
{
@ -1640,20 +1638,20 @@ class Parser
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;
$this->appendComment($commentStatement);
}
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->commentsSeen[$startCommentCount] = true;
$this->count = $endCommentCount;
} else {
@ -1675,9 +1673,13 @@ class Parser
* 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;
}
@ -1688,9 +1690,13 @@ class Parser
*
* @param array|null $statement
* @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));
@ -1720,11 +1726,15 @@ class Parser
*/
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;
}
/**
@ -2069,10 +2079,12 @@ class Parser
/**
* Parse directive value list that considers $vars as keyword
*
* @param array $out
* @param bool|string $endChar
* @param array $out
* @param string|false $endChar
*
* @return bool
*
* @phpstan-impure
*/
protected function directiveValue(&$out, $endChar = false)
{
@ -2211,6 +2223,7 @@ class Parser
{
$s = $this->count;
$items = [];
/** @var array|Number|null $value */
$value = null;
while ($this->$parseItem($value)) {
@ -2224,9 +2237,12 @@ class Parser
$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);
@ -2251,8 +2267,10 @@ class Parser
$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);
@ -2306,6 +2324,8 @@ class Parser
* @param bool $lookForExp
*
* @return bool
*
* @phpstan-impure
*/
protected function expression(&$out, $listOnly = false, $lookForExp = true)
{
@ -2366,12 +2386,14 @@ class Parser
/**
* Parse expression specifically checking for lists in parenthesis or brackets
*
* @param array $out
* @param int $s
* @param string $closingParen
* @param array $allowedTypes
* @param array $out
* @param int $s
* @param string $closingParen
* @param string[] $allowedTypes
*
* @return bool
*
* @phpstan-param array<Type::*> $allowedTypes
*/
protected function enclosedExpression(&$out, $s, $closingParen = ')', $allowedTypes = [Type::T_LIST, Type::T_MAP])
{
@ -2439,7 +2461,7 @@ class Parser
$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]) &&
@ -2463,7 +2485,7 @@ class Parser
}
// 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];
@ -2782,6 +2804,10 @@ class Parser
$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;
@ -4016,7 +4042,7 @@ class Parser
*
* @param array $value
*
* @return array
* @return string[]
*/
protected function stripAssignmentFlags(&$value)
{
@ -4043,7 +4069,7 @@ class Parser
*
* @param array $selectors
*
* @return string
* @return bool
*/
protected function stripOptionalFlag(&$selectors)
{
@ -4092,6 +4118,8 @@ class Parser
* Extract line numbers from buffer
*
* @param string $buffer
*
* @return void
*/
private function extractLineNumbers($buffer)
{
@ -4116,6 +4144,7 @@ class Parser
* @param int $pos
*
* @return array
* @phpstan-return array{int, int}
*/
private function getSourcePosition($pos)
{

View File

@ -107,7 +107,7 @@ 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();
}
@ -140,7 +140,7 @@ class SourceMapGenerator
*
* @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 @@ class SourceMapGenerator
public function saveMap($content)
{
$file = $this->options['sourceMapWriteTo'];
assert($file !== null);
$dir = \dirname($file);
// directory does not exist
@ -201,7 +202,7 @@ class SourceMapGenerator
// 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 @@ class SourceMapGenerator
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;
}
/**

View File

@ -19,5 +19,5 @@ namespace ScssPhp\ScssPhp;
*/
class Version
{
const VERSION = '1.10.0';
const VERSION = '1.11.0';
}

View File

@ -366,7 +366,7 @@ All rights reserved.</copyright>
<location>scssphp</location>
<name>scssphp</name>
<description>scssphp is a compiler for SCSS written in PHP.</description>
<version>1.10.0</version>
<version>1.11.0</version>
<license>MIT</license>
<repository>https://github.com/scssphp/scssphp/</repository>
<copyrights>