Skip to content
12 changes: 12 additions & 0 deletions config/phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ parameters:
count: 3
path: ../src/Value/Color.php

-
message: '#^Parameter \#2 \$arguments of class Sabberworm\\CSS\\Value\\Expression constructor expects array\<Sabberworm\\CSS\\Value\\Value\|string\>\|Sabberworm\\CSS\\Value\\RuleValueList, Sabberworm\\CSS\\Value\\Value\|string given\.$#'
identifier: argument.type
count: 1
path: ../src/Value/Expression.php

-
message: '#^Parameters should have "float" types as the only types passed to this method$#'
identifier: typePerfect.narrowPublicClassMethodParamType
Expand Down Expand Up @@ -107,3 +113,9 @@ parameters:
identifier: argument.type
count: 2
path: ../tests/Unit/CSSList/CSSListTest.php

-
message: '#^Call to an undefined static method Sabberworm\\CSS\\Tests\\Value\\ExpressionTest\:\:listDelimiterForRule\(\)\.$#'
identifier: staticMethod.notFound
count: 1
path: ../tests/Unit/Value/ExpressionTest.php
6 changes: 3 additions & 3 deletions src/Value/CSSFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@
class CSSFunction extends ValueList
{
/**
* @var non-empty-string
* @var string
*
* @internal since 8.8.0
*/
protected $name;

/**
* @param non-empty-string $name
* @param string $name
* @param RuleValueList|array<Value|string> $arguments
* @param non-empty-string $separator
* @param int<1, max>|null $lineNumber
Expand Down Expand Up @@ -76,7 +76,7 @@ private static function parseName(ParserState $parserState, bool $ignoreCase = f
* @throws UnexpectedEOFException
* @throws UnexpectedTokenException
*/
private static function parseArguments(ParserState $parserState)
protected static function parseArguments(ParserState $parserState)
{
return Value::parseValue($parserState, ['=', ' ', ',']);
}
Expand Down
27 changes: 27 additions & 0 deletions src/Value/Expression.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Sabberworm\CSS\Value;

use Sabberworm\CSS\Parsing\ParserState;
use Sabberworm\CSS\Parsing\SourceException;

/**
* An `Expression` represents a special kind of value that is comprised of multiple components wrapped in parenthesis.
* Examle `height: (vh - 10);`.
*/
class Expression extends CSSFunction
{
/**
* @throws SourceException
*/
public static function parse(ParserState $oParserState, bool $bIgnoreCase = false): CSSFunction
{
$oParserState->consume('(');
$aArguments = parent::parseArguments($oParserState);
$mResult = new Expression('', $aArguments, ',', $oParserState->currentLine());
$oParserState->consume(')');
return $mResult;
}
}
2 changes: 2 additions & 0 deletions src/Value/Value.php
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ public static function parsePrimitiveValue(ParserState $parserState)
$value = LineName::parse($parserState);
} elseif ($parserState->comes('U+')) {
$value = self::parseUnicodeRangeValue($parserState);
} elseif ($parserState->comes('(')) {
$value = Expression::parse($parserState);
} else {
$nextCharacter = $parserState->peek(1);
try {
Expand Down
31 changes: 29 additions & 2 deletions tests/ParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,20 @@ public function functionSyntax(): void
self::assertSame($expected, $document->render());
}

/**
* @test
*/
public function parseExpressions(): void
{
$oDoc = self::parsedStructureForFile('expressions');
$sExpected = 'div {height: (vh - 10);}'
. "\n"
. 'div {height: (vh - 10)/2;}'
. "\n"
. 'div {height: max(5,(vh - 10));}';
self::assertSame($sExpected, $oDoc->render());
}

/**
* @test
*/
Expand Down Expand Up @@ -593,8 +607,8 @@ public function calcNestedInFile(): void
public function invalidCalcInFile(): void
{
$document = self::parsedStructureForFile('calc-invalid', Settings::create()->withMultibyteSupport(true));
$expected = 'div {}
div {}
$expected = 'div {height: calc (25% - 1em);}
div {height: calc (25% - 1em);}
div {}
div {height: -moz-calc;}
div {height: calc;}';
Expand Down Expand Up @@ -1177,6 +1191,19 @@ public function lonelyImport(): void
self::assertSame($expected, $document->render());
}

/**
* @test
*/
public function functionArithmeticInFile(): void
{
$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(): void
{
$document = self::parsedStructureForFile('escaped-tokens');
Expand Down
69 changes: 69 additions & 0 deletions tests/Unit/Value/ExpressionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

declare(strict_types=1);

namespace Sabberworm\CSS\Tests\Value;

use PHPUnit\Framework\TestCase;
use Sabberworm\CSS\Parsing\ParserState;
use Sabberworm\CSS\OutputFormat;
use Sabberworm\CSS\Settings;
use Sabberworm\CSS\Value\Value;
use Sabberworm\CSS\Value\ValueList;
use Sabberworm\CSS\Value\Expression;
use Sabberworm\CSS\Rule\Rule;

/**
* @covers \Sabberworm\CSS\Value\Expression
*/
final class ExpressionTest extends TestCase
{
/**
* @return list<array{input: non-empty-string, expected_output: non-empty-string, expression_index: int}>
*/
public static function provideExpressions(): array
{
return [
[
'input' => '(vh - 10) / 2',
'expected_output' => '(vh - 10)/2',
'expression_index' => 0,
],
[
'input' => 'max(5, (vh - 10))',
'expected_output' => 'max(5,(vh - 10))',
'expression_index' => 1,
],
];
}

/**
* @test
*
* @dataProvider provideExpressions
*/
public function parseExpressions(string $input, string $expected, int $expression_index): void
{
$val = Value::parseValue(
new ParserState($input, Settings::create()),
$this->getDelimiters('height')
);

self::assertInstanceOf(ValueList::class, $val);
self::assertInstanceOf(Expression::class, $val->getListComponents()[$expression_index]);
self::assertSame($expected, $val->render(OutputFormat::createCompact()));
}

/**
* @return list<non-empty-string>
*/
private function getDelimiters(string $rule): array
{
$closure = function ($rule) {
return self::listDelimiterForRule($rule);
};

$getter = $closure->bindTo(null, Rule::class);
return $getter($rule);
}
}
11 changes: 11 additions & 0 deletions tests/fixtures/expressions.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
div {
height: (vh - 10);
}

div {
height: (vh - 10) / 2;
}

div {
height: max(5, (vh - 10));
}
12 changes: 12 additions & 0 deletions tests/fixtures/function-arithmetic.css
Original file line number Diff line number Diff line change
@@ -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);
}