diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index 300dc3ec..570dc0fc 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -33,6 +33,35 @@ public function __construct($sName, $aArguments, $sSeparator = ',', $iLineNo = 0 parent::__construct($aArguments, $sSeparator, $iLineNo); } + /** + * @param ParserState $oParserState + * @param bool $bIgnoreCase + * + * @return string + * + * @throws SourceException + * @throws UnexpectedEOFException + * @throws UnexpectedTokenException + */ + public static function parseName(ParserState $oParserState, $bIgnoreCase = false) + { + return $oParserState->parseIdentifier($bIgnoreCase); + } + + /** + * @param ParserState $oParserState + * + * @return array + * + * @throws SourceException + * @throws UnexpectedEOFException + * @throws UnexpectedTokenException + */ + public static function parseArgs(ParserState $oParserState) + { + return Value::parseValue($oParserState, ['=', ' ', ',']); + } + /** * @param ParserState $oParserState * @param bool $bIgnoreCase @@ -45,9 +74,9 @@ public function __construct($sName, $aArguments, $sSeparator = ',', $iLineNo = 0 */ public static function parse(ParserState $oParserState, $bIgnoreCase = false) { - $mResult = $oParserState->parseIdentifier($bIgnoreCase); + $mResult = self::parseName($oParserState, $bIgnoreCase); $oParserState->consume('('); - $aArguments = Value::parseValue($oParserState, ['=', ' ', ',']); + $aArguments = self::parseArgs($oParserState); $mResult = new CSSFunction($mResult, $aArguments, ',', $oParserState->currentLine()); $oParserState->consume(')'); return $mResult; diff --git a/src/Value/Value.php b/src/Value/Value.php index a920396b..3cd98da1 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -72,6 +72,9 @@ public static function parseValue(ParserState $oParserState, array $aListDelimit } $iStartPosition = null; while (($iStartPosition = array_search($sDelimiter, $aStack, true)) !== false) { + if ($iStartPosition === 0) { + break; + } $iLength = 2; //Number of elements to be joined for ($i = $iStartPosition + 2; $i < count($aStack); $i += 2, ++$iLength) { if ($sDelimiter !== $aStack[$i]) { @@ -156,7 +159,16 @@ public static function parsePrimitiveValue(ParserState $oParserState) } 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; diff --git a/tests/ParserTest.php b/tests/ParserTest.php index 74449ee2..9235cb03 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -1240,6 +1240,29 @@ public function lonelyImport() self::assertSame($sExpected, $oDoc->render()); } + /** + * @test + */ + public function functionArithmeticInFile() + { + $oDoc = self::parsedStructureForFile('function-arithmetic', Settings::create()->withMultibyteSupport(true)); + $sExpected = 'div {height: max(300,vh + 10);} +div {height: max(300,vh - 10);} +div {height: max(300,vh * 10);} +div {height: max(300,vh / 10);}'; + self::assertSame($sExpected, $oDoc->render()); + } + + /** + * @test + */ + public function infiniteLoopInFile() + { + $oDoc = self::parsedStructureForFile('infinite-loop', Settings::create()->withMultibyteSupport(true)); + $sExpected = 'div {}'; + self::assertSame($sExpected, $oDoc->render()); + } + public function escapedSpecialCaseTokens() { $oDoc = $this->parsedStructureForFile('escaped-tokens'); diff --git a/tests/fixtures/function-arithmetic.css b/tests/fixtures/function-arithmetic.css new file mode 100644 index 00000000..07a2c318 --- /dev/null +++ b/tests/fixtures/function-arithmetic.css @@ -0,0 +1,12 @@ +div { + height: max(300, vh + 10); +} +div { + height: max(300, vh - 10); +} +div { + height: max(300, vh * 10); +} +div { + height: max(300, vh / 10); +} diff --git a/tests/fixtures/infinite-loop.css b/tests/fixtures/infinite-loop.css new file mode 100644 index 00000000..9f7f31d5 --- /dev/null +++ b/tests/fixtures/infinite-loop.css @@ -0,0 +1,3 @@ +div { + height: ///!; +}