N°8834 - Add compatibility with PHP 8.4 (#819)

* N°8834 - Add compatibility with PHP 8.4

* Rollback of scssphp/scssphp version upgrade due to compilation error
This commit is contained in:
Lenaick
2026-02-26 10:36:32 +01:00
committed by GitHub
parent d4821b7edc
commit fc967c06ce
961 changed files with 12298 additions and 7130 deletions

View File

@@ -3,11 +3,21 @@
namespace Sabberworm\CSS\Value;
use Sabberworm\CSS\OutputFormat;
use Sabberworm\CSS\Parsing\ParserState;
use Sabberworm\CSS\Parsing\SourceException;
use Sabberworm\CSS\Parsing\UnexpectedEOFException;
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
/**
* A `CSSFunction` represents a special kind of value that also contains a function name and where the values are the
* functions arguments. It also handles equals-sign-separated argument lists like `filter: alpha(opacity=90);`.
*/
class CSSFunction extends ValueList
{
/**
* @var string
*
* @internal since 8.8.0
*/
protected $sName;
@@ -24,10 +34,32 @@ class CSSFunction extends ValueList
$aArguments = $aArguments->getListComponents();
}
$this->sName = $sName;
$this->iLineNo = $iLineNo;
$this->setPosition($iLineNo); // TODO: redundant?
parent::__construct($aArguments, $sSeparator, $iLineNo);
}
/**
* @param ParserState $oParserState
* @param bool $bIgnoreCase
*
* @return CSSFunction
*
* @throws SourceException
* @throws UnexpectedEOFException
* @throws UnexpectedTokenException
*
* @internal since V8.8.0
*/
public static function parse(ParserState $oParserState, $bIgnoreCase = false)
{
$mResult = $oParserState->parseIdentifier($bIgnoreCase);
$oParserState->consume('(');
$aArguments = Value::parseValue($oParserState, ['=', ' ', ',']);
$mResult = new CSSFunction($mResult, $aArguments, ',', $oParserState->currentLine());
$oParserState->consume(')');
return $mResult;
}
/**
* @return string
*/
@@ -56,6 +88,8 @@ class CSSFunction extends ValueList
/**
* @return string
*
* @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead.
*/
public function __toString()
{
@@ -63,9 +97,11 @@ class CSSFunction extends ValueList
}
/**
* @param OutputFormat|null $oOutputFormat
*
* @return string
*/
public function render(OutputFormat $oOutputFormat)
public function render($oOutputFormat)
{
$aArguments = parent::render($oOutputFormat);
return "{$this->sName}({$aArguments})";

View File

@@ -8,6 +8,11 @@ use Sabberworm\CSS\Parsing\SourceException;
use Sabberworm\CSS\Parsing\UnexpectedEOFException;
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
/**
* This class is a wrapper for quoted strings to distinguish them from keywords.
*
* `CSSString`s always output with double quotes.
*/
class CSSString extends PrimitiveValue
{
/**
@@ -31,6 +36,8 @@ class CSSString extends PrimitiveValue
* @throws SourceException
* @throws UnexpectedEOFException
* @throws UnexpectedTokenException
*
* @internal since V8.8.0
*/
public static function parse(ParserState $oParserState)
{
@@ -87,6 +94,8 @@ class CSSString extends PrimitiveValue
/**
* @return string
*
* @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead.
*/
public function __toString()
{
@@ -94,9 +103,11 @@ class CSSString extends PrimitiveValue
}
/**
* @param OutputFormat|null $oOutputFormat
*
* @return string
*/
public function render(OutputFormat $oOutputFormat)
public function render($oOutputFormat)
{
$sString = addslashes($this->sString);
$sString = str_replace("\n", '\A', $sString);

View File

@@ -6,33 +6,57 @@ use Sabberworm\CSS\Parsing\ParserState;
use Sabberworm\CSS\Parsing\UnexpectedEOFException;
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
/**
* Support for `-webkit-calc` and `-moz-calc` is deprecated in version 8.8.0, and will be removed in version 9.0.0.
*/
class CalcFunction extends CSSFunction
{
/**
* @var int
*
* @internal
*/
const T_OPERAND = 1;
/**
* @var int
*
* @internal
*/
const T_OPERATOR = 2;
/**
* @param ParserState $oParserState
* @param bool $bIgnoreCase
*
* @return CalcFunction
*
* @throws UnexpectedTokenException
* @throws UnexpectedEOFException
*
* @internal since V8.8.0
*/
public static function parse(ParserState $oParserState)
public static function parse(ParserState $oParserState, $bIgnoreCase = false)
{
$aOperators = ['+', '-', '*', '/'];
$sFunction = trim($oParserState->consumeUntil('(', false, true));
$sFunction = $oParserState->parseIdentifier();
if ($oParserState->peek() != '(') {
// Found ; or end of line before an opening bracket
throw new UnexpectedTokenException('(', $oParserState->peek(), 'literal', $oParserState->currentLine());
} elseif (!in_array($sFunction, ['calc', '-moz-calc', '-webkit-calc'])) {
// Found invalid calc definition. Example calc (...
throw new UnexpectedTokenException('calc', $sFunction, 'literal', $oParserState->currentLine());
}
$oParserState->consume('(');
$oCalcList = new CalcRuleValueList($oParserState->currentLine());
$oList = new RuleValueList(',', $oParserState->currentLine());
$iNestingLevel = 0;
$iLastComponentType = null;
while (!$oParserState->comes(')') || $iNestingLevel > 0) {
if ($oParserState->isEnd() && $iNestingLevel === 0) {
break;
}
$oParserState->consumeWhiteSpace();
if ($oParserState->comes('(')) {
$iNestingLevel++;
@@ -72,7 +96,7 @@ class CalcFunction extends CSSFunction
sprintf(
'Next token was expected to be an operand of type %s. Instead "%s" was found.',
implode(', ', $aOperators),
$oVal
$oParserState->peek()
),
'',
'custom',
@@ -83,7 +107,9 @@ class CalcFunction extends CSSFunction
$oParserState->consumeWhiteSpace();
}
$oList->addListComponent($oCalcList);
$oParserState->consume(')');
if (!$oParserState->isEnd()) {
$oParserState->consume(')');
}
return new CalcFunction($sFunction, $oList, ',', $oParserState->currentLine());
}
}

View File

@@ -15,9 +15,11 @@ class CalcRuleValueList extends RuleValueList
}
/**
* @param OutputFormat|null $oOutputFormat
*
* @return string
*/
public function render(OutputFormat $oOutputFormat)
public function render($oOutputFormat)
{
return $oOutputFormat->implode(' ', $this->aComponents);
}

View File

@@ -7,6 +7,10 @@ use Sabberworm\CSS\Parsing\ParserState;
use Sabberworm\CSS\Parsing\UnexpectedEOFException;
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
/**
* `Color's can be input in the form #rrggbb, #rgb or schema(val1, val2, …) but are always stored as an array of
* ('s' => val1, 'c' => val2, 'h' => val3, …) and output in the second form.
*/
class Color extends CSSFunction
{
/**
@@ -19,12 +23,17 @@ class Color extends CSSFunction
}
/**
* @param ParserState $oParserState
* @param bool $bIgnoreCase
*
* @return Color|CSSFunction
*
* @throws UnexpectedEOFException
* @throws UnexpectedTokenException
*
* @internal since V8.8.0
*/
public static function parse(ParserState $oParserState)
public static function parse(ParserState $oParserState, $bIgnoreCase = false)
{
$aColor = [];
if ($oParserState->comes('#')) {
@@ -49,12 +58,19 @@ class Color extends CSSFunction
$oParserState->currentLine()
),
];
} else {
} elseif ($oParserState->strlen($sValue) === 6) {
$aColor = [
'r' => new Size(intval($sValue[0] . $sValue[1], 16), null, true, $oParserState->currentLine()),
'g' => new Size(intval($sValue[2] . $sValue[3], 16), null, true, $oParserState->currentLine()),
'b' => new Size(intval($sValue[4] . $sValue[5], 16), null, true, $oParserState->currentLine()),
];
} else {
throw new UnexpectedTokenException(
'Invalid hex color value',
$sValue,
'custom',
$oParserState->currentLine()
);
}
} else {
$sColorMode = $oParserState->parseIdentifier(true);
@@ -139,6 +155,8 @@ class Color extends CSSFunction
/**
* @return string
*
* @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead.
*/
public function __toString()
{
@@ -146,9 +164,11 @@ class Color extends CSSFunction
}
/**
* @param OutputFormat|null $oOutputFormat
*
* @return string
*/
public function render(OutputFormat $oOutputFormat)
public function render($oOutputFormat)
{
// Shorthand RGB color values
if ($oOutputFormat->getRGBHashNotation() && implode('', array_keys($this->aComponents)) === 'rgb') {

View File

@@ -23,6 +23,8 @@ class LineName extends ValueList
*
* @throws UnexpectedTokenException
* @throws UnexpectedEOFException
*
* @internal since V8.8.0
*/
public static function parse(ParserState $oParserState)
{
@@ -49,6 +51,8 @@ class LineName extends ValueList
/**
* @return string
*
* @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead.
*/
public function __toString()
{
@@ -56,9 +60,11 @@ class LineName extends ValueList
}
/**
* @param OutputFormat|null $oOutputFormat
*
* @return string
*/
public function render(OutputFormat $oOutputFormat)
public function render($oOutputFormat)
{
return '[' . parent::render(OutputFormat::createCompact()) . ']';
}

View File

@@ -2,6 +2,11 @@
namespace Sabberworm\CSS\Value;
/**
* This class is used to represent all multivalued rules like `font: bold 12px/3 Helvetica, Verdana, sans-serif;`
* (where the value would be a whitespace-separated list of the primitive value `bold`, a slash-separated list
* and a comma-separated list).
*/
class RuleValueList extends ValueList
{
/**

View File

@@ -7,24 +7,49 @@ use Sabberworm\CSS\Parsing\ParserState;
use Sabberworm\CSS\Parsing\UnexpectedEOFException;
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
/**
* A `Size` consists of a numeric `size` value and a unit.
*/
class Size extends PrimitiveValue
{
/**
* vh/vw/vm(ax)/vmin/rem are absolute insofar as they dont scale to the immediate parent (only the viewport)
*
* @var array<int, string>
*
* @internal
*/
const ABSOLUTE_SIZE_UNITS = ['px', 'cm', 'mm', 'mozmm', 'in', 'pt', 'pc', 'vh', 'vw', 'vmin', 'vmax', 'rem'];
const ABSOLUTE_SIZE_UNITS = [
'px',
'pt',
'pc',
'cm',
'mm',
'mozmm',
'in',
'vh',
'dvh',
'svh',
'lvh',
'vw',
'vmin',
'vmax',
'rem',
];
/**
* @var array<int, string>
*
* @internal
*/
const RELATIVE_SIZE_UNITS = ['%', 'em', 'ex', 'ch', 'fr'];
/**
* @var array<int, string>
*
* @internal
*/
const NON_SIZE_UNITS = ['deg', 'grad', 'rad', 's', 'ms', 'turns', 'Hz', 'kHz'];
const NON_SIZE_UNITS = ['deg', 'grad', 'rad', 's', 'ms', 'turn', 'Hz', 'kHz'];
/**
* @var array<int, array<string, string>>|null
@@ -67,6 +92,8 @@ class Size extends PrimitiveValue
*
* @throws UnexpectedEOFException
* @throws UnexpectedTokenException
*
* @internal since V8.8.0
*/
public static function parse(ParserState $oParserState, $bIsColorComponent = false)
{
@@ -74,9 +101,16 @@ class Size extends PrimitiveValue
if ($oParserState->comes('-')) {
$sSize .= $oParserState->consume('-');
}
while (is_numeric($oParserState->peek()) || $oParserState->comes('.')) {
while (is_numeric($oParserState->peek()) || $oParserState->comes('.') || $oParserState->comes('e', true)) {
if ($oParserState->comes('.')) {
$sSize .= $oParserState->consume('.');
} elseif ($oParserState->comes('e', true)) {
$sLookahead = $oParserState->peek(1, 1);
if (is_numeric($sLookahead) || $sLookahead === '+' || $sLookahead === '-') {
$sSize .= $oParserState->consume(2);
} else {
break; // Reached the unit part of the number like "em" or "ex"
}
} else {
$sSize .= $oParserState->consume(1);
}
@@ -188,6 +222,8 @@ class Size extends PrimitiveValue
/**
* @return string
*
* @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead.
*/
public function __toString()
{
@@ -195,14 +231,16 @@ class Size extends PrimitiveValue
}
/**
* @param OutputFormat|null $oOutputFormat
*
* @return string
*/
public function render(OutputFormat $oOutputFormat)
public function render($oOutputFormat)
{
$l = localeconv();
$sPoint = preg_quote($l['decimal_point'], '/');
$sSize = preg_match("/[\d\.]+e[+-]?\d+/i", (string)$this->fSize)
? preg_replace("/$sPoint?0+$/", "", sprintf("%f", $this->fSize)) : $this->fSize;
? preg_replace("/$sPoint?0+$/", "", sprintf("%f", $this->fSize)) : (string)$this->fSize;
return preg_replace(["/$sPoint/", "/^(-?)0\./"], ['.', '$1.'], $sSize)
. ($this->sUnit === null ? '' : $this->sUnit);
}

View File

@@ -8,6 +8,9 @@ use Sabberworm\CSS\Parsing\SourceException;
use Sabberworm\CSS\Parsing\UnexpectedEOFException;
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
/**
* This class represents URLs in CSS. `URL`s always output in `URL("")` notation.
*/
class URL extends PrimitiveValue
{
/**
@@ -30,14 +33,26 @@ class URL extends PrimitiveValue
* @throws SourceException
* @throws UnexpectedEOFException
* @throws UnexpectedTokenException
*
* @internal since V8.8.0
*/
public static function parse(ParserState $oParserState)
{
$bUseUrl = $oParserState->comes('url', true);
$oAnchor = $oParserState->anchor();
$sIdentifier = '';
for ($i = 0; $i < 3; $i++) {
$sChar = $oParserState->parseCharacter(true);
if ($sChar === null) {
break;
}
$sIdentifier .= $sChar;
}
$bUseUrl = $oParserState->streql($sIdentifier, 'url');
if ($bUseUrl) {
$oParserState->consume('url');
$oParserState->consumeWhiteSpace();
$oParserState->consume('(');
} else {
$oAnchor->backtrack();
}
$oParserState->consumeWhiteSpace();
$oResult = new URL(CSSString::parse($oParserState), $oParserState->currentLine());
@@ -66,6 +81,8 @@ class URL extends PrimitiveValue
/**
* @return string
*
* @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead.
*/
public function __toString()
{
@@ -73,9 +90,11 @@ class URL extends PrimitiveValue
}
/**
* @param OutputFormat|null $oOutputFormat
*
* @return string
*/
public function render(OutputFormat $oOutputFormat)
public function render($oOutputFormat)
{
return "url({$this->oURL->render($oOutputFormat)})";
}

View File

@@ -2,25 +2,28 @@
namespace Sabberworm\CSS\Value;
use Sabberworm\CSS\CSSElement;
use Sabberworm\CSS\Parsing\ParserState;
use Sabberworm\CSS\Parsing\SourceException;
use Sabberworm\CSS\Parsing\UnexpectedEOFException;
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
use Sabberworm\CSS\Renderable;
use Sabberworm\CSS\Position\Position;
use Sabberworm\CSS\Position\Positionable;
abstract class Value implements Renderable
/**
* Abstract base class for specific classes of CSS values: `Size`, `Color`, `CSSString` and `URL`, and another
* abstract subclass `ValueList`.
*/
abstract class Value implements CSSElement, Positionable
{
/**
* @var int
*/
protected $iLineNo;
use Position;
/**
* @param int $iLineNo
*/
public function __construct($iLineNo = 0)
{
$this->iLineNo = $iLineNo;
$this->setPosition($iLineNo);
}
/**
@@ -30,6 +33,8 @@ abstract class Value implements Renderable
*
* @throws UnexpectedTokenException
* @throws UnexpectedEOFException
*
* @internal since V8.8.0
*/
public static function parseValue(ParserState $oParserState, array $aListDelimiters = [])
{
@@ -39,8 +44,9 @@ abstract class Value implements Renderable
//Build a list of delimiters and parsed values
while (
!($oParserState->comes('}') || $oParserState->comes(';') || $oParserState->comes('!')
|| $oParserState->comes(')')
|| $oParserState->comes('\\'))
|| $oParserState->comes(')')
|| $oParserState->comes('\\')
|| $oParserState->isEnd())
) {
if (count($aStack) > 0) {
$bFoundDelimiter = false;
@@ -62,23 +68,30 @@ abstract class Value implements Renderable
}
// Convert the list to list objects
foreach ($aListDelimiters as $sDelimiter) {
if (count($aStack) === 1) {
$iStackLength = count($aStack);
if ($iStackLength === 1) {
return $aStack[0];
}
$iStartPosition = null;
while (($iStartPosition = array_search($sDelimiter, $aStack, true)) !== false) {
$aNewStack = [];
for ($iStartPosition = 0; $iStartPosition < $iStackLength; ++$iStartPosition) {
if ($iStartPosition === ($iStackLength - 1) || $sDelimiter !== $aStack[$iStartPosition + 1]) {
$aNewStack[] = $aStack[$iStartPosition];
continue;
}
$iLength = 2; //Number of elements to be joined
for ($i = $iStartPosition + 2; $i < count($aStack); $i += 2, ++$iLength) {
for ($i = $iStartPosition + 3; $i < $iStackLength; $i += 2, ++$iLength) {
if ($sDelimiter !== $aStack[$i]) {
break;
}
}
$oList = new RuleValueList($sDelimiter, $oParserState->currentLine());
for ($i = $iStartPosition - 1; $i - $iStartPosition + 1 < $iLength * 2; $i += 2) {
for ($i = $iStartPosition; $i - $iStartPosition < $iLength * 2; $i += 2) {
$oList->addListComponent($aStack[$i]);
}
array_splice($aStack, $iStartPosition - 1, $iLength * 2 - 1, [$oList]);
$aNewStack[] = $oList;
$iStartPosition += $iLength * 2 - 2;
}
$aStack = $aNewStack;
}
if (!isset($aStack[0])) {
throw new UnexpectedTokenException(
@@ -98,19 +111,30 @@ abstract class Value implements Renderable
*
* @throws UnexpectedEOFException
* @throws UnexpectedTokenException
*
* @internal since V8.8.0
*/
public static function parseIdentifierOrFunction(ParserState $oParserState, $bIgnoreCase = false)
{
$sResult = $oParserState->parseIdentifier($bIgnoreCase);
$oAnchor = $oParserState->anchor();
$mResult = $oParserState->parseIdentifier($bIgnoreCase);
if ($oParserState->comes('(')) {
$oParserState->consume('(');
$aArguments = Value::parseValue($oParserState, ['=', ' ', ',']);
$sResult = new CSSFunction($sResult, $aArguments, ',', $oParserState->currentLine());
$oParserState->consume(')');
$oAnchor->backtrack();
if ($oParserState->streql('url', $mResult)) {
$mResult = URL::parse($oParserState);
} elseif (
$oParserState->streql('calc', $mResult)
|| $oParserState->streql('-webkit-calc', $mResult)
|| $oParserState->streql('-moz-calc', $mResult)
) {
$mResult = CalcFunction::parse($oParserState);
} else {
$mResult = CSSFunction::parse($oParserState, $bIgnoreCase);
}
}
return $sResult;
return $mResult;
}
/**
@@ -119,6 +143,8 @@ abstract class Value implements Renderable
* @throws UnexpectedEOFException
* @throws UnexpectedTokenException
* @throws SourceException
*
* @internal since V8.8.0
*/
public static function parsePrimitiveValue(ParserState $oParserState)
{
@@ -133,13 +159,6 @@ abstract class Value implements Renderable
$oValue = Size::parse($oParserState);
} elseif ($oParserState->comes('#') || $oParserState->comes('rgb', true) || $oParserState->comes('hsl', true)) {
$oValue = Color::parse($oParserState);
} elseif ($oParserState->comes('url', true)) {
$oValue = URL::parse($oParserState);
} elseif (
$oParserState->comes('calc', true) || $oParserState->comes('-webkit-calc', true)
|| $oParserState->comes('-moz-calc', true)
) {
$oValue = CalcFunction::parse($oParserState);
} elseif ($oParserState->comes("'") || $oParserState->comes('"')) {
$oValue = CSSString::parse($oParserState);
} elseif ($oParserState->comes("progid:") && $oParserState->getSettings()->bLenientParsing) {
@@ -149,7 +168,16 @@ abstract class Value implements Renderable
} elseif ($oParserState->comes("U+")) {
$oValue = self::parseUnicodeRangeValue($oParserState);
} else {
$oValue = self::parseIdentifierOrFunction($oParserState);
$sNextChar = $oParserState->peek(1);
try {
$oValue = self::parseIdentifierOrFunction($oParserState);
} catch (UnexpectedTokenException $e) {
if (\in_array($sNextChar, ['+', '-', '*', '/'], true)) {
$oValue = $oParserState->consume(1);
} else {
throw $e;
}
}
}
$oParserState->consumeWhiteSpace();
return $oValue;
@@ -187,12 +215,4 @@ abstract class Value implements Renderable
} while (strlen($sRange) < $iCodepointMaxLength && preg_match("/[A-Fa-f0-9\?-]/", $oParserState->peek()));
return "U+{$sRange}";
}
/**
* @return int
*/
public function getLineNo()
{
return $this->iLineNo;
}
}

View File

@@ -4,15 +4,25 @@ namespace Sabberworm\CSS\Value;
use Sabberworm\CSS\OutputFormat;
/**
* A `ValueList` represents a lists of `Value`s, separated by some separation character
* (mostly `,`, whitespace, or `/`).
*
* There are two types of `ValueList`s: `RuleValueList` and `CSSFunction`
*/
abstract class ValueList extends Value
{
/**
* @var array<int, RuleValueList|CSSFunction|CSSString|LineName|Size|URL|string>
*
* @internal since 8.8.0
*/
protected $aComponents;
/**
* @var string
*
* @internal since 8.8.0
*/
protected $sSeparator;
@@ -80,6 +90,8 @@ abstract class ValueList extends Value
/**
* @return string
*
* @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead.
*/
public function __toString()
{
@@ -87,9 +99,11 @@ abstract class ValueList extends Value
}
/**
* @param OutputFormat|null $oOutputFormat
*
* @return string
*/
public function render(OutputFormat $oOutputFormat)
public function render($oOutputFormat)
{
return $oOutputFormat->implode(
$oOutputFormat->spaceBeforeListArgumentSeparator($this->sSeparator) . $this->sSeparator