Skip to content

Commit ba37b45

Browse files
authored
[FEATURE] Support arithmetic operators in CSS functions (#624)
This is the enhancement from #390. `calc` was already supported. Co-authored-by: Jake Hotson <jake.github@qzdesign.co.uk>
1 parent 9b91918 commit ba37b45

File tree

3 files changed

+118
-2
lines changed

3 files changed

+118
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).
66
## x.y.z
77

88
### Added
9+
- Support arithmetic operators in CSS function arguments (#607)
910
- Add support for inserting an item in a CSS list (#545)
10-
1111
- Add support for the `dvh`, `lvh` and `svh` length units (#415)
1212

1313
### Changed

src/Value/Value.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,16 @@ public static function parsePrimitiveValue(ParserState $oParserState)
163163
} elseif ($oParserState->comes("U+")) {
164164
$oValue = self::parseUnicodeRangeValue($oParserState);
165165
} else {
166-
$oValue = self::parseIdentifierOrFunction($oParserState);
166+
$sNextChar = $oParserState->peek(1);
167+
try {
168+
$oValue = self::parseIdentifierOrFunction($oParserState);
169+
} catch (UnexpectedTokenException $e) {
170+
if (\in_array($sNextChar, ['+', '-', '*', '/'], true)) {
171+
$oValue = $oParserState->consume(1);
172+
} else {
173+
throw $e;
174+
}
175+
}
167176
}
168177
$oParserState->consumeWhiteSpace();
169178
return $oValue;

tests/Value/ValueTest.php

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?php
2+
3+
namespace Sabberworm\CSS\Tests\Value;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use Sabberworm\CSS\Parsing\ParserState;
7+
use Sabberworm\CSS\Settings;
8+
use Sabberworm\CSS\Value\Value;
9+
10+
/**
11+
* @covers \Sabberworm\CSS\Value\Value
12+
*/
13+
final class ValueTest extends TestCase
14+
{
15+
/**
16+
* @return array<string, array{0: string}>
17+
*/
18+
public static function provideArithmeticOperator()
19+
{
20+
$units = ['+', '-', '*', '/'];
21+
22+
return \array_combine(
23+
$units,
24+
\array_map(
25+
function ($unit) {
26+
return [$unit];
27+
},
28+
$units
29+
)
30+
);
31+
}
32+
33+
/**
34+
* @test
35+
*
36+
* @dataProvider provideArithmeticOperator
37+
*/
38+
public function parsesArithmeticInFunctions($operator)
39+
{
40+
$subject = Value::parseValue(new ParserState('max(300px, 50vh ' . $operator . ' 10px);', Settings::create()));
41+
42+
self::assertSame('max(300px,50vh ' . $operator . ' 10px)', (string) $subject);
43+
}
44+
45+
/**
46+
* @return array<string, array{0: string, 1: string}>
47+
* The first datum is a template for the parser (using `sprintf` insertion marker `%s` for some expression).
48+
* The second is for the expected result, which may have whitespace and trailing semicolon removed.
49+
*/
50+
public static function provideCssFunctionTemplates()
51+
{
52+
return [
53+
'calc' => [
54+
'to be parsed' => 'calc(%s);',
55+
'expected' => 'calc(%s)',
56+
],
57+
'max' => [
58+
'to be parsed' => 'max(300px, %s);',
59+
'expected' => 'max(300px,%s)',
60+
],
61+
];
62+
}
63+
64+
/**
65+
* @test
66+
*
67+
* @dataProvider provideCssFunctionTemplates
68+
*/
69+
public function parsesArithmeticWithMultipleOperatorsInFunctions(
70+
$parserTemplate,
71+
$expectedResultTemplate
72+
) {
73+
static $expression = '300px + 10% + 10vw';
74+
75+
$subject = Value::parseValue(new ParserState(\sprintf($parserTemplate, $expression), Settings::create()));
76+
77+
self::assertSame(\sprintf($expectedResultTemplate, $expression), (string) $subject);
78+
}
79+
80+
/**
81+
* @return array<string, array{0: string, 1: string}>
82+
*/
83+
public static function provideMalformedLengthOperands()
84+
{
85+
return [
86+
'LHS missing number' => ['vh', '10px'],
87+
'RHS missing number' => ['50vh', 'px'],
88+
'LHS missing unit' => ['50', '10px'],
89+
'RHS missing unit' => ['50vh', '10'],
90+
];
91+
}
92+
93+
/**
94+
* @test
95+
*
96+
* @dataProvider provideMalformedLengthOperands
97+
*/
98+
public function parsesArithmeticWithMalformedOperandsInFunctions($leftOperand, $rightOperand)
99+
{
100+
$subject = Value::parseValue(new ParserState(
101+
'max(300px, ' . $leftOperand . ' + ' . $rightOperand . ');',
102+
Settings::create()
103+
));
104+
105+
self::assertSame('max(300px,' . $leftOperand . ' + ' . $rightOperand . ')', (string) $subject);
106+
}
107+
}

0 commit comments

Comments
 (0)