From 093c527264f81bd730880145f76392541e8d3332 Mon Sep 17 00:00:00 2001 From: Ivailo Hristov Date: Mon, 19 Sep 2022 17:47:22 +0300 Subject: [PATCH 1/5] Support arithmetic ops (+,-,*,/) in functions --- src/Value/CSSFunction.php | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index 300dc3ec..19e7d33b 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; From f734920a8f698e6009b57cc8ca0dfb8b20f1d5ec Mon Sep 17 00:00:00 2001 From: Ivailo Hristov Date: Mon, 19 Sep 2022 17:56:22 +0300 Subject: [PATCH 2/5] Add tests for arithmetic in functions --- tests/ParserTest.php | 13 +++++++++++++ tests/fixtures/function-arithmetic.css | 12 ++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 tests/fixtures/function-arithmetic.css diff --git a/tests/ParserTest.php b/tests/ParserTest.php index b6197700..0777af7e 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -1225,6 +1225,19 @@ 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()); + } + 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); +} From 184b610dad161cbf72caf4464c22f102ee70e8dd Mon Sep 17 00:00:00 2001 From: Ivailo Hristov Date: Mon, 19 Sep 2022 19:00:08 +0300 Subject: [PATCH 3/5] Handle arithmetic operators in Value --- src/Value/CSSFunction.php | 2 +- src/Value/Value.php | 11 ++++++++++- tests/ParserTest.php | 8 ++++---- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index 19e7d33b..570dc0fc 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -59,7 +59,7 @@ public static function parseName(ParserState $oParserState, $bIgnoreCase = false */ public static function parseArgs(ParserState $oParserState) { - return Value::parseValue($oParserState, ['=', ' ', ',', '/', '*', '+', '-']); + return Value::parseValue($oParserState, ['=', ' ', ',']); } /** diff --git a/src/Value/Value.php b/src/Value/Value.php index a920396b..8efa5195 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -156,7 +156,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, ['+', '-', '*', '/'])) { + $oValue = $oParserState->consume(1); + } else { + throw $e; + } + } } $oParserState->consumeWhiteSpace(); return $oValue; diff --git a/tests/ParserTest.php b/tests/ParserTest.php index 0777af7e..791942fe 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -1231,10 +1231,10 @@ public function lonelyImport() 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);}'; + $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()); } From 83d7609f27bbe052b3c49a2d6141d80f9d380718 Mon Sep 17 00:00:00 2001 From: Ivailo Hristov Date: Tue, 20 Sep 2022 07:20:14 +0300 Subject: [PATCH 4/5] Make the check for arithmetic operators strict --- src/Value/Value.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Value/Value.php b/src/Value/Value.php index 8efa5195..d0b74df2 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -160,7 +160,7 @@ public static function parsePrimitiveValue(ParserState $oParserState) try { $oValue = self::parseIdentifierOrFunction($oParserState); } catch (UnexpectedTokenException $e) { - if (in_array($sNextChar, ['+', '-', '*', '/'])) { + if (in_array($sNextChar, ['+', '-', '*', '/'], true)) { $oValue = $oParserState->consume(1); } else { throw $e; From ec9fc7c74c05342daddc8e86bab039f63582ae7b Mon Sep 17 00:00:00 2001 From: Ivailo Hristov Date: Wed, 21 Sep 2022 19:58:31 +0300 Subject: [PATCH 5/5] Resolve an infinite loop case --- src/Value/Value.php | 3 +++ tests/ParserTest.php | 10 ++++++++++ tests/fixtures/infinite-loop.css | 3 +++ 3 files changed, 16 insertions(+) create mode 100644 tests/fixtures/infinite-loop.css diff --git a/src/Value/Value.php b/src/Value/Value.php index d0b74df2..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]) { diff --git a/tests/ParserTest.php b/tests/ParserTest.php index 791942fe..2b93d7df 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -1238,6 +1238,16 @@ public function functionArithmeticInFile() 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/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: ///!; +}