diff --git a/CHANGELOG.md b/CHANGELOG.md index e29397aa..217e5c36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). ## x.y.z ### Added +- Support arithmetic operators in CSS function arguments (#607) - Add support for inserting an item in a CSS list (#545) - - Add support for the `dvh`, `lvh` and `svh` length units (#415) ### Changed diff --git a/src/Value/Value.php b/src/Value/Value.php index 6be2110c..4b174db0 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -163,7 +163,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/Value/ValueTest.php b/tests/Value/ValueTest.php new file mode 100644 index 00000000..ac259466 --- /dev/null +++ b/tests/Value/ValueTest.php @@ -0,0 +1,107 @@ + + */ + public static function provideArithmeticOperator() + { + $units = ['+', '-', '*', '/']; + + return \array_combine( + $units, + \array_map( + function ($unit) { + return [$unit]; + }, + $units + ) + ); + } + + /** + * @test + * + * @dataProvider provideArithmeticOperator + */ + public function parsesArithmeticInFunctions($operator) + { + $subject = Value::parseValue(new ParserState('max(300px, 50vh ' . $operator . ' 10px);', Settings::create())); + + self::assertSame('max(300px,50vh ' . $operator . ' 10px)', (string) $subject); + } + + /** + * @return array + * The first datum is a template for the parser (using `sprintf` insertion marker `%s` for some expression). + * The second is for the expected result, which may have whitespace and trailing semicolon removed. + */ + public static function provideCssFunctionTemplates() + { + return [ + 'calc' => [ + 'to be parsed' => 'calc(%s);', + 'expected' => 'calc(%s)', + ], + 'max' => [ + 'to be parsed' => 'max(300px, %s);', + 'expected' => 'max(300px,%s)', + ], + ]; + } + + /** + * @test + * + * @dataProvider provideCssFunctionTemplates + */ + public function parsesArithmeticWithMultipleOperatorsInFunctions( + $parserTemplate, + $expectedResultTemplate + ) { + static $expression = '300px + 10% + 10vw'; + + $subject = Value::parseValue(new ParserState(\sprintf($parserTemplate, $expression), Settings::create())); + + self::assertSame(\sprintf($expectedResultTemplate, $expression), (string) $subject); + } + + /** + * @return array + */ + public static function provideMalformedLengthOperands() + { + return [ + 'LHS missing number' => ['vh', '10px'], + 'RHS missing number' => ['50vh', 'px'], + 'LHS missing unit' => ['50', '10px'], + 'RHS missing unit' => ['50vh', '10'], + ]; + } + + /** + * @test + * + * @dataProvider provideMalformedLengthOperands + */ + public function parsesArithmeticWithMalformedOperandsInFunctions($leftOperand, $rightOperand) + { + $subject = Value::parseValue(new ParserState( + 'max(300px, ' . $leftOperand . ' + ' . $rightOperand . ');', + Settings::create() + )); + + self::assertSame('max(300px,' . $leftOperand . ' + ' . $rightOperand . ')', (string) $subject); + } +}