From 0083c91961b057ab528fa3db6fc1442eb7993eaa Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 4 Feb 2024 01:07:21 +0100 Subject: [PATCH 01/78] [BUGFIX] Git-ignore the PHPUnit result cache (#429) The corresponding file gets created by more recent versions of PHPUnit. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c1747f26..acf0d9d8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /.phive/* /.php-cs-fixer.cache /.php_cs.cache +/.phpunit.result.cache /composer.lock /phpstan.neon /vendor/ From f0a35a54fd9faf8e3a1c7dcdb86339bd313795b8 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 3 Feb 2024 20:16:51 +0100 Subject: [PATCH 02/78] [TASK] Add the new maintainers to the `composer.json` (#428) --- composer.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/composer.json b/composer.json index 6b7c9607..70d5ea60 100644 --- a/composer.json +++ b/composer.json @@ -12,6 +12,14 @@ "authors": [ { "name": "Raphael Schweikert" + }, + { + "name": "Oliver Klee", + "email": "github@oliverklee.de" + }, + { + "name": "Jake Hotson", + "email": "jake.github@qzdesign.co.uk" } ], "require": { From a94b2f7c968b410b83c600356c92b8bc5cbdbc1d Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 6 Feb 2024 19:52:15 +0100 Subject: [PATCH 03/78] [CLEANUP] Make variable acces in `DeclarationBlock` more obvious (#448) --- config/phpstan-baseline.neon | 5 ----- src/RuleSet/DeclarationBlock.php | 3 ++- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index b730548c..243e36c4 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -10,11 +10,6 @@ parameters: count: 2 path: ../src/RuleSet/DeclarationBlock.php - - - message: "#^Variable \\$oRule might not be defined\\.$#" - count: 2 - path: ../src/RuleSet/DeclarationBlock.php - - message: "#^Variable \\$oVal might not be defined\\.$#" count: 1 diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index de487bc1..b218bd8c 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -565,6 +565,7 @@ public function expandListStyleShorthand() public function createShorthandProperties(array $aProperties, $sShorthand) { $aRules = $this->getRulesAssoc(); + $oRule = null; $aNewValues = []; foreach ($aProperties as $sProperty) { if (!isset($aRules[$sProperty])) { @@ -585,7 +586,7 @@ public function createShorthandProperties(array $aProperties, $sShorthand) $this->removeRule($sProperty); } } - if (count($aNewValues)) { + if ($aNewValues !== [] && $oRule instanceof Rule) { $oNewRule = new Rule($sShorthand, $oRule->getLineNo(), $oRule->getColNo()); foreach ($aNewValues as $mValue) { $oNewRule->addValue($mValue); From 046dd7ed9087c6618f76b65cd335b6a764b73e33 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 5 Feb 2024 03:41:25 +0100 Subject: [PATCH 04/78] [TASK] Make the data providers static (#443) Newer versions of PHPUnit require this, and we might as well do this now. Closes #414 --- tests/CSSList/AtRuleBlockListTest.php | 2 +- tests/CSSList/DocumentTest.php | 2 +- tests/RuleSet/DeclarationBlockTest.php | 16 ++++++++-------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/CSSList/AtRuleBlockListTest.php b/tests/CSSList/AtRuleBlockListTest.php index 48e6e578..7e5959b1 100644 --- a/tests/CSSList/AtRuleBlockListTest.php +++ b/tests/CSSList/AtRuleBlockListTest.php @@ -46,7 +46,7 @@ public function implementsCommentable() /** * @return array> */ - public function mediaRuleDataProvider() + public static function mediaRuleDataProvider() { return [ 'without spaces around arguments' => ['@media(min-width: 768px){.class{color:red}}'], diff --git a/tests/CSSList/DocumentTest.php b/tests/CSSList/DocumentTest.php index a727400b..3798eba9 100644 --- a/tests/CSSList/DocumentTest.php +++ b/tests/CSSList/DocumentTest.php @@ -50,7 +50,7 @@ public function getContentsInitiallyReturnsEmptyArray() /** * @return array>> */ - public function contentsDataProvider() + public static function contentsDataProvider() { return [ 'empty array' => [[]], diff --git a/tests/RuleSet/DeclarationBlockTest.php b/tests/RuleSet/DeclarationBlockTest.php index 49526952..5f92457e 100644 --- a/tests/RuleSet/DeclarationBlockTest.php +++ b/tests/RuleSet/DeclarationBlockTest.php @@ -33,7 +33,7 @@ public function expandBorderShorthand($sCss, $sExpected) /** * @return array> */ - public function expandBorderShorthandProvider() + public static function expandBorderShorthandProvider() { return [ ['body{ border: 2px solid #000 }', 'body {border-width: 2px;border-style: solid;border-color: #000;}'], @@ -66,7 +66,7 @@ public function expandFontShorthand($sCss, $sExpected) /** * @return array> */ - public function expandFontShorthandProvider() + public static function expandFontShorthandProvider() { return [ [ @@ -122,7 +122,7 @@ public function expandBackgroundShorthand($sCss, $sExpected) /** * @return array> */ - public function expandBackgroundShorthandProvider() + public static function expandBackgroundShorthandProvider() { return [ ['body {border: 1px;}', 'body {border: 1px;}'], @@ -175,7 +175,7 @@ public function expandDimensionsShorthand($sCss, $sExpected) /** * @return array> */ - public function expandDimensionsShorthandProvider() + public static function expandDimensionsShorthandProvider() { return [ ['body {border: 1px;}', 'body {border: 1px;}'], @@ -213,7 +213,7 @@ public function createBorderShorthand($sCss, $sExpected) /** * @return array> */ - public function createBorderShorthandProvider() + public static function createBorderShorthandProvider() { return [ ['body {border-width: 2px;border-style: solid;border-color: #000;}', 'body {border: 2px solid #000;}'], @@ -244,7 +244,7 @@ public function createFontShorthand($sCss, $sExpected) /** * @return array> */ - public function createFontShorthandProvider() + public static function createFontShorthandProvider() { return [ ['body {font-size: 12px; font-family: serif}', 'body {font: 12px serif;}'], @@ -287,7 +287,7 @@ public function createDimensionsShorthand($sCss, $sExpected) /** * @return array> */ - public function createDimensionsShorthandProvider() + public static function createDimensionsShorthandProvider() { return [ ['body {border: 1px;}', 'body {border: 1px;}'], @@ -325,7 +325,7 @@ public function createBackgroundShorthand($sCss, $sExpected) /** * @return array> */ - public function createBackgroundShorthandProvider() + public static function createBackgroundShorthandProvider() { return [ ['body {border: 1px;}', 'body {border: 1px;}'], From 57c353b66fb45f0fef3fb3c7a014ed29d68b5550 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 5 Feb 2024 03:16:48 +0100 Subject: [PATCH 05/78] [BUGFIX] Avoid double CI runs for pull requests (#437) --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b23f7449..8c169800 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,8 +1,10 @@ # https://help.github.com/en/categories/automating-your-workflow-with-github-actions on: - pull_request: push: + branches: + - main + pull_request: schedule: - cron: '3 3 * * 1' From 0e956a86710ec9bd6c8803ff667412f937dc34b0 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 4 Feb 2024 02:44:47 +0100 Subject: [PATCH 06/78] [CLEANUP] Autoformat the code (#440) Autoformat the code with the default PhpStorm settings (and re-run `composer fix:php` to minimize formatting-related changes when code is changed and autoformatted. --- src/OutputFormatter.php | 1 + src/Value/Value.php | 6 +++--- tests/Comment/CommentTest.php | 14 +++++++------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/OutputFormatter.php b/src/OutputFormatter.php index 7418494c..501d15da 100644 --- a/src/OutputFormatter.php +++ b/src/OutputFormatter.php @@ -215,6 +215,7 @@ public function removeLastSemicolon($sString) /** * * @param array $aComments + * * @return string */ public function comments(Commentable $oCommentable) diff --git a/src/Value/Value.php b/src/Value/Value.php index a920396b..ce6d5790 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -43,9 +43,9 @@ public static function parseValue(ParserState $oParserState, array $aListDelimit //Build a list of delimiters and parsed values while ( !($oParserState->comes('}') || $oParserState->comes(';') || $oParserState->comes('!') - || $oParserState->comes(')') - || $oParserState->comes('\\') - || $oParserState->isEnd()) + || $oParserState->comes(')') + || $oParserState->comes('\\') + || $oParserState->isEnd()) ) { if (count($aStack) > 0) { $bFoundDelimiter = false; diff --git a/tests/Comment/CommentTest.php b/tests/Comment/CommentTest.php index 29385f01..3096069a 100644 --- a/tests/Comment/CommentTest.php +++ b/tests/Comment/CommentTest.php @@ -140,11 +140,11 @@ public function keepCommentsInOutput() ', $oCss->render(OutputFormat::createPretty())); self::assertSame( '/** Number 11 **//**' . "\n" - . ' * Comments' . "\n" - . ' *//* Hell */@import url("some/url.css") screen;' - . '/* Number 4 *//* Number 5 */.foo,#bar{' - . '/* Number 6 */background-color:#000;}@media screen{' - . '/** Number 10 **/#foo.bar{/** Number 10b **/position:absolute;}}', + . ' * Comments' . "\n" + . ' *//* Hell */@import url("some/url.css") screen;' + . '/* Number 4 *//* Number 5 */.foo,#bar{' + . '/* Number 6 */background-color:#000;}@media screen{' + . '/** Number 10 **/#foo.bar{/** Number 10b **/position:absolute;}}', $oCss->render(OutputFormat::createCompact()->setRenderComments(true)) ); } @@ -170,8 +170,8 @@ public function stripCommentsFromOutput() ', $oCss->render(OutputFormat::createPretty()->setRenderComments(false))); self::assertSame( '@import url("some/url.css") screen;' - . '.foo,#bar{background-color:#000;}' - . '@media screen{#foo.bar{position:absolute;}}', + . '.foo,#bar{background-color:#000;}' + . '@media screen{#foo.bar{position:absolute;}}', $oCss->render(OutputFormat::createCompact()) ); } From 07b428712c29146f0122a62752e93d4a9fe1c675 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 4 Feb 2024 02:14:28 +0100 Subject: [PATCH 07/78] [TASK] Make the testcases final (#435) This helps communicate that the testcases are not intended to be subclassed. Fixes #431 --- tests/CSSList/AtRuleBlockListTest.php | 2 +- tests/CSSList/DocumentTest.php | 2 +- tests/CSSList/KeyFrameTest.php | 2 +- tests/Comment/CommentTest.php | 2 +- tests/OutputFormatTest.php | 2 +- tests/ParserTest.php | 2 +- tests/RuleSet/DeclarationBlockTest.php | 2 +- tests/RuleSet/LenientParsingTest.php | 2 +- tests/Value/CalcRuleValueListTest.php | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/CSSList/AtRuleBlockListTest.php b/tests/CSSList/AtRuleBlockListTest.php index 7e5959b1..cc234ce6 100644 --- a/tests/CSSList/AtRuleBlockListTest.php +++ b/tests/CSSList/AtRuleBlockListTest.php @@ -11,7 +11,7 @@ /** * @covers \Sabberworm\CSS\CSSList\AtRuleBlockList */ -class AtRuleBlockListTest extends TestCase +final class AtRuleBlockListTest extends TestCase { /** * @test diff --git a/tests/CSSList/DocumentTest.php b/tests/CSSList/DocumentTest.php index 3798eba9..23f6ef85 100644 --- a/tests/CSSList/DocumentTest.php +++ b/tests/CSSList/DocumentTest.php @@ -11,7 +11,7 @@ /** * @covers \Sabberworm\CSS\CSSList\Document */ -class DocumentTest extends TestCase +final class DocumentTest extends TestCase { /** * @var Document diff --git a/tests/CSSList/KeyFrameTest.php b/tests/CSSList/KeyFrameTest.php index 080d5f94..e1ae0615 100644 --- a/tests/CSSList/KeyFrameTest.php +++ b/tests/CSSList/KeyFrameTest.php @@ -11,7 +11,7 @@ /** * @covers \Sabberworm\CSS\CSSList\KeyFrame */ -class KeyFrameTest extends TestCase +final class KeyFrameTest extends TestCase { /** * @var KeyFrame diff --git a/tests/Comment/CommentTest.php b/tests/Comment/CommentTest.php index 3096069a..d26da963 100644 --- a/tests/Comment/CommentTest.php +++ b/tests/Comment/CommentTest.php @@ -14,7 +14,7 @@ * @covers \Sabberworm\CSS\OutputFormat * @covers \Sabberworm\CSS\OutputFormatter */ -class CommentTest extends TestCase +final class CommentTest extends TestCase { /** * @test diff --git a/tests/OutputFormatTest.php b/tests/OutputFormatTest.php index 0de39123..33714127 100644 --- a/tests/OutputFormatTest.php +++ b/tests/OutputFormatTest.php @@ -11,7 +11,7 @@ /** * @covers \Sabberworm\CSS\OutputFormat */ -class OutputFormatTest extends TestCase +final class OutputFormatTest extends TestCase { /** * @var string diff --git a/tests/ParserTest.php b/tests/ParserTest.php index 74449ee2..c5ddeb44 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -35,7 +35,7 @@ * @covers \Sabberworm\CSS\Value\Size::parse * @covers \Sabberworm\CSS\Value\URL::parse */ -class ParserTest extends TestCase +final class ParserTest extends TestCase { /** * @test diff --git a/tests/RuleSet/DeclarationBlockTest.php b/tests/RuleSet/DeclarationBlockTest.php index 5f92457e..27c6615f 100644 --- a/tests/RuleSet/DeclarationBlockTest.php +++ b/tests/RuleSet/DeclarationBlockTest.php @@ -10,7 +10,7 @@ /** * @covers \Sabberworm\CSS\RuleSet\DeclarationBlock */ -class DeclarationBlockTest extends TestCase +final class DeclarationBlockTest extends TestCase { /** * @param string $sCss diff --git a/tests/RuleSet/LenientParsingTest.php b/tests/RuleSet/LenientParsingTest.php index 5f5f224a..54d63e5b 100644 --- a/tests/RuleSet/LenientParsingTest.php +++ b/tests/RuleSet/LenientParsingTest.php @@ -19,7 +19,7 @@ * @covers \Sabberworm\CSS\Value\Size::parse * @covers \Sabberworm\CSS\Value\URL::parse */ -class LenientParsingTest extends TestCase +final class LenientParsingTest extends TestCase { /** * @test diff --git a/tests/Value/CalcRuleValueListTest.php b/tests/Value/CalcRuleValueListTest.php index 0a2c5304..0ce279fd 100644 --- a/tests/Value/CalcRuleValueListTest.php +++ b/tests/Value/CalcRuleValueListTest.php @@ -9,7 +9,7 @@ /** * @covers \Sabberworm\CSS\Value\CalcRuleValueList */ -class CalcRuleValueListTest extends TestCase +final class CalcRuleValueListTest extends TestCase { /** * @test From ffd5a84be790da59d8b42326fb5122744aa05369 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Wed, 14 Feb 2024 07:46:23 +0000 Subject: [PATCH 08/78] [BUGFIX] Allow at-rules to be parsed in strict mode (#456) (#475) The reverts the change to `CSSList` in https://github.com/MyIntervals/PHP-CSS-Parser/commit/134f4e62fe8ab9f316425f3c0f480b3f4f52d804 and adds a comment that `null` is an expected return value when the end of the list (or block) is reached. Fixes #352 --- CHANGELOG.md | 2 + src/CSSList/CSSList.php | 17 +++---- tests/CSSList/AtRuleBlockListTest.php | 64 +++++++++++++++++++++------ 3 files changed, 60 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c70ef0a1..ba1d8c96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Fixed +- Fix (regression) failure to parse at-rules with strict parsing (#456) + ## 8.5.0 ### Added diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index dcd8c331..603f662b 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -131,18 +131,15 @@ private static function parseListItem(ParserState $oParserState, CSSList $oList) } return $oAtRule; } elseif ($oParserState->comes('}')) { - if (!$oParserState->getSettings()->bLenientParsing) { - throw new UnexpectedTokenException('CSS selector', '}', 'identifier', $oParserState->currentLine()); - } else { - if ($bIsRoot) { - if ($oParserState->getSettings()->bLenientParsing) { - return DeclarationBlock::parse($oParserState); - } else { - throw new SourceException("Unopened {", $oParserState->currentLine()); - } + if ($bIsRoot) { + if ($oParserState->getSettings()->bLenientParsing) { + return DeclarationBlock::parse($oParserState); } else { - return null; + throw new SourceException("Unopened {", $oParserState->currentLine()); } + } else { + // End of list + return null; } } else { return DeclarationBlock::parse($oParserState, $oList); diff --git a/tests/CSSList/AtRuleBlockListTest.php b/tests/CSSList/AtRuleBlockListTest.php index cc234ce6..2e2bd046 100644 --- a/tests/CSSList/AtRuleBlockListTest.php +++ b/tests/CSSList/AtRuleBlockListTest.php @@ -7,12 +7,46 @@ use Sabberworm\CSS\CSSList\AtRuleBlockList; use Sabberworm\CSS\Parser; use Sabberworm\CSS\Renderable; +use Sabberworm\CSS\Settings; /** * @covers \Sabberworm\CSS\CSSList\AtRuleBlockList */ final class AtRuleBlockListTest extends TestCase { + /** + * @return array + */ + public static function provideMinWidthMediaRule() + { + return [ + 'without spaces around arguments' => ['@media(min-width: 768px){.class{color:red}}'], + 'with spaces around arguments' => ['@media (min-width: 768px) {.class{color:red}}'], + ]; + } + + /** + * @return array + */ + public static function provideSyntacticlyCorrectAtRule() + { + return [ + 'media print' => ['@media print { html { background: white; color: black; } }'], + 'keyframes' => ['@keyframes mymove { from { top: 0px; } }'], + 'supports' => [' + @supports (display: flex) { + .flex-container > * { + text-shadow: 0 0 2px blue; + float: none; + } + .flex-container { + display: flex; + } + } + '], + ]; + } + /** * @test */ @@ -43,23 +77,12 @@ public function implementsCommentable() self::assertInstanceOf(Commentable::class, $subject); } - /** - * @return array> - */ - public static function mediaRuleDataProvider() - { - return [ - 'without spaces around arguments' => ['@media(min-width: 768px){.class{color:red}}'], - 'with spaces around arguments' => ['@media (min-width: 768px) {.class{color:red}}'], - ]; - } - /** * @test * * @param string $css * - * @dataProvider mediaRuleDataProvider + * @dataProvider provideMinWidthMediaRule */ public function parsesRuleNameOfMediaQueries($css) { @@ -74,7 +97,7 @@ public function parsesRuleNameOfMediaQueries($css) * * @param string $css * - * @dataProvider mediaRuleDataProvider + * @dataProvider provideMinWidthMediaRule */ public function parsesArgumentsOfMediaQueries($css) { @@ -83,4 +106,19 @@ public function parsesArgumentsOfMediaQueries($css) self::assertSame('(min-width: 768px)', $atRuleBlockList->atRuleArgs()); } + + /** + * @test + * + * @param string $css + * + * @dataProvider provideMinWidthMediaRule + * @dataProvider provideSyntacticlyCorrectAtRule + */ + public function parsesSyntacticlyCorrectAtRuleInStrictMode($css) + { + $contents = (new Parser($css, Settings::create()->beStrict()))->parse()->getContents(); + + self::assertNotEmpty($contents, 'Failing CSS: `' . $css . '`'); + } } From 95d641d8f362e3a9686f59195e4528103fbae819 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 14 Feb 2024 19:53:53 +0100 Subject: [PATCH 09/78] [TASK] Stop collecting code coverage on the maintenance branch (#476) We're not using the code coverage of maintenance branches for anything and could as well speed up the CI build instead. --- .github/workflows/ci.yml | 17 +++-------------- composer.json | 3 +-- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8c169800..494a3b26 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,11 +41,7 @@ jobs: strategy: fail-fast: false matrix: - php-version: [ '5.6', '7.0', '7.1', '7.2', '7.3' ] - coverage: [ 'none' ] - include: - - php-version: '7.4' - coverage: xdebug + php-version: [ '5.6', '7.0', '7.1', '7.2', '7.3', '7.4' ] steps: - name: Checkout @@ -57,7 +53,7 @@ jobs: php-version: ${{ matrix.php-version }} ini-values: error_reporting=E_ALL tools: composer:v2 - coverage: "${{ matrix.coverage }}" + coverage: none - name: Show the Composer configuration run: composer config --global --list @@ -76,14 +72,7 @@ jobs: composer show; - name: Run Tests - run: ./vendor/bin/phpunit --coverage-clover build/coverage/xml - - - name: Upload coverage results to Codacy - env: - CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }} - if: "${{ matrix.coverage != 'none' && env.CODACY_PROJECT_TOKEN != '' }}" - run: | - ./vendor/bin/codacycoverage clover build/coverage/xml + run: ./vendor/bin/phpunit static-analysis: name: Static Analysis diff --git a/composer.json b/composer.json index 70d5ea60..e20dad75 100644 --- a/composer.json +++ b/composer.json @@ -27,8 +27,7 @@ "ext-iconv": "*" }, "require-dev": { - "phpunit/phpunit": "^5.7.27", - "codacy/coverage": "^1.4.3" + "phpunit/phpunit": "^5.7.27" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" From 1dbbccbb1868b71b9b05b4c836d3c14ad097007c Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 14 Feb 2024 19:57:40 +0100 Subject: [PATCH 10/78] [TASK] Also PHP-lint with PHP 8.3 on CI (#477) This is the backport of #426 to the maintenance branch. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 494a3b26..5c7c8631 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-22.04 strategy: matrix: - php-version: [ '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2' ] + php-version: [ '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3' ] steps: - name: Checkout From 4a3d572b0f8b28bb6fd016ae8bbfc445facef152 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 15 Feb 2024 17:41:13 +0100 Subject: [PATCH 11/78] [TASK] Prepare the 8.5.1 release (#478) --- CHANGELOG.md | 4 ++++ composer.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba1d8c96..302cf1dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Fixed +## 8.5.1 + +### Fixed + - Fix (regression) failure to parse at-rules with strict parsing (#456) ## 8.5.0 diff --git a/composer.json b/composer.json index e20dad75..cf15a944 100644 --- a/composer.json +++ b/composer.json @@ -44,7 +44,7 @@ }, "extra": { "branch-alias": { - "dev-master": "9.0.x-dev" + "dev-main": "9.0.x-dev" } }, "scripts": { From eed92ad5e92f3cb8c30d82fe1dfc1d1dcf9f2752 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 22 Feb 2024 11:45:41 +0100 Subject: [PATCH 12/78] [BUGFIX] Handle incorrect RGB colors better (#485) (#490) --- CHANGELOG.md | 1 + src/Value/Color.php | 9 ++++++++- tests/ParserTest.php | 2 ++ tests/fixtures/colortest.css | 1 + 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 302cf1dd..9175a1f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Fixed +- Fix PHP notice caused by parsing invalid color values having less than 6 characters (#485) - Fix (regression) failure to parse at-rules with strict parsing (#456) ## 8.5.0 diff --git a/src/Value/Color.php b/src/Value/Color.php index 1cf00cce..5daad412 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -56,12 +56,19 @@ public static function parse(ParserState $oParserState, $bIgnoreCase = false) $oParserState->currentLine() ), ]; - } else { + } elseif ($oParserState->strlen($sValue) === 6) { $aColor = [ 'r' => new Size(intval($sValue[0] . $sValue[1], 16), null, true, $oParserState->currentLine()), 'g' => new Size(intval($sValue[2] . $sValue[3], 16), null, true, $oParserState->currentLine()), 'b' => new Size(intval($sValue[4] . $sValue[5], 16), null, true, $oParserState->currentLine()), ]; + } else { + throw new UnexpectedTokenException( + 'Invalid hex color value', + $sValue, + 'custom', + $oParserState->currentLine() + ); } } else { $sColorMode = $oParserState->parseIdentifier(true); diff --git a/tests/ParserTest.php b/tests/ParserTest.php index c5ddeb44..a48ac0e7 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -146,6 +146,8 @@ public function colorParsing() 'l' => new Size(220.0, '%', true, $oColor->getLineNo()), 'a' => new Size(0000.3, null, true, $oColor->getLineNo()), ], $oColor->getColor()); + $aColorRule = $oRuleSet->getRules('outline-color'); + self::assertEmpty($aColorRule); } } foreach ($oDoc->getAllValues('color') as $sColor) { diff --git a/tests/fixtures/colortest.css b/tests/fixtures/colortest.css index 1c89cf41..f834aa77 100644 --- a/tests/fixtures/colortest.css +++ b/tests/fixtures/colortest.css @@ -9,6 +9,7 @@ #yours { background-color: hsl(220, 10%, 220%); background-color: hsla(220, 10%, 220%, 0.3); + outline-color: #22; } #variables { From b48925e53777c5a97c2273aaf88bb7ebacb46b67 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 24 Feb 2024 20:34:59 +0100 Subject: [PATCH 13/78] [TASK] Mark all class constants as `@internal` (#500) This communicates that the constants must not be accessed from outside the project, thus preventing breakage as most of the constants will have a reduced visibility in version 9 (#470). --- CHANGELOG.md | 2 ++ src/Parsing/ParserState.php | 2 ++ src/Property/AtRule.php | 4 ++++ src/Property/KeyframeSelector.php | 2 ++ src/Property/Selector.php | 6 ++++++ src/Value/CalcFunction.php | 4 ++++ src/Value/Size.php | 6 ++++++ 7 files changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9175a1f6..cf362493 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Changed +- Mark all class constants as `@internal` (#500) + ### Deprecated ### Removed diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 7a99f327..07578461 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -9,6 +9,8 @@ class ParserState { /** * @var null + * + * @internal */ const EOF = null; diff --git a/src/Property/AtRule.php b/src/Property/AtRule.php index 9536ff5e..77b8fd82 100644 --- a/src/Property/AtRule.php +++ b/src/Property/AtRule.php @@ -12,6 +12,8 @@ interface AtRule extends Renderable, Commentable * we’re whitelisting the block rules and have anything else be treated as a set rule. * * @var string + * + * @internal */ const BLOCK_RULES = 'media/document/supports/region-style/font-feature-values'; @@ -19,6 +21,8 @@ interface AtRule extends Renderable, Commentable * … and more font-specific ones (to be used inside font-feature-values) * * @var string + * + * @internal */ const SET_RULES = 'font-face/counter-style/page/swash/styleset/annotation'; diff --git a/src/Property/KeyframeSelector.php b/src/Property/KeyframeSelector.php index 14ea5ebb..9d4795a2 100644 --- a/src/Property/KeyframeSelector.php +++ b/src/Property/KeyframeSelector.php @@ -8,6 +8,8 @@ class KeyframeSelector extends Selector * regexp for specificity calculations * * @var string + * + * @internal */ const SELECTOR_VALIDATION_RX = '/ ^( diff --git a/src/Property/Selector.php b/src/Property/Selector.php index 70c9b2fd..159b64c4 100644 --- a/src/Property/Selector.php +++ b/src/Property/Selector.php @@ -12,6 +12,8 @@ class Selector * regexp for specificity calculations * * @var string + * + * @internal */ const NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX = '/ (\.[\w]+) # classes @@ -36,6 +38,8 @@ class Selector * regexp for specificity calculations * * @var string + * + * @internal */ const ELEMENTS_AND_PSEUDO_ELEMENTS_RX = '/ ((^|[\s\+\>\~]+)[\w]+ # elements @@ -49,6 +53,8 @@ class Selector * regexp for specificity calculations * * @var string + * + * @internal */ const SELECTOR_VALIDATION_RX = '/ ^( diff --git a/src/Value/CalcFunction.php b/src/Value/CalcFunction.php index 5ffd071f..c8791d98 100644 --- a/src/Value/CalcFunction.php +++ b/src/Value/CalcFunction.php @@ -10,11 +10,15 @@ class CalcFunction extends CSSFunction { /** * @var int + * + * @internal */ const T_OPERAND = 1; /** * @var int + * + * @internal */ const T_OPERATOR = 2; diff --git a/src/Value/Size.php b/src/Value/Size.php index 36a32381..19a29d52 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -16,16 +16,22 @@ class Size extends PrimitiveValue * vh/vw/vm(ax)/vmin/rem are absolute insofar as they don’t scale to the immediate parent (only the viewport) * * @var array + * + * @internal */ const ABSOLUTE_SIZE_UNITS = ['px', 'cm', 'mm', 'mozmm', 'in', 'pt', 'pc', 'vh', 'vw', 'vmin', 'vmax', 'rem']; /** * @var array + * + * @internal */ const RELATIVE_SIZE_UNITS = ['%', 'em', 'ex', 'ch', 'fr']; /** * @var array + * + * @internal */ const NON_SIZE_UNITS = ['deg', 'grad', 'rad', 's', 'ms', 'turn', 'Hz', 'kHz']; From 7d413c8f2c50ac9275b4a85e780b75494b2beca0 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 24 Jun 2024 00:39:00 +0200 Subject: [PATCH 14/78] [BUGFIX] Fix undefined local variable in `CalcFunction::parse()` (#617) This is the 8.x backport of #593. Fixes #585 --- CHANGELOG.md | 2 ++ config/phpstan-baseline.neon | 5 ----- src/Value/CalcFunction.php | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf362493..8e587109 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Fixed +- Fix undefined local variable in `CalcFunction::parse()` (#593) + ## 8.5.1 ### Fixed diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 243e36c4..82fcb3f4 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -10,8 +10,3 @@ parameters: count: 2 path: ../src/RuleSet/DeclarationBlock.php - - - message: "#^Variable \\$oVal might not be defined\\.$#" - count: 1 - path: ../src/Value/CalcFunction.php - diff --git a/src/Value/CalcFunction.php b/src/Value/CalcFunction.php index c8791d98..a3db2710 100644 --- a/src/Value/CalcFunction.php +++ b/src/Value/CalcFunction.php @@ -91,7 +91,7 @@ public static function parse(ParserState $oParserState, $bIgnoreCase = false) sprintf( 'Next token was expected to be an operand of type %s. Instead "%s" was found.', implode(', ', $aOperators), - $oVal + $oParserState->peek() ), '', 'custom', From ef8c59c02e54315ad6ab44113b42522878040010 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 28 Jun 2024 18:34:52 +0200 Subject: [PATCH 15/78] [TASK] Prepare the 8.5.2 release (#625) --- CHANGELOG.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e587109..86ceb2ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,14 +9,20 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Changed -- Mark all class constants as `@internal` (#500) - ### Deprecated ### Removed ### Fixed +## 8.5.2 + +### Changed + +- Mark all class constants as `@internal` (#500) + +### Fixed + - Fix undefined local variable in `CalcFunction::parse()` (#593) ## 8.5.1 From 2487bde9970da1ffffecf310bff743201b850059 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sat, 29 Jun 2024 08:15:29 +0100 Subject: [PATCH 16/78] [FEATURE] Add support for inserting an item in a CSSList (#623) Co-authored-by: Daniel Ziegenberg Co-authored-by: Jake Hotson --- CHANGELOG.md | 1 + src/CSSList/CSSList.php | 16 ++++++++++ tests/CSSList/DocumentTest.php | 55 ++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86ceb2ca..a68b8526 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ## x.y.z ### Added +- Add support for inserting an item in a CSS list (#545) ### Changed diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 603f662b..4bb37589 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -296,6 +296,22 @@ public function splice($iOffset, $iLength = null, $mReplacement = null) array_splice($this->aContents, $iOffset, $iLength, $mReplacement); } + /** + * Inserts an item in the CSS list before its sibling. If the desired sibling cannot be found, + * the item is appended at the end. + * + * @param RuleSet|CSSList|Import|Charset $item + * @param RuleSet|CSSList|Import|Charset $sibling + */ + public function insertBefore($item, $sibling) + { + if (in_array($sibling, $this->aContents, true)) { + $this->replace($sibling, [$item, $sibling]); + } else { + $this->append($item); + } + } + /** * Removes an item from the CSS list. * diff --git a/tests/CSSList/DocumentTest.php b/tests/CSSList/DocumentTest.php index 23f6ef85..d4903b7f 100644 --- a/tests/CSSList/DocumentTest.php +++ b/tests/CSSList/DocumentTest.php @@ -85,4 +85,59 @@ public function setContentsReplacesContentsSetInPreviousCall() self::assertSame($contents2, $this->subject->getContents()); } + + /** + * @test + */ + public function insertContentBeforeInsertsContentBeforeSibbling() + { + $bogusOne = new DeclarationBlock(); + $bogusOne->setSelectors('.bogus-one'); + $bogusTwo = new DeclarationBlock(); + $bogusTwo->setSelectors('.bogus-two'); + + $item = new DeclarationBlock(); + $item->setSelectors('.item'); + + $sibling = new DeclarationBlock(); + $sibling->setSelectors('.sibling'); + + $this->subject->setContents([$bogusOne, $sibling, $bogusTwo]); + + self::assertCount(3, $this->subject->getContents()); + + $this->subject->insertBefore($item, $sibling); + + self::assertCount(4, $this->subject->getContents()); + self::assertSame([$bogusOne, $item, $sibling, $bogusTwo], $this->subject->getContents()); + } + + /** + * @test + */ + public function insertContentBeforeAppendsIfSibblingNotFound() + { + $bogusOne = new DeclarationBlock(); + $bogusOne->setSelectors('.bogus-one'); + $bogusTwo = new DeclarationBlock(); + $bogusTwo->setSelectors('.bogus-two'); + + $item = new DeclarationBlock(); + $item->setSelectors('.item'); + + $sibling = new DeclarationBlock(); + $sibling->setSelectors('.sibling'); + + $orphan = new DeclarationBlock(); + $orphan->setSelectors('.forever-alone'); + + $this->subject->setContents([$bogusOne, $sibling, $bogusTwo]); + + self::assertCount(3, $this->subject->getContents()); + + $this->subject->insertBefore($item, $orphan); + + self::assertCount(4, $this->subject->getContents()); + self::assertSame([$bogusOne, $sibling, $bogusTwo, $item], $this->subject->getContents()); + } } From ab0baf7a5f362eae03e5bad47582f35f13da9d7e Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sat, 29 Jun 2024 08:22:08 +0100 Subject: [PATCH 17/78] [FEATURE] Add support for the `dvh`, `lvh` and `svh` length units (#627) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #412 For now, the `TestCase` just tests that all the unit values are parsed. (Other tests can be added here, but are beyond the scope of this change.) Co-authored-by: Kai König <50620424+KaiOnGitHub@users.noreply.github.com> Co-authored-by: Jake Hotson --- CHANGELOG.md | 2 ++ src/Value/Size.php | 7 +++++- tests/Value/SizeTest.php | 51 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 tests/Value/SizeTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index a68b8526..3f293533 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Added - Add support for inserting an item in a CSS list (#545) +- Add support for the `dvh`, `lvh` and `svh` length units (#415) + ### Changed ### Deprecated diff --git a/src/Value/Size.php b/src/Value/Size.php index 19a29d52..5b5ab772 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -19,7 +19,12 @@ class Size extends PrimitiveValue * * @internal */ - const ABSOLUTE_SIZE_UNITS = ['px', 'cm', 'mm', 'mozmm', 'in', 'pt', 'pc', 'vh', 'vw', 'vmin', 'vmax', 'rem']; + const ABSOLUTE_SIZE_UNITS = [ + 'px', 'pt', 'pc', + 'cm', 'mm', 'mozmm', 'in', + 'vh', 'dvh', 'svh', 'lvh', + 'vw', 'vmin', 'vmax', 'rem', + ]; /** * @var array diff --git a/tests/Value/SizeTest.php b/tests/Value/SizeTest.php new file mode 100644 index 00000000..d743087a --- /dev/null +++ b/tests/Value/SizeTest.php @@ -0,0 +1,51 @@ + + */ + public static function provideUnit() + { + $units = [ + 'px', 'pt', 'pc', + 'cm', 'mm', 'mozmm', 'in', + 'vh', 'dvh', 'svh', 'lvh', + 'vw', 'vmin', 'vmax', 'rem', + '%', 'em', 'ex', 'ch', 'fr', + 'deg', 'grad', 'rad', 's', 'ms', 'turn', 'Hz', 'kHz', + ]; + + return \array_combine( + $units, + \array_map( + function ($unit) { + return [$unit]; + }, + $units + ) + ); + } + + /** + * @test + * + * @dataProvider provideUnit + */ + public function parsesUnit($unit) + { + $subject = Size::parse(new ParserState('1' . $unit, Settings::create())); + + self::assertSame($unit, $subject->getUnit()); + } +} From 9b919186abb7d4fc1e3917ebd65fc361d0b8ddf8 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sat, 29 Jun 2024 08:22:55 +0100 Subject: [PATCH 18/78] [TASK] Avoid poor scaling of array_search() with very long arrays (#628) When there were many many elements in `$aStack`, starting the delimiter search from the beginning for each loop iteration was very slow. This is addressed by building a new array, rather than modifying `$aStack` in place, and iterating over it in a single pass. A particular 1.6M style string is now parsed in 5 seconds rather than 4 minutes. Co-authored-by: Bart Butler --- CHANGELOG.md | 2 ++ src/Value/Value.php | 19 +++++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f293533..e29397aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Changed +- Improve performance of Value::parseValue with many delimiters by refactoring to remove array_search() (#413) + ### Deprecated ### Removed diff --git a/src/Value/Value.php b/src/Value/Value.php index ce6d5790..6be2110c 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -67,23 +67,30 @@ public static function parseValue(ParserState $oParserState, array $aListDelimit } // Convert the list to list objects foreach ($aListDelimiters as $sDelimiter) { - if (count($aStack) === 1) { + $iStackLength = count($aStack); + if ($iStackLength === 1) { return $aStack[0]; } - $iStartPosition = null; - while (($iStartPosition = array_search($sDelimiter, $aStack, true)) !== false) { + $aNewStack = []; + for ($iStartPosition = 0; $iStartPosition < $iStackLength; ++$iStartPosition) { + if ($iStartPosition === ($iStackLength - 1) || $sDelimiter !== $aStack[$iStartPosition + 1]) { + $aNewStack[] = $aStack[$iStartPosition]; + continue; + } $iLength = 2; //Number of elements to be joined - for ($i = $iStartPosition + 2; $i < count($aStack); $i += 2, ++$iLength) { + for ($i = $iStartPosition + 3; $i < $iStackLength; $i += 2, ++$iLength) { if ($sDelimiter !== $aStack[$i]) { break; } } $oList = new RuleValueList($sDelimiter, $oParserState->currentLine()); - for ($i = $iStartPosition - 1; $i - $iStartPosition + 1 < $iLength * 2; $i += 2) { + for ($i = $iStartPosition; $i - $iStartPosition < $iLength * 2; $i += 2) { $oList->addListComponent($aStack[$i]); } - array_splice($aStack, $iStartPosition - 1, $iLength * 2 - 1, [$oList]); + $aNewStack[] = $oList; + $iStartPosition += $iLength * 2 - 2; } + $aStack = $aNewStack; } if (!isset($aStack[0])) { throw new UnexpectedTokenException( From ba37b451b6e81bda7e434e5a2f95afc72ee2cddb Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sun, 30 Jun 2024 13:49:07 +0100 Subject: [PATCH 19/78] [FEATURE] Support arithmetic operators in CSS functions (#624) This is the enhancement from #390. `calc` was already supported. Co-authored-by: Jake Hotson --- CHANGELOG.md | 2 +- src/Value/Value.php | 11 +++- tests/Value/ValueTest.php | 107 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 tests/Value/ValueTest.php 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); + } +} From d2fb94a9641be84d79c7548c6d39bbebba6e9a70 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Mon, 1 Jul 2024 08:33:21 +0100 Subject: [PATCH 20/78] [TASK] Prepare the 8.6.0 release (#630) --- CHANGELOG.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 217e5c36..d1837d0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,20 +6,26 @@ 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 -- Improve performance of Value::parseValue with many delimiters by refactoring to remove array_search() (#413) - ### Deprecated ### Removed ### Fixed +## 8.6.0 + +### 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 + +- Improve performance of Value::parseValue with many delimiters by refactoring to remove array_search() (#413) + ## 8.5.2 ### Changed From ddd9ea7e307fd79dd5296c517f3466cad645c6da Mon Sep 17 00:00:00 2001 From: Daniel Ziegenberg Date: Sun, 25 Aug 2024 21:54:37 +0200 Subject: [PATCH 21/78] [BUGFIX] Fix comment parsing to support multiple comments (#671) (#671) Because of an eager consumption of whitespace, the rule parsing would swallow a trailing comment, meaning the comment for the next rule would be affected. This patch addresses this by only consuming real whitespace without comments after a rule. Fixes #173 Signed-off-by: Daniel Ziegenberg --- CHANGELOG.md | 2 ++ src/Rule/Rule.php | 7 ++++++- tests/ParserTest.php | 18 +++++++++++++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1837d0b..cc91e719 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Fixed +- Fix comment parsing to support multiple comments (#671) + ## 8.6.0 ### Added diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index fc00c880..9b693ece 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -107,7 +107,12 @@ public static function parse(ParserState $oParserState) while ($oParserState->comes(';')) { $oParserState->consume(';'); } - $oParserState->consumeWhiteSpace(); + + // NOTE: This is a backport to fix comment parsing to support multiple + // comments. This will be rectified in version 9.0.0. + while (preg_match('/\\s/isSu', $oParserState->peek()) === 1) { + $oParserState->consume(1); + } return $oRule; } diff --git a/tests/ParserTest.php b/tests/ParserTest.php index a48ac0e7..036fd033 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -1162,7 +1162,7 @@ public function commentExtracting() /** * @test */ - public function flatCommentExtracting() + public function flatCommentExtractingOneComment() { $parser = new Parser('div {/*Find Me!*/left:10px; text-align:left;}'); $doc = $parser->parse(); @@ -1172,6 +1172,22 @@ public function flatCommentExtracting() self::assertCount(1, $comments); self::assertSame("Find Me!", $comments[0]->getComment()); } + /** + * @test + */ + public function flatCommentExtractingTwoComments() + { + $parser = new Parser('div {/*Find Me!*/left:10px; /*Find Me Too!*/text-align:left;}'); + $doc = $parser->parse(); + $contents = $doc->getContents(); + $divRules = $contents[0]->getRules(); + $rule1Comments = $divRules[0]->getComments(); + $rule2Comments = $divRules[1]->getComments(); + self::assertCount(1, $rule1Comments); + self::assertCount(1, $rule2Comments); + self::assertEquals('Find Me!', $rule1Comments[0]->getComment()); + self::assertEquals('Find Me Too!', $rule2Comments[0]->getComment()); + } /** * @test From 223a09f9837977fbc86d6c554aec982d253a53ab Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 27 Aug 2024 00:09:33 +0200 Subject: [PATCH 22/78] [FEATURE] Add PHP 8.4 to the CI matrix (#675) --- .github/workflows/ci.yml | 2 +- CHANGELOG.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c7c8631..b74e43c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-22.04 strategy: matrix: - php-version: [ '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3' ] + php-version: [ '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4' ] steps: - name: Checkout diff --git a/CHANGELOG.md b/CHANGELOG.md index cc91e719..97d58097 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Added +- Add official support for PHP 8.4 (#675) + ### Changed ### Deprecated From 2f3dbdb02f186c6c2e9d815ecea4a1a17922d909 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 28 Aug 2024 02:15:24 +0200 Subject: [PATCH 23/78] [BUGFIX] Avoid using `setUp()` in testcases (#683) If we want to work both with PHPUnit 5.x and 8.x, we cannot use PHPUnit's `setUp()` method: This method would need to have a `: void` return type declaration for PHPUnit 8.x, which is not possible with PHP 5.6 (the lowest version we support). --- tests/CSSList/DocumentTest.php | 16 +++++++++++++- tests/CSSList/KeyFrameTest.php | 8 ++++++- tests/OutputFormatTest.php | 38 +++++++++++++++++++++++++++++++++- 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/tests/CSSList/DocumentTest.php b/tests/CSSList/DocumentTest.php index d4903b7f..70a09c53 100644 --- a/tests/CSSList/DocumentTest.php +++ b/tests/CSSList/DocumentTest.php @@ -18,7 +18,7 @@ final class DocumentTest extends TestCase */ private $subject; - protected function setUp() + private function setUpTestcase() { $this->subject = new Document(); } @@ -28,6 +28,8 @@ protected function setUp() */ public function implementsRenderable() { + $this->setUpTestcase(); + self::assertInstanceOf(Renderable::class, $this->subject); } @@ -36,6 +38,8 @@ public function implementsRenderable() */ public function implementsCommentable() { + $this->setUpTestcase(); + self::assertInstanceOf(Commentable::class, $this->subject); } @@ -44,6 +48,8 @@ public function implementsCommentable() */ public function getContentsInitiallyReturnsEmptyArray() { + $this->setUpTestcase(); + self::assertSame([], $this->subject->getContents()); } @@ -68,6 +74,8 @@ public static function contentsDataProvider() */ public function setContentsSetsContents(array $contents) { + $this->setUpTestcase(); + $this->subject->setContents($contents); self::assertSame($contents, $this->subject->getContents()); @@ -78,6 +86,8 @@ public function setContentsSetsContents(array $contents) */ public function setContentsReplacesContentsSetInPreviousCall() { + $this->setUpTestcase(); + $contents2 = [new DeclarationBlock()]; $this->subject->setContents([new DeclarationBlock()]); @@ -91,6 +101,8 @@ public function setContentsReplacesContentsSetInPreviousCall() */ public function insertContentBeforeInsertsContentBeforeSibbling() { + $this->setUpTestcase(); + $bogusOne = new DeclarationBlock(); $bogusOne->setSelectors('.bogus-one'); $bogusTwo = new DeclarationBlock(); @@ -117,6 +129,8 @@ public function insertContentBeforeInsertsContentBeforeSibbling() */ public function insertContentBeforeAppendsIfSibblingNotFound() { + $this->setUpTestcase(); + $bogusOne = new DeclarationBlock(); $bogusOne->setSelectors('.bogus-one'); $bogusTwo = new DeclarationBlock(); diff --git a/tests/CSSList/KeyFrameTest.php b/tests/CSSList/KeyFrameTest.php index e1ae0615..b29c1018 100644 --- a/tests/CSSList/KeyFrameTest.php +++ b/tests/CSSList/KeyFrameTest.php @@ -18,7 +18,7 @@ final class KeyFrameTest extends TestCase */ protected $subject; - protected function setUp() + private function setUpTestcase() { $this->subject = new KeyFrame(); } @@ -28,6 +28,8 @@ protected function setUp() */ public function implementsAtRule() { + $this->setUpTestcase(); + self::assertInstanceOf(AtRule::class, $this->subject); } @@ -36,6 +38,8 @@ public function implementsAtRule() */ public function implementsRenderable() { + $this->setUpTestcase(); + self::assertInstanceOf(Renderable::class, $this->subject); } @@ -44,6 +48,8 @@ public function implementsRenderable() */ public function implementsCommentable() { + $this->setUpTestcase(); + self::assertInstanceOf(Commentable::class, $this->subject); } } diff --git a/tests/OutputFormatTest.php b/tests/OutputFormatTest.php index 33714127..ff543a9c 100644 --- a/tests/OutputFormatTest.php +++ b/tests/OutputFormatTest.php @@ -43,7 +43,7 @@ final class OutputFormatTest extends TestCase */ private $oDocument; - protected function setUp() + private function setUpTestcase() { $this->oParser = new Parser(self::TEST_CSS); $this->oDocument = $this->oParser->parse(); @@ -54,6 +54,8 @@ protected function setUp() */ public function plain() { + $this->setUpTestcase(); + self::assertSame( '.main, .test {font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif;background: white;} @media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}', @@ -66,6 +68,8 @@ public function plain() */ public function compact() { + $this->setUpTestcase(); + self::assertSame( '.main,.test{font:italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif;background:white;}' . '@media screen{.main{background-size:100% 100%;font-size:1.3em;background-color:#fff;}}', @@ -78,6 +82,8 @@ public function compact() */ public function pretty() { + $this->setUpTestcase(); + self::assertSame(self::TEST_CSS, $this->oDocument->render(OutputFormat::createPretty())); } @@ -86,6 +92,8 @@ public function pretty() */ public function spaceAfterListArgumentSeparator() { + $this->setUpTestcase(); + self::assertSame( '.main, .test {font: italic normal bold 16px/ 1.2 ' . '"Helvetica", Verdana, sans-serif;background: white;}' @@ -99,6 +107,8 @@ public function spaceAfterListArgumentSeparator() */ public function spaceAfterListArgumentSeparatorComplex() { + $this->setUpTestcase(); + self::assertSame( '.main, .test {font: italic normal bold 16px/1.2 "Helvetica", Verdana, sans-serif;background: white;}' . "\n@media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}", @@ -116,6 +126,8 @@ public function spaceAfterListArgumentSeparatorComplex() */ public function spaceAfterSelectorSeparator() { + $this->setUpTestcase(); + self::assertSame( '.main, .test {font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif;background: white;} @@ -129,6 +141,8 @@ public function spaceAfterSelectorSeparator() */ public function stringQuotingType() { + $this->setUpTestcase(); + self::assertSame( '.main, .test {font: italic normal bold 16px/1.2 \'Helvetica\',Verdana,sans-serif;background: white;} @media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}', @@ -141,6 +155,8 @@ public function stringQuotingType() */ public function rGBHashNotation() { + $this->setUpTestcase(); + self::assertSame( '.main, .test {font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif;background: white;} @media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: rgb(255,255,255);}}', @@ -153,6 +169,8 @@ public function rGBHashNotation() */ public function semicolonAfterLastRule() { + $this->setUpTestcase(); + self::assertSame( '.main, .test {font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif;background: white} @media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff}}', @@ -165,6 +183,8 @@ public function semicolonAfterLastRule() */ public function spaceAfterRuleName() { + $this->setUpTestcase(); + self::assertSame( '.main, .test {font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif;background: white;} @media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}', @@ -177,6 +197,8 @@ public function spaceAfterRuleName() */ public function spaceRules() { + $this->setUpTestcase(); + self::assertSame('.main, .test { font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif; background: white; @@ -193,6 +215,8 @@ public function spaceRules() */ public function spaceBlocks() { + $this->setUpTestcase(); + self::assertSame(' .main, .test {font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif;background: white;} @media screen { @@ -206,6 +230,8 @@ public function spaceBlocks() */ public function spaceBoth() { + $this->setUpTestcase(); + self::assertSame(' .main, .test { font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif; @@ -226,6 +252,8 @@ public function spaceBoth() */ public function spaceBetweenBlocks() { + $this->setUpTestcase(); + self::assertSame( '.main, .test {font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif;background: white;}' . '@media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}', @@ -238,6 +266,8 @@ public function spaceBetweenBlocks() */ public function indentation() { + $this->setUpTestcase(); + self::assertSame(' .main, .test { font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif; @@ -261,6 +291,8 @@ public function indentation() */ public function spaceBeforeBraces() { + $this->setUpTestcase(); + self::assertSame( '.main, .test{font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif;background: white;} @media screen{.main{background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}', @@ -273,6 +305,8 @@ public function spaceBeforeBraces() */ public function ignoreExceptionsOff() { + $this->setUpTestcase(); + $this->expectException(OutputException::class); $aBlocks = $this->oDocument->getAllDeclarationBlocks(); @@ -292,6 +326,8 @@ public function ignoreExceptionsOff() */ public function ignoreExceptionsOn() { + $this->setUpTestcase(); + $aBlocks = $this->oDocument->getAllDeclarationBlocks(); $oFirstBlock = $aBlocks[0]; $oFirstBlock->removeSelector('.main'); From 9e020021dbed170d5426a1158ea436f66ba00189 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 28 Aug 2024 23:33:51 +0200 Subject: [PATCH 24/78] [TASK] Block installations on unsupported higher PHP versions (#692) We want Composer to be able to figure out which versions of which libraries it can install on which version of PHP. To make this possible, we should only allow installations on PHP versions that we know to work. This is the v8.x backport of #691. --- CHANGELOG.md | 2 ++ composer.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97d58097..ddbb7c78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Changed +- Block installations on unsupported higher PHP versions (#691) + ### Deprecated ### Removed diff --git a/composer.json b/composer.json index cf15a944..049ff127 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ } ], "require": { - "php": ">=5.6.20", + "php": "^5.6.20 || ^7.0.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", "ext-iconv": "*" }, "require-dev": { From 895f7ad5493256dae58ac5cafdbdc5f4b34aa7f4 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 28 Aug 2024 23:45:31 +0200 Subject: [PATCH 25/78] [BUGFIX] Fix type errors in PHP strict mode (#695) This is the backport of #664. --- CHANGELOG.md | 1 + src/Value/Size.php | 2 +- tests/ParserTest.php | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddbb7c78..7d2d606d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Fixed +- Fix type errors in PHP strict mode (#695) - Fix comment parsing to support multiple comments (#671) ## 8.6.0 diff --git a/src/Value/Size.php b/src/Value/Size.php index 5b5ab772..648c9a60 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -223,7 +223,7 @@ public function render(OutputFormat $oOutputFormat) $l = localeconv(); $sPoint = preg_quote($l['decimal_point'], '/'); $sSize = preg_match("/[\d\.]+e[+-]?\d+/i", (string)$this->fSize) - ? preg_replace("/$sPoint?0+$/", "", sprintf("%f", $this->fSize)) : $this->fSize; + ? preg_replace("/$sPoint?0+$/", "", sprintf("%f", $this->fSize)) : (string)$this->fSize; return preg_replace(["/$sPoint/", "/^(-?)0\./"], ['.', '$1.'], $sSize) . ($this->sUnit === null ? '' : $this->sUnit); } diff --git a/tests/ParserTest.php b/tests/ParserTest.php index 036fd033..200dcba8 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -279,7 +279,7 @@ public function specificity() new Selector('ol li::before', true), ], $oDoc->getSelectorsBySpecificity('< 100')); self::assertEquals([new Selector('li.green', true)], $oDoc->getSelectorsBySpecificity('11')); - self::assertEquals([new Selector('ol li::before', true)], $oDoc->getSelectorsBySpecificity(3)); + self::assertEquals([new Selector('ol li::before', true)], $oDoc->getSelectorsBySpecificity('3')); } /** From c339830467317f14da1ae94e866e91358e379f99 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 29 Aug 2024 15:18:00 +0200 Subject: [PATCH 26/78] [TASK] Also allow higher versions of PHPUnit (#682) This hopefully will allow us to run the unit tests for all PHP version that we claim to support, i.e., from 5.6.x to currently 8.4.x. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 049ff127..e6a9f336 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "ext-iconv": "*" }, "require-dev": { - "phpunit/phpunit": "^5.7.27" + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.39" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" From d234a19c7900f9eae25e36a2744d3056031a222a Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 29 Aug 2024 18:22:02 +0200 Subject: [PATCH 27/78] [TASK] Run the tests with PHP versions up to 8.3 (#697) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b74e43c3..eebe098a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: strategy: fail-fast: false matrix: - php-version: [ '5.6', '7.0', '7.1', '7.2', '7.3', '7.4' ] + php-version: [ '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3' ] steps: - name: Checkout From b547d123144d13db83ef8e4573702dc0e10a5919 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 3 Sep 2024 01:42:02 +0200 Subject: [PATCH 28/78] [FEATURE] Run the tests on CI with PHP 8.4 as well (#701) --- .github/workflows/ci.yml | 2 +- CHANGELOG.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eebe098a..5b38b294 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: strategy: fail-fast: false matrix: - php-version: [ '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3' ] + php-version: [ '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4' ] steps: - name: Checkout diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d2d606d..e16874f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Added -- Add official support for PHP 8.4 (#675) +- Add support for PHP 8.4 (#675, #701) ### Changed From 592f4161a28a6b860b458f529dee0a931a29fd8f Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 3 Sep 2024 18:00:18 +0200 Subject: [PATCH 29/78] [TASK] Deprecate `Parser::setCharset()` and `Parser::getCharset()` (#703) This is the backport of #688, which fixed #681. --- CHANGELOG.md | 2 ++ src/Parser.php | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e16874f9..0c66fbf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Deprecated +- Deprecate `Parser::setCharset()` and `Parser::getCharset()` (#703) + ### Removed ### Fixed diff --git a/src/Parser.php b/src/Parser.php index e582cfab..d653b3a8 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -35,6 +35,8 @@ public function __construct($sText, Settings $oParserSettings = null, $iLineNo = * @param string $sCharset * * @return void + * + * @deprecated will be removed in version 9.0.0 with #687 */ public function setCharset($sCharset) { @@ -45,6 +47,8 @@ public function setCharset($sCharset) * Returns the charset that is used if the CSS does not contain an `@charset` declaration. * * @return void + * + * @deprecated will be removed in version 9.0.0 with #687 */ public function getCharset() { From d6a4b2abacd2883e4eb3fd0391deac8a97747fc6 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 5 Sep 2024 18:11:33 +0200 Subject: [PATCH 30/78] [TASK] Mark parsing-internal classes and methods as `@internal` (#711) Code that uses this library is not expected to call internal parsing functionality. Communicate this with the corresponding `@internal` annotation. This allows us to boldly refactor the parser code. This is the backport of #674. Part of #668 --- CHANGELOG.md | 1 + src/Parsing/Anchor.php | 3 +++ src/Parsing/ParserState.php | 3 +++ 3 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c66fbf3..ab4ecb9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Changed +- Mark parsing-internal classes and methods as `@internal` (#711) - Block installations on unsupported higher PHP versions (#691) ### Deprecated diff --git a/src/Parsing/Anchor.php b/src/Parsing/Anchor.php index 93789e26..9060b54e 100644 --- a/src/Parsing/Anchor.php +++ b/src/Parsing/Anchor.php @@ -2,6 +2,9 @@ namespace Sabberworm\CSS\Parsing; +/** + * @internal + */ class Anchor { /** diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 07578461..82e9006f 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -5,6 +5,9 @@ use Sabberworm\CSS\Comment\Comment; use Sabberworm\CSS\Settings; +/** + * @internal + */ class ParserState { /** From da556df72bf638e92ef6822b54391cc0049f1d9f Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 10 Sep 2024 18:18:40 +0200 Subject: [PATCH 31/78] [TASK] Deprecate the expansion of shorthand properties (#719) This is the backport of #714 and all those changes: - #578 - #580 - #579 - #577 - #576 - #575 - #574 - #573 - #572 - #571 - #570 - #569 - #566 - #567 - #558 --- CHANGELOG.md | 1 + src/CSSList/Document.php | 4 ++++ src/RuleSet/DeclarationBlock.php | 26 ++++++++++++++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab4ecb9b..eafcfe5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Deprecated +- Deprecate the expansion of shorthand properties (#719) - Deprecate `Parser::setCharset()` and `Parser::getCharset()` (#703) ### Removed diff --git a/src/CSSList/Document.php b/src/CSSList/Document.php index bad99831..133f5fea 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -128,6 +128,8 @@ public function getSelectorsBySpecificity($sSpecificitySearch = null) * Expands all shorthand properties to their long value. * * @return void + * + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function expandShorthands() { @@ -140,6 +142,8 @@ public function expandShorthands() * Create shorthands properties whenever possible. * * @return void + * + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function createShorthands() { diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index b218bd8c..f9de6558 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -181,6 +181,8 @@ public function getSelectors() * Splits shorthand declarations (e.g. `margin` or `font`) into their constituent parts. * * @return void + * + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function expandShorthands() { @@ -196,6 +198,8 @@ public function expandShorthands() * Creates shorthand declarations (e.g. `margin` or `font`) whenever possible. * * @return void + * + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function createShorthands() { @@ -215,6 +219,8 @@ public function createShorthands() * Multiple borders are not yet supported as of 3. * * @return void + * + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function expandBorderShorthand() { @@ -276,6 +282,8 @@ public function expandBorderShorthand() * Handles `margin`, `padding`, `border-color`, `border-style` and `border-width`. * * @return void + * + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function expandDimensionsShorthand() { @@ -336,6 +344,8 @@ public function expandDimensionsShorthand() * into their constituent parts. * * @return void + * + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function expandFontShorthand() { @@ -406,6 +416,8 @@ public function expandFontShorthand() * @see http://www.w3.org/TR/21/colors.html#propdef-background * * @return void + * + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function expandBackgroundShorthand() { @@ -478,6 +490,8 @@ public function expandBackgroundShorthand() /** * @return void + * + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function expandListStyleShorthand() { @@ -561,6 +575,8 @@ public function expandListStyleShorthand() * @param string $sShorthand * * @return void + * + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function createShorthandProperties(array $aProperties, $sShorthand) { @@ -597,6 +613,8 @@ public function createShorthandProperties(array $aProperties, $sShorthand) /** * @return void + * + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function createBackgroundShorthand() { @@ -612,6 +630,8 @@ public function createBackgroundShorthand() /** * @return void + * + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function createListStyleShorthand() { @@ -629,6 +649,8 @@ public function createListStyleShorthand() * Should be run after `create_dimensions_shorthand`! * * @return void + * + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function createBorderShorthand() { @@ -646,6 +668,8 @@ public function createBorderShorthand() * and converts them into shorthand CSS properties. * * @return void + * + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function createDimensionsShorthand() { @@ -720,6 +744,8 @@ public function createDimensionsShorthand() * At least `font-size` AND `font-family` must be present in order to create a shorthand declaration. * * @return void + * + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function createFontShorthand() { From 8e4e373b94def76d58f42fb4b04ec374d504ca45 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 19 Sep 2024 18:45:50 +0200 Subject: [PATCH 32/78] [TASK] State since which version code is `@internal`/`@deprecated` (#723) This will help us avoid breaking things in backports. This is the v8.x backport of #722. --- src/CSSList/Document.php | 4 ++-- src/Parser.php | 4 ++-- src/Parsing/Anchor.php | 2 +- src/Parsing/ParserState.php | 4 ++-- src/Property/AtRule.php | 4 ++-- src/Property/KeyframeSelector.php | 2 +- src/Property/Selector.php | 2 +- src/RuleSet/DeclarationBlock.php | 26 +++++++++++++------------- 8 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/CSSList/Document.php b/src/CSSList/Document.php index 133f5fea..bc5214db 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -129,7 +129,7 @@ public function getSelectorsBySpecificity($sSpecificitySearch = null) * * @return void * - * @deprecated This will be removed without substitution in version 9.0 in #511. + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function expandShorthands() { @@ -143,7 +143,7 @@ public function expandShorthands() * * @return void * - * @deprecated This will be removed without substitution in version 9.0 in #511. + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function createShorthands() { diff --git a/src/Parser.php b/src/Parser.php index d653b3a8..beada56e 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -36,7 +36,7 @@ public function __construct($sText, Settings $oParserSettings = null, $iLineNo = * * @return void * - * @deprecated will be removed in version 9.0.0 with #687 + * @deprecated since 8.7.0, will be removed in version 9.0.0 with #687 */ public function setCharset($sCharset) { @@ -48,7 +48,7 @@ public function setCharset($sCharset) * * @return void * - * @deprecated will be removed in version 9.0.0 with #687 + * @deprecated since 8.7.0, will be removed in version 9.0.0 with #687 */ public function getCharset() { diff --git a/src/Parsing/Anchor.php b/src/Parsing/Anchor.php index 9060b54e..a42893da 100644 --- a/src/Parsing/Anchor.php +++ b/src/Parsing/Anchor.php @@ -3,7 +3,7 @@ namespace Sabberworm\CSS\Parsing; /** - * @internal + * @internal since 8.7.0 */ class Anchor { diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 82e9006f..2427c5e1 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -6,14 +6,14 @@ use Sabberworm\CSS\Settings; /** - * @internal + * @internal since 8.7.0 */ class ParserState { /** * @var null * - * @internal + * @internal since 8.5.2 */ const EOF = null; diff --git a/src/Property/AtRule.php b/src/Property/AtRule.php index 77b8fd82..d946a904 100644 --- a/src/Property/AtRule.php +++ b/src/Property/AtRule.php @@ -13,7 +13,7 @@ interface AtRule extends Renderable, Commentable * * @var string * - * @internal + * @internal since 8.5.2 */ const BLOCK_RULES = 'media/document/supports/region-style/font-feature-values'; @@ -22,7 +22,7 @@ interface AtRule extends Renderable, Commentable * * @var string * - * @internal + * @internal since 8.5.2 */ const SET_RULES = 'font-face/counter-style/page/swash/styleset/annotation'; diff --git a/src/Property/KeyframeSelector.php b/src/Property/KeyframeSelector.php index 9d4795a2..2aff8d24 100644 --- a/src/Property/KeyframeSelector.php +++ b/src/Property/KeyframeSelector.php @@ -9,7 +9,7 @@ class KeyframeSelector extends Selector * * @var string * - * @internal + * @internal since 8.5.2 */ const SELECTOR_VALIDATION_RX = '/ ^( diff --git a/src/Property/Selector.php b/src/Property/Selector.php index 159b64c4..44355e63 100644 --- a/src/Property/Selector.php +++ b/src/Property/Selector.php @@ -54,7 +54,7 @@ class Selector * * @var string * - * @internal + * @internal since 8.5.2 */ const SELECTOR_VALIDATION_RX = '/ ^( diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index f9de6558..46578fcf 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -182,7 +182,7 @@ public function getSelectors() * * @return void * - * @deprecated This will be removed without substitution in version 9.0 in #511. + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function expandShorthands() { @@ -199,7 +199,7 @@ public function expandShorthands() * * @return void * - * @deprecated This will be removed without substitution in version 9.0 in #511. + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function createShorthands() { @@ -220,7 +220,7 @@ public function createShorthands() * * @return void * - * @deprecated This will be removed without substitution in version 9.0 in #511. + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function expandBorderShorthand() { @@ -283,7 +283,7 @@ public function expandBorderShorthand() * * @return void * - * @deprecated This will be removed without substitution in version 9.0 in #511. + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function expandDimensionsShorthand() { @@ -345,7 +345,7 @@ public function expandDimensionsShorthand() * * @return void * - * @deprecated This will be removed without substitution in version 9.0 in #511. + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function expandFontShorthand() { @@ -417,7 +417,7 @@ public function expandFontShorthand() * * @return void * - * @deprecated This will be removed without substitution in version 9.0 in #511. + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function expandBackgroundShorthand() { @@ -491,7 +491,7 @@ public function expandBackgroundShorthand() /** * @return void * - * @deprecated This will be removed without substitution in version 9.0 in #511. + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function expandListStyleShorthand() { @@ -576,7 +576,7 @@ public function expandListStyleShorthand() * * @return void * - * @deprecated This will be removed without substitution in version 9.0 in #511. + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function createShorthandProperties(array $aProperties, $sShorthand) { @@ -614,7 +614,7 @@ public function createShorthandProperties(array $aProperties, $sShorthand) /** * @return void * - * @deprecated This will be removed without substitution in version 9.0 in #511. + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function createBackgroundShorthand() { @@ -631,7 +631,7 @@ public function createBackgroundShorthand() /** * @return void * - * @deprecated This will be removed without substitution in version 9.0 in #511. + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function createListStyleShorthand() { @@ -650,7 +650,7 @@ public function createListStyleShorthand() * * @return void * - * @deprecated This will be removed without substitution in version 9.0 in #511. + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function createBorderShorthand() { @@ -669,7 +669,7 @@ public function createBorderShorthand() * * @return void * - * @deprecated This will be removed without substitution in version 9.0 in #511. + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function createDimensionsShorthand() { @@ -745,7 +745,7 @@ public function createDimensionsShorthand() * * @return void * - * @deprecated This will be removed without substitution in version 9.0 in #511. + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function createFontShorthand() { From 81c426f12a7192a1a4e0687498fae16765651ae6 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 20 Sep 2024 18:47:28 +0200 Subject: [PATCH 33/78] [TASK] Update PHPUnit (#725) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index e6a9f336..1a3dbfc0 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "ext-iconv": "*" }, "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.39" + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.40" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" From df0ac7c8e57833fab5a53f212435a4f87e9dd085 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 20 Sep 2024 18:55:59 +0200 Subject: [PATCH 34/78] [TASK] Use fixed PHPUnit versions (#726) This avoids build breakage when a new version of PHPUnit gets released. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 1a3dbfc0..685db65e 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "ext-iconv": "*" }, "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.40" + "phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.40" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" From 404aff35afa9c686e7aab3c3c67358f71ad36284 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 23 Oct 2024 17:54:19 +0200 Subject: [PATCH 35/78] [TASK] Add some more tests for parsing comments (#739) Also add a skipped test for the broken behavior that currently is blocking Emogrifier. This is the 8.x backport of #738. --- tests/RuleSet/DeclarationBlockTest.php | 57 ++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/tests/RuleSet/DeclarationBlockTest.php b/tests/RuleSet/DeclarationBlockTest.php index 27c6615f..ea48f283 100644 --- a/tests/RuleSet/DeclarationBlockTest.php +++ b/tests/RuleSet/DeclarationBlockTest.php @@ -3,8 +3,10 @@ namespace Sabberworm\CSS\Tests\RuleSet; use PHPUnit\Framework\TestCase; +use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Parser; use Sabberworm\CSS\Rule\Rule; +use Sabberworm\CSS\Settings as ParserSettings; use Sabberworm\CSS\Value\Size; /** @@ -450,4 +452,59 @@ public function orderOfElementsMatchingOriginalOrderAfterExpandingShorthands() array_map('strval', $oDeclaration->getRulesAssoc()) ); } + + /** + * @return array + */ + public static function declarationBlocksWithCommentsProvider() + { + return [ + 'CSS comments with one asterisk' => ['p {color: #000;/* black */}', 'p {color: #000;}'], + 'CSS comments with two asterisks' => ['p {color: #000;/** black */}', 'p {color: #000;}'], + ]; + } + + /** + * @test + * + * @param string $cssWithComments + * @param string $cssWithoutComments + * + * @dataProvider declarationBlocksWithCommentsProvider + */ + public function canRemoveCommentsFromRulesUsingLenientParsing( + $cssWithComments, + $cssWithoutComments + ) { + $parserSettings = ParserSettings::create()->withLenientParsing(true); + $document = (new Parser($cssWithComments, $parserSettings))->parse(); + + $outputFormat = (new OutputFormat())->setRenderComments(false); + $renderedDocument = $document->render($outputFormat); + + self::assertSame($cssWithoutComments, $renderedDocument); + } + + /** + * @test + * + * @param string $cssWithComments + * @param string $cssWithoutComments + * + * @dataProvider declarationBlocksWithCommentsProvider + */ + public function canRemoveCommentsFromRulesUsingStrictParsing( + $cssWithComments, + $cssWithoutComments + ) { + self::markTestSkipped('This currently crashes, and we need to fix it.'); + + $parserSettings = ParserSettings::create()->withLenientParsing(false); + $document = (new Parser($cssWithComments, $parserSettings))->parse(); + + $outputFormat = (new OutputFormat())->setRenderComments(false); + $renderedDocument = $document->render($outputFormat); + + self::assertSame($cssWithoutComments, $renderedDocument); + } } From 81582a72cc937beb986b5206e834314c00ddf388 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 24 Oct 2024 18:10:52 +0200 Subject: [PATCH 36/78] [BUGFIX] Revert broken support for multiple comments (#741) This reverts e4c66f6 (#672), which broke comment parsing in strict mode. We'll need to re-implement support for multiple comments later in a way that does not break the existing comment parsing. This is the v8.x backport of #740. --- CHANGELOG.md | 1 - src/Rule/Rule.php | 6 +----- tests/ParserTest.php | 3 +++ tests/RuleSet/DeclarationBlockTest.php | 2 -- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eafcfe5f..1428bf75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,6 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Fixed - Fix type errors in PHP strict mode (#695) -- Fix comment parsing to support multiple comments (#671) ## 8.6.0 diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index 9b693ece..80f2490f 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -108,11 +108,7 @@ public static function parse(ParserState $oParserState) $oParserState->consume(';'); } - // NOTE: This is a backport to fix comment parsing to support multiple - // comments. This will be rectified in version 9.0.0. - while (preg_match('/\\s/isSu', $oParserState->peek()) === 1) { - $oParserState->consume(1); - } + $oParserState->consumeWhiteSpace(); return $oRule; } diff --git a/tests/ParserTest.php b/tests/ParserTest.php index 200dcba8..c8e8ac76 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -1172,11 +1172,14 @@ public function flatCommentExtractingOneComment() self::assertCount(1, $comments); self::assertSame("Find Me!", $comments[0]->getComment()); } + /** * @test */ public function flatCommentExtractingTwoComments() { + self::markTestSkipped('This is currently broken.'); + $parser = new Parser('div {/*Find Me!*/left:10px; /*Find Me Too!*/text-align:left;}'); $doc = $parser->parse(); $contents = $doc->getContents(); diff --git a/tests/RuleSet/DeclarationBlockTest.php b/tests/RuleSet/DeclarationBlockTest.php index ea48f283..d37ece18 100644 --- a/tests/RuleSet/DeclarationBlockTest.php +++ b/tests/RuleSet/DeclarationBlockTest.php @@ -497,8 +497,6 @@ public function canRemoveCommentsFromRulesUsingStrictParsing( $cssWithComments, $cssWithoutComments ) { - self::markTestSkipped('This currently crashes, and we need to fix it.'); - $parserSettings = ParserSettings::create()->withLenientParsing(false); $document = (new Parser($cssWithComments, $parserSettings))->parse(); From 068853dc0f6fb9eb3a0c7419a6e40fd876e65dcc Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 26 Oct 2024 00:45:37 +0200 Subject: [PATCH 37/78] [BUGFIX] Avoid implicit nullable parameters (#746) This avoids deprecation warnings in PHP 8.4. Unfortunately, we need to remove the native type declaration for this to keep supporting PHP down to version 5.6. --- CHANGELOG.md | 2 +- src/CSSList/AtRuleBlockList.php | 4 +++- src/CSSList/Document.php | 2 +- src/CSSList/KeyFrame.php | 4 +++- src/Comment/Comment.php | 4 +++- src/Parser.php | 2 +- src/Property/CSSNamespace.php | 4 +++- src/Property/Charset.php | 4 +++- src/Property/Import.php | 4 +++- src/Renderable.php | 4 +++- src/Rule/Rule.php | 4 +++- src/RuleSet/AtRuleSet.php | 4 +++- src/RuleSet/DeclarationBlock.php | 4 +++- src/Value/CSSFunction.php | 4 +++- src/Value/CSSString.php | 4 +++- src/Value/CalcRuleValueList.php | 4 +++- src/Value/Color.php | 4 +++- src/Value/LineName.php | 4 +++- src/Value/Size.php | 4 +++- src/Value/URL.php | 4 +++- src/Value/ValueList.php | 4 +++- 21 files changed, 57 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1428bf75..dfe4b918 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Added -- Add support for PHP 8.4 (#675, #701) +- Add support for PHP 8.4 (#675, #701, #746) ### Changed diff --git a/src/CSSList/AtRuleBlockList.php b/src/CSSList/AtRuleBlockList.php index 598fefc1..5930b932 100644 --- a/src/CSSList/AtRuleBlockList.php +++ b/src/CSSList/AtRuleBlockList.php @@ -57,9 +57,11 @@ public function __toString() } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { $sResult = $oOutputFormat->comments($this); $sResult .= $oOutputFormat->sBeforeAtRuleBlock; diff --git a/src/CSSList/Document.php b/src/CSSList/Document.php index bc5214db..7d9c6ec0 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -159,7 +159,7 @@ public function createShorthands() * * @return string */ - public function render(OutputFormat $oOutputFormat = null) + public function render($oOutputFormat = null) { if ($oOutputFormat === null) { $oOutputFormat = new OutputFormat(); diff --git a/src/CSSList/KeyFrame.php b/src/CSSList/KeyFrame.php index caef7b3d..69e2e4d9 100644 --- a/src/CSSList/KeyFrame.php +++ b/src/CSSList/KeyFrame.php @@ -68,9 +68,11 @@ public function __toString() } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { $sResult = $oOutputFormat->comments($this); $sResult .= "@{$this->vendorKeyFrame} {$this->animationName}{$oOutputFormat->spaceBeforeOpeningBrace()}{"; diff --git a/src/Comment/Comment.php b/src/Comment/Comment.php index 6128d749..e6ffaaf7 100644 --- a/src/Comment/Comment.php +++ b/src/Comment/Comment.php @@ -62,9 +62,11 @@ public function __toString() } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { return '/*' . $this->sComment . '*/'; } diff --git a/src/Parser.php b/src/Parser.php index beada56e..f60fc086 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -21,7 +21,7 @@ class Parser * @param Settings|null $oParserSettings * @param int $iLineNo the line number (starting from 1, not from 0) */ - public function __construct($sText, Settings $oParserSettings = null, $iLineNo = 1) + public function __construct($sText, $oParserSettings = null, $iLineNo = 1) { if ($oParserSettings === null) { $oParserSettings = Settings::create(); diff --git a/src/Property/CSSNamespace.php b/src/Property/CSSNamespace.php index 0d7eb496..d1bac4f7 100644 --- a/src/Property/CSSNamespace.php +++ b/src/Property/CSSNamespace.php @@ -60,9 +60,11 @@ public function __toString() } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { return '@namespace ' . ($this->sPrefix === null ? '' : $this->sPrefix . ' ') . $this->mUrl->render($oOutputFormat) . ';'; diff --git a/src/Property/Charset.php b/src/Property/Charset.php index 26e1b250..870380e3 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -78,9 +78,11 @@ public function __toString() } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { return "{$oOutputFormat->comments($this)}@charset {$this->oCharset->render($oOutputFormat)};"; } diff --git a/src/Property/Import.php b/src/Property/Import.php index d715a7a0..2a7cad54 100644 --- a/src/Property/Import.php +++ b/src/Property/Import.php @@ -79,9 +79,11 @@ public function __toString() } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { return $oOutputFormat->comments($this) . "@import " . $this->oLocation->render($oOutputFormat) . ($this->sMediaQuery === null ? '' : ' ' . $this->sMediaQuery) . ';'; diff --git a/src/Renderable.php b/src/Renderable.php index dc1bff3c..d7c6aba6 100644 --- a/src/Renderable.php +++ b/src/Renderable.php @@ -10,9 +10,11 @@ interface Renderable public function __toString(); /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat); + public function render($oOutputFormat); /** * @return int diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index 80f2490f..c1bad65e 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -344,9 +344,11 @@ public function __toString() } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { $sResult = "{$oOutputFormat->comments($this)}{$this->sRule}:{$oOutputFormat->spaceAfterRuleName()}"; if ($this->mValue instanceof Value) { // Can also be a ValueList diff --git a/src/RuleSet/AtRuleSet.php b/src/RuleSet/AtRuleSet.php index aab6d799..93fd07af 100644 --- a/src/RuleSet/AtRuleSet.php +++ b/src/RuleSet/AtRuleSet.php @@ -60,9 +60,11 @@ public function __toString() } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { $sResult = $oOutputFormat->comments($this); $sArgs = $this->sArgs; diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 46578fcf..8a1da14e 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -836,11 +836,13 @@ public function __toString() } /** + * @param OutputFormat|null $oOutputFormat + * * @return string * * @throws OutputException */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { $sResult = $oOutputFormat->comments($this); if (count($this->aSelectors) === 0) { diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index 300dc3ec..82ffc48c 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -88,9 +88,11 @@ public function __toString() } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { $aArguments = parent::render($oOutputFormat); return "{$this->sName}({$aArguments})"; diff --git a/src/Value/CSSString.php b/src/Value/CSSString.php index da498d41..c19b238f 100644 --- a/src/Value/CSSString.php +++ b/src/Value/CSSString.php @@ -99,9 +99,11 @@ public function __toString() } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { $sString = addslashes($this->sString); $sString = str_replace("\n", '\A', $sString); diff --git a/src/Value/CalcRuleValueList.php b/src/Value/CalcRuleValueList.php index 7dbd26a1..17fbe7cf 100644 --- a/src/Value/CalcRuleValueList.php +++ b/src/Value/CalcRuleValueList.php @@ -15,9 +15,11 @@ public function __construct($iLineNo = 0) } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { return $oOutputFormat->implode(' ', $this->aComponents); } diff --git a/src/Value/Color.php b/src/Value/Color.php index 5daad412..a002760b 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -160,9 +160,11 @@ public function __toString() } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { // Shorthand RGB color values if ($oOutputFormat->getRGBHashNotation() && implode('', array_keys($this->aComponents)) === 'rgb') { diff --git a/src/Value/LineName.php b/src/Value/LineName.php index e231ce38..588cb4cb 100644 --- a/src/Value/LineName.php +++ b/src/Value/LineName.php @@ -56,9 +56,11 @@ public function __toString() } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { return '[' . parent::render(OutputFormat::createCompact()) . ']'; } diff --git a/src/Value/Size.php b/src/Value/Size.php index 648c9a60..af8b3a3b 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -216,9 +216,11 @@ public function __toString() } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { $l = localeconv(); $sPoint = preg_quote($l['decimal_point'], '/'); diff --git a/src/Value/URL.php b/src/Value/URL.php index cdb911c3..92da9726 100644 --- a/src/Value/URL.php +++ b/src/Value/URL.php @@ -86,9 +86,11 @@ public function __toString() } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { return "url({$this->oURL->render($oOutputFormat)})"; } diff --git a/src/Value/ValueList.php b/src/Value/ValueList.php index a93acc7b..80b26f97 100644 --- a/src/Value/ValueList.php +++ b/src/Value/ValueList.php @@ -93,9 +93,11 @@ public function __toString() } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { return $oOutputFormat->implode( $oOutputFormat->spaceBeforeListArgumentSeparator($this->sSeparator) . $this->sSeparator From 1ad9ad933c873126858a2955a531f37600bda813 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 27 Oct 2024 00:46:38 +0200 Subject: [PATCH 38/78] [TASK] Configure PHPUnit to be more strict (#744) This is the v8.x backport of #743. --- phpunit.xml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/phpunit.xml b/phpunit.xml index 5f3dd458..249dd48b 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,6 +1,15 @@ + xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/5.7/phpunit.xsd" + beStrictAboutChangesToGlobalState="true" + beStrictAboutOutputDuringTests="true" + beStrictAboutTodoAnnotatedTests="true" + cacheResult="false" + colors="true" + convertDeprecationsToExceptions="true" + forceCoversAnnotation="true" + verbose="true" +> tests From 1dd952b0d941a703256c86d1f310d862e758f5d5 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 27 Oct 2024 01:07:16 +0200 Subject: [PATCH 39/78] [CLEANUP] Autoformat the changelog (#750) This allows autoformatting the changelog after adding new entries possible without introducing unrelated changes. This is the v8.x backport of #718. --- CHANGELOG.md | 109 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 77 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfe4b918..a3e2b3ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,13 +28,15 @@ This project adheres to [Semantic Versioning](https://semver.org/). ## 8.6.0 ### 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 -- Improve performance of Value::parseValue with many delimiters by refactoring to remove array_search() (#413) +- Improve performance of Value::parseValue with many delimiters by refactoring + to remove `array_search()` (#413) ## 8.5.2 @@ -50,7 +52,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Fixed -- Fix PHP notice caused by parsing invalid color values having less than 6 characters (#485) +- Fix PHP notice caused by parsing invalid color values having less than + 6 characters (#485) - Fix (regression) failure to parse at-rules with strict parsing (#456) ## 8.5.0 @@ -75,7 +78,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). * Support for PHP 8.x * PHPDoc annotations -* Allow usage of CSS variables inside color functions (by parsing them as regular functions) +* Allow usage of CSS variables inside color functions (by parsing them as + regular functions) * Use PSR-12 code style * *No deprecations* @@ -90,7 +94,10 @@ This project adheres to [Semantic Versioning](https://semver.org/). * Allow a file to end after an `@import` * Preserve case of CSS variables as specced * Allow identifiers to use escapes the same way as strings -* No longer use `eval` for the comparison in `getSelectorsBySpecificity`, in case it gets passed untrusted input (CVE-2020-13756). Also fixed in 8.3.1, 8.2.1, 8.1.1, 8.0.1, 7.0.4, 6.0.2, 5.2.1, 5.1.3, 5.0.9, 4.0.1, 3.0.1, 2.0.1, 1.0.1. +* No longer use `eval` for the comparison in `getSelectorsBySpecificity`, in + case it gets passed untrusted input (CVE-2020-13756). Also fixed in 8.3.1, + 8.2.1, 8.1.1, 8.0.1, 7.0.4, 6.0.2, 5.2.1, 5.1.3, 5.0.9, 4.0.1, 3.0.1, 2.0.1, + 1.0.1. * Prevent an infinite loop when parsing invalid grid line names * Remove invalid unit `vm` * Retain rule order after expanding shorthands @@ -102,11 +109,16 @@ This project adheres to [Semantic Versioning](https://semver.org/). ## 8.3.0 (2019-02-22) -* Refactor parsing logic to mostly reside in the class files whose data structure is to be parsed (this should eventually allow us to unit-test specific parts of the parsing logic individually). -* Fix error in parsing `calc` expessions when the first operand is a negative number, thanks to @raxbg. -* Support parsing CSS4 colors in hex notation with alpha values, thanks to @raxbg. +* Refactor parsing logic to mostly reside in the class files whose data + structure is to be parsed (this should eventually allow us to unit-test + specific parts of the parsing logic individually). +* Fix error in parsing `calc` expessions when the first operand is a negative + number, thanks to @raxbg. +* Support parsing CSS4 colors in hex notation with alpha values, thanks to + @raxbg. * Swallow more errors in lenient mode, thanks to @raxbg. -* Allow specifying arbitrary strings to output before and after declaration blocks, thanks to @westonruter. +* Allow specifying arbitrary strings to output before and after declaration + blocks, thanks to @westonruter. * *No backwards-incompatible changes* * *No deprecations* @@ -114,16 +126,20 @@ This project adheres to [Semantic Versioning](https://semver.org/). * Support parsing `calc()`, thanks to @raxbg. * Support parsing grid-lines, again thanks to @raxbg. -* Support parsing legacy IE filters (`progid:`) in lenient mode, thanks to @FMCorz +* Support parsing legacy IE filters (`progid:`) in lenient mode, thanks to + @FMCorz * Performance improvements parsing large files, again thanks to @FMCorz * *No backwards-incompatible changes* * *No deprecations* ## 8.1.0 (2016-07-19) -* Comments are no longer silently ignored but stored with the object with which they appear (no render support, though). Thanks to @FMCorz. -* The IE hacks using `\0` and `\9` can now be parsed (and rendered) in lenient mode. Thanks (again) to @FMCorz. -* Media queries with or without spaces before the query are parsed. Still no *real* parsing support, though. Sorry… +* Comments are no longer silently ignored but stored with the object with which + they appear (no render support, though). Thanks to @FMCorz. +* The IE hacks using `\0` and `\9` can now be parsed (and rendered) in lenient + mode. Thanks (again) to @FMCorz. +* Media queries with or without spaces before the query are parsed. Still no + *real* parsing support, though. Sorry… * PHPUnit is now listed as a dev-dependency in composer.json. * *No backwards-incompatible changes* * *No deprecations* @@ -135,7 +151,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Backwards-incompatible changes -* Unrecoverable parser errors throw an exception of type `Sabberworm\CSS\Parsing\SourceException` instead of `\Exception`. +* Unrecoverable parser errors throw an exception of type + `Sabberworm\CSS\Parsing\SourceException` instead of `\Exception`. ## 7.0.3 (2016-04-27) @@ -145,7 +162,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). ## 7.0.2 (2016-02-11) -* 150 time performance boost thanks to @[ossinkine](https://github.com/ossinkine) +* 150 time performance boost thanks + to @[ossinkine](https://github.com/ossinkine) * *No backwards-incompatible changes* * *No deprecations* @@ -162,7 +180,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Backwards-incompatible changes -* The `Sabberworm\CSS\Value\String` class has been renamed to `Sabberworm\CSS\Value\CSSString`. +* The `Sabberworm\CSS\Value\String` class has been renamed to + `Sabberworm\CSS\Value\CSSString`. ## 6.0.1 (2015-08-24) @@ -176,22 +195,27 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Deprecations -* The parse() method replaces __toString with an optional argument (instance of the OutputFormat class) +* The parse() method replaces __toString with an optional argument (instance of + the OutputFormat class) ## 5.2.0 (2014-06-30) -* Support removing a selector from a declaration block using `$oBlock->removeSelector($mSelector)` -* Introduce a specialized exception (Sabberworm\CSS\Parsing\OuputException) for exceptions during output rendering +* Support removing a selector from a declaration block using + `$oBlock->removeSelector($mSelector)` +* Introduce a specialized exception (Sabberworm\CSS\Parsing\OuputException) for + exceptions during output rendering * *No deprecations* #### Backwards-incompatible changes -* Outputting a declaration block that has no selectors throws an OuputException instead of outputting an invalid ` {…}` into the CSS document. +* Outputting a declaration block that has no selectors throws an OuputException + instead of outputting an invalid ` {…}` into the CSS document. ## 5.1.2 (2013-10-30) -* Remove the use of consumeUntil in comment parsing. This makes it possible to parse comments such as `/** Perfectly valid **/` +* Remove the use of consumeUntil in comment parsing. This makes it possible to + parse comments such as `/** Perfectly valid **/` * Add fr relative size unit * Fix some issues with HHVM * *No backwards-incompatible changes* @@ -206,13 +230,15 @@ This project adheres to [Semantic Versioning](https://semver.org/). ## 5.1.0 (2013-10-24) * Performance enhancements by Michael M Slusarz -* More rescue entry points for lenient parsing (unexpected tokens between declaration blocks and unclosed comments) +* More rescue entry points for lenient parsing (unexpected tokens between + declaration blocks and unclosed comments) * *No backwards-incompatible changes* * *No deprecations* ## 5.0.8 (2013-08-15) -* Make default settings’ multibyte parsing option dependent on whether or not the mbstring extension is actually installed. +* Make default settings’ multibyte parsing option dependent on whether or not + the mbstring extension is actually installed. * *No backwards-incompatible changes* * *No deprecations* @@ -230,7 +256,9 @@ This project adheres to [Semantic Versioning](https://semver.org/). ## 5.0.5 (2013-04-17) -* Initial support for lenient parsing (setting this parser option will catch some exceptions internally and recover the parser’s state as neatly as possible). +* Initial support for lenient parsing (setting this parser option will catch + some exceptions internally and recover the parser’s state as neatly as + possible). * *No backwards-incompatible changes* * *No deprecations* @@ -267,18 +295,22 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Backwards-incompatible changes -* `Sabberworm\CSS\Value\Color`’s `__toString` method overrides `CSSList`’s to maybe return something other than `type(value, …)` (see above). +* `Sabberworm\CSS\Value\Color`’s `__toString` method overrides `CSSList`’s to + maybe return something other than `type(value, …)` (see above). ## 4.0.0 (2013-03-19) * Support for more @-rules -* Generic interface `Sabberworm\CSS\Property\AtRule`, implemented by all @-rule classes +* Generic interface `Sabberworm\CSS\Property\AtRule`, implemented by all @-rule + classes * *No deprecations* ### Backwards-incompatible changes * `Sabberworm\CSS\RuleSet\AtRule` renamed to `Sabberworm\CSS\RuleSet\AtRuleSet` -* `Sabberworm\CSS\CSSList\MediaQuery` renamed to `Sabberworm\CSS\RuleSet\CSSList\AtRuleBlockList` with differing semantics and API (which also works for other block-list-based @-rules like `@supports`). +* `Sabberworm\CSS\CSSList\MediaQuery` renamed to + `Sabberworm\CSS\RuleSet\CSSList\AtRuleBlockList` with differing semantics and + API (which also works for other block-list-based @-rules like `@supports`). ## 3.0.0 (2013-03-06) @@ -287,10 +319,18 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Backwards-incompatible changes -* All properties (like whether or not to use `mb_`-functions, which default charset to use and – new – whether or not to be forgiving when parsing) are now encapsulated in an instance of `Sabberworm\CSS\Settings` which can be passed as the second argument to `Sabberworm\CSS\Parser->__construct()`. -* Specifying a charset as the second argument to `Sabberworm\CSS\Parser->__construct()` is no longer supported. Use `Sabberworm\CSS\Settings::create()->withDefaultCharset('some-charset')` instead. -* Setting `Sabberworm\CSS\Parser->bUseMbFunctions` has no effect. Use `Sabberworm\CSS\Settings::create()->withMultibyteSupport(true/false)` instead. -* `Sabberworm\CSS\Parser->parse()` may throw a `Sabberworm\CSS\Parsing\UnexpectedTokenException` when in strict parsing mode. +* All properties (like whether or not to use `mb_`-functions, which default + charset to use and – new – whether or not to be forgiving when parsing) are + now encapsulated in an instance of `Sabberworm\CSS\Settings` which can be + passed as the second argument to `Sabberworm\CSS\Parser->__construct()`. +* Specifying a charset as the second argument to + `Sabberworm\CSS\Parser->__construct()` is no longer supported. Use + `Sabberworm\CSS\Settings::create()->withDefaultCharset('some-charset')` + instead. +* Setting `Sabberworm\CSS\Parser->bUseMbFunctions` has no effect. Use + `Sabberworm\CSS\Settings::create()->withMultibyteSupport(true/false)` instead. +* `Sabberworm\CSS\Parser->parse()` may throw a + `Sabberworm\CSS\Parsing\UnexpectedTokenException` when in strict parsing mode. ## 2.0.0 (2013-01-29) @@ -298,8 +338,13 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Backwards-incompatible changes -* `Sabberworm\CSS\RuleSet->getRules()` returns an index-based array instead of an associative array. Use `Sabberworm\CSS\RuleSet->getRulesAssoc()` (which eliminates duplicate rules and lets the later rule of the same name win). -* `Sabberworm\CSS\RuleSet->removeRule()` works as it did before except when passed an instance of `Sabberworm\CSS\Rule\Rule`, in which case it would only remove the exact rule given instead of all the rules of the same type. To get the old behaviour, use `Sabberworm\CSS\RuleSet->removeRule($oRule->getRule()`; +* `Sabberworm\CSS\RuleSet->getRules()` returns an index-based array instead of + an associative array. Use `Sabberworm\CSS\RuleSet->getRulesAssoc()` (which + eliminates duplicate rules and lets the later rule of the same name win). +* `Sabberworm\CSS\RuleSet->removeRule()` works as it did before except when + passed an instance of `Sabberworm\CSS\Rule\Rule`, in which case it would only + remove the exact rule given instead of all the rules of the same type. To get + the old behaviour, use `Sabberworm\CSS\RuleSet->removeRule($oRule->getRule()`; ## 1.0 From cef9e7c14407b58514f0def061daa6aaf31c5bbe Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 27 Oct 2024 17:23:41 +0100 Subject: [PATCH 40/78] [BUGFIX] Fix another implictly nullable parameter (#751) This fixes compatibility with PHP 8.4. This is the last part of the v8.x backport of #643. --- CHANGELOG.md | 2 +- src/RuleSet/RuleSet.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3e2b3ca..c39c583f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Added -- Add support for PHP 8.4 (#675, #701, #746) +- Add support for PHP 8.4 (#675, #701, #746, #751) ### Changed diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index adb9be92..8cbbd34f 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -102,7 +102,7 @@ public function getLineNo() * * @return void */ - public function addRule(Rule $oRule, Rule $oSibling = null) + public function addRule(Rule $oRule, $oSibling = null) { $sRule = $oRule->getRule(); if (!isset($this->aRules[$sRule])) { From f414ff953002a9b18e3a116f5e462c56f21237cf Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 27 Oct 2024 18:38:32 +0100 Subject: [PATCH 41/78] [TASK] Prepare the 8.7.0 release (#742) --- CHANGELOG.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c39c583f..c90927cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,18 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Added +### Changed + +### Deprecated + +### Removed + +### Fixed + +## 8.7.0: Add support for PHP 8.4 + +### Added + - Add support for PHP 8.4 (#675, #701, #746, #751) ### Changed @@ -19,8 +31,6 @@ This project adheres to [Semantic Versioning](https://semver.org/). - Deprecate the expansion of shorthand properties (#719) - Deprecate `Parser::setCharset()` and `Parser::getCharset()` (#703) -### Removed - ### Fixed - Fix type errors in PHP strict mode (#695) From 6098beefbd0501d708c478af51ccd0f1ce5e275a Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 31 Oct 2024 18:21:31 +0100 Subject: [PATCH 42/78] [CLEANUP] Autoformat the code (#749) This is the v8.x backport of #748. --- src/Value/Size.php | 19 +++++++++++---- tests/CSSList/AtRuleBlockListTest.php | 22 +++++++++-------- tests/Value/SizeTest.php | 34 ++++++++++++++++++++++----- 3 files changed, 55 insertions(+), 20 deletions(-) diff --git a/src/Value/Size.php b/src/Value/Size.php index af8b3a3b..8907526a 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -20,10 +20,21 @@ class Size extends PrimitiveValue * @internal */ const ABSOLUTE_SIZE_UNITS = [ - 'px', 'pt', 'pc', - 'cm', 'mm', 'mozmm', 'in', - 'vh', 'dvh', 'svh', 'lvh', - 'vw', 'vmin', 'vmax', 'rem', + 'px', + 'pt', + 'pc', + 'cm', + 'mm', + 'mozmm', + 'in', + 'vh', + 'dvh', + 'svh', + 'lvh', + 'vw', + 'vmin', + 'vmax', + 'rem', ]; /** diff --git a/tests/CSSList/AtRuleBlockListTest.php b/tests/CSSList/AtRuleBlockListTest.php index 2e2bd046..030c9874 100644 --- a/tests/CSSList/AtRuleBlockListTest.php +++ b/tests/CSSList/AtRuleBlockListTest.php @@ -33,17 +33,19 @@ public static function provideSyntacticlyCorrectAtRule() return [ 'media print' => ['@media print { html { background: white; color: black; } }'], 'keyframes' => ['@keyframes mymove { from { top: 0px; } }'], - 'supports' => [' - @supports (display: flex) { - .flex-container > * { - text-shadow: 0 0 2px blue; - float: none; + 'supports' => [ + ' + @supports (display: flex) { + .flex-container > * { + text-shadow: 0 0 2px blue; + float: none; + } + .flex-container { + display: flex; + } } - .flex-container { - display: flex; - } - } - '], + ', + ], ]; } diff --git a/tests/Value/SizeTest.php b/tests/Value/SizeTest.php index d743087a..6470148f 100644 --- a/tests/Value/SizeTest.php +++ b/tests/Value/SizeTest.php @@ -18,12 +18,34 @@ final class SizeTest extends TestCase public static function provideUnit() { $units = [ - 'px', 'pt', 'pc', - 'cm', 'mm', 'mozmm', 'in', - 'vh', 'dvh', 'svh', 'lvh', - 'vw', 'vmin', 'vmax', 'rem', - '%', 'em', 'ex', 'ch', 'fr', - 'deg', 'grad', 'rad', 's', 'ms', 'turn', 'Hz', 'kHz', + 'px', + 'pt', + 'pc', + 'cm', + 'mm', + 'mozmm', + 'in', + 'vh', + 'dvh', + 'svh', + 'lvh', + 'vw', + 'vmin', + 'vmax', + 'rem', + '%', + 'em', + 'ex', + 'ch', + 'fr', + 'deg', + 'grad', + 'rad', + 's', + 'ms', + 'turn', + 'Hz', + 'kHz', ]; return \array_combine( From a8ba64914e24f5c3d2c8d22fd13fc725fbcdd714 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 19 Jan 2025 20:32:20 +0100 Subject: [PATCH 43/78] [TASK] Update the development tools (#796) --- .github/workflows/ci.yml | 2 +- .phive/phars.xml | 8 ++++---- composer.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5b38b294..815ccb43 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -121,7 +121,7 @@ jobs: - name: Install development tools run: | - phive --no-progress install --trust-gpg-keys BBAB5DF0A0D6672989CF1869E82B2FB314E9906E,A972B9ABB95D0B760B51442231C7E470E2138192,D32680D5957DC7116BE29C14CF1A108D0E7AE720 + phive --no-progress install --trust-gpg-keys 0FDE18AE1D09E19F60F6B1CBC00543248C87FB13,BBAB5DF0A0D6672989CF1869E82B2FB314E9906E,689DAD778FF08760E046228BA978220305CD5C32,CA7C2C7A30C8E8E1274A847651C67305FFC2E5C0 - name: Run Command run: composer ci:php:${{ matrix.command }} diff --git a/.phive/phars.xml b/.phive/phars.xml index d353fbf9..875c1d19 100644 --- a/.phive/phars.xml +++ b/.phive/phars.xml @@ -1,7 +1,7 @@ - - - - + + + + diff --git a/composer.json b/composer.json index 685db65e..e562ed6d 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "ext-iconv": "*" }, "require-dev": { - "phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.40" + "phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.41" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" From 6f37a7a708a344319834f1d3740b695fc51db5fb Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 20 Jan 2025 00:25:53 +0100 Subject: [PATCH 44/78] [BUGFIX] Parse `@font-face` src property as comma-delimited list (#794) Fixes #789. Also adds an initial `TestCase` for `Rule/Rule`. This is the 8.x backport of #790. --- CHANGELOG.md | 2 ++ src/Rule/Rule.php | 14 ++++++-- tests/Unit/Rule/RuleTest.php | 62 ++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 tests/Unit/Rule/RuleTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index c90927cc..969e437d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Fixed +- Parse `@font-face` `src` property as comma-delimited list (#794) + ## 8.7.0: Add support for PHP 8.4 ### Added diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index c1bad65e..f10dbe66 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -114,16 +114,26 @@ public static function parse(ParserState $oParserState) } /** + * Returns a list of delimiters (or separators). + * The first item is the innermost separator (or, put another way, the highest-precedence operator). + * The sequence continues to the outermost separator (or lowest-precedence operator). + * * @param string $sRule * - * @return array + * @return list */ private static function listDelimiterForRule($sRule) { if (preg_match('/^font($|-)/', $sRule)) { return [',', '/', ' ']; } - return [',', ' ', '/']; + + switch ($sRule) { + case 'src': + return [' ', ',']; + default: + return [',', ' ', '/']; + } } /** diff --git a/tests/Unit/Rule/RuleTest.php b/tests/Unit/Rule/RuleTest.php new file mode 100644 index 00000000..0364e1ea --- /dev/null +++ b/tests/Unit/Rule/RuleTest.php @@ -0,0 +1,62 @@ +}> + */ + public static function provideRulesAndExpectedParsedValueListTypes() + { + return [ + 'src (e.g. in @font-face)' => [ + " + src: url('../fonts/open-sans-italic-300.woff2') format('woff2'), + url('../fonts/open-sans-italic-300.ttf') format('truetype'); + ", + [RuleValueList::class, RuleValueList::class], + ], + ]; + } + + /** + * @test + * + * @param string $rule + * @param list $expectedTypeClassnames + * + * @dataProvider provideRulesAndExpectedParsedValueListTypes + */ + public function parsesValuesIntoExpectedTypeList($rule, array $expectedTypeClassnames) + { + $subject = Rule::parse(new ParserState($rule, Settings::create())); + + $value = $subject->getValue(); + self::assertInstanceOf(ValueList::class, $value); + + $actualClassnames = \array_map( + /** + * @param Value|string $component + * @return string + */ + static function ($component) { + return \is_string($component) ? 'string' : \get_class($component); + }, + $value->getListComponents() + ); + + self::assertSame($expectedTypeClassnames, $actualClassnames); + } +} From 6f30dcb4a630fabad48b2b01998c0dcf450fa3c9 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 27 Jan 2025 19:40:18 +0100 Subject: [PATCH 45/78] [TASK] Drop the PHIVE-installed development tools (#832) It has turned out that our current set of PHIVE-installed development tools for the V8.x maintenance branch is no longer installable due to issues with the GPG keys used for signing the PHARs. As the maintenance branch has very little activity anyway now, relying on PHP linting and the unit tests should suffice, saving us the hassle with PHIVE on that branch. (We're still keeping some `.gitignore` entries related to the tools to make switching branches easier.) Closes #831 Closes #830 --- .github/workflows/ci.yml | 52 ------------------------------------ .phive/phars.xml | 7 ----- composer.json | 31 --------------------- config/php-cs-fixer.php | 34 ----------------------- config/phpcs.xml | 17 ------------ config/phpstan-baseline.neon | 12 --------- config/phpstan.neon | 18 ------------- 7 files changed, 171 deletions(-) delete mode 100644 .phive/phars.xml delete mode 100644 config/php-cs-fixer.php delete mode 100644 config/phpcs.xml delete mode 100644 config/phpstan-baseline.neon delete mode 100644 config/phpstan.neon diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 815ccb43..865d202d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,55 +73,3 @@ jobs: - name: Run Tests run: ./vendor/bin/phpunit - - static-analysis: - name: Static Analysis - - runs-on: ubuntu-22.04 - - needs: [ php-lint ] - - strategy: - fail-fast: false - matrix: - include: - - command: sniffer - php-version: '7.4' - - command: fixer - php-version: '7.4' - - command: stan - php-version: '7.4' - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Install PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-version }} - tools: "composer:v2, phive" - coverage: none - - - name: Show the Composer configuration - run: composer config --global --list - - - name: Cache dependencies installed with composer - uses: actions/cache@v4 - with: - path: ~/.cache/composer - key: php${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.json') }} - restore-keys: | - php${{ matrix.php-version }}-composer- - - - name: Install Composer dependencies - run: | - composer update --with-dependencies --no-progress; - composer show; - - - name: Install development tools - run: | - phive --no-progress install --trust-gpg-keys 0FDE18AE1D09E19F60F6B1CBC00543248C87FB13,BBAB5DF0A0D6672989CF1869E82B2FB314E9906E,689DAD778FF08760E046228BA978220305CD5C32,CA7C2C7A30C8E8E1274A847651C67305FFC2E5C0 - - - name: Run Command - run: composer ci:php:${{ matrix.command }} diff --git a/.phive/phars.xml b/.phive/phars.xml deleted file mode 100644 index 875c1d19..00000000 --- a/.phive/phars.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/composer.json b/composer.json index e562ed6d..acf650a2 100644 --- a/composer.json +++ b/composer.json @@ -46,36 +46,5 @@ "branch-alias": { "dev-main": "9.0.x-dev" } - }, - "scripts": { - "ci": [ - "@ci:static" - ], - "ci:php:fixer": "@php ./.phive/php-cs-fixer.phar --config=config/php-cs-fixer.php fix --dry-run -v --show-progress=dots bin src tests", - "ci:php:sniffer": "@php ./.phive/phpcs.phar --standard=config/phpcs.xml bin src tests", - "ci:php:stan": "@php ./.phive/phpstan.phar --configuration=config/phpstan.neon", - "ci:static": [ - "@ci:php:fixer", - "@ci:php:sniffer", - "@ci:php:stan" - ], - "fix:php": [ - "@fix:php:fixer", - "@fix:php:sniffer" - ], - "fix:php:fixer": "@php ./.phive/php-cs-fixer.phar --config=config/php-cs-fixer.php fix bin src tests", - "fix:php:sniffer": "@php ./.phive/phpcbf.phar --standard=config/phpcs.xml bin src tests", - "phpstan:baseline": "@php ./.phive/phpstan.phar --configuration=config/phpstan.neon --generate-baseline=config/phpstan-baseline.neon" - }, - "scripts-descriptions": { - "ci": "Runs all dynamic and static code checks (i.e. currently, only the static checks).", - "ci:php:fixer": "Checks the code style with PHP CS Fixer.", - "ci:php:sniffer": "Checks the code style with PHP_CodeSniffer.", - "ci:php:stan": "Checks the types with PHPStan.", - "ci:static": "Runs all static code analysis checks for the code.", - "fix:php": "Autofixes all autofixable issues in the PHP code.", - "fix:php:fixer": "Fixes autofixable issues found by PHP CS Fixer.", - "fix:php:sniffer": "Fixes autofixable issues found by PHP_CodeSniffer.", - "phpstand:baseline": "Updates the PHPStan baseline file to match the code." } } diff --git a/config/php-cs-fixer.php b/config/php-cs-fixer.php deleted file mode 100644 index 88a9a692..00000000 --- a/config/php-cs-fixer.php +++ /dev/null @@ -1,34 +0,0 @@ -setRiskyAllowed(true) - ->setRules( - [ - '@PSR12' => true, - // Disable constant visibility from the PSR12 rule set as this would break compatibility with PHP < 7.1. - 'visibility_required' => ['elements' => ['property', 'method']], - - '@PHPUnit50Migration:risky' => true, - '@PHPUnit52Migration:risky' => true, - '@PHPUnit54Migration:risky' => true, - '@PHPUnit55Migration:risky' => true, - '@PHPUnit56Migration:risky' => true, - '@PHPUnit57Migration:risky' => true, - - 'php_unit_construct' => true, - 'php_unit_dedicate_assert' => ['target' => '5.6'], - 'php_unit_expectation' => ['target' => '5.6'], - 'php_unit_fqcn_annotation' => true, - 'php_unit_method_casing' => true, - 'php_unit_mock' => ['target' => '5.5'], - 'php_unit_mock_short_will_return' => true, - 'php_unit_namespaced' => ['target' => '5.7'], - 'php_unit_set_up_tear_down_visibility' => true, - 'php_unit_test_annotation' => ['style' => 'annotation'], - 'php_unit_test_case_static_method_calls' => ['call_type' => 'self'], - ] - ); diff --git a/config/phpcs.xml b/config/phpcs.xml deleted file mode 100644 index 14473bb2..00000000 --- a/config/phpcs.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - This standard requires PHP_CodeSniffer >= 3.6.0. - - - - - - - - - - - - - diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon deleted file mode 100644 index 82fcb3f4..00000000 --- a/config/phpstan-baseline.neon +++ /dev/null @@ -1,12 +0,0 @@ -parameters: - ignoreErrors: - - - message: "#^Call to an undefined method Sabberworm\\\\CSS\\\\OutputFormat\\:\\:setIndentation\\(\\)\\.$#" - count: 2 - path: ../src/OutputFormat.php - - - - message: "#^Class Sabberworm\\\\CSS\\\\Value\\\\Size constructor invoked with 5 parameters, 1\\-4 required\\.$#" - count: 2 - path: ../src/RuleSet/DeclarationBlock.php - diff --git a/config/phpstan.neon b/config/phpstan.neon deleted file mode 100644 index 3d7611a6..00000000 --- a/config/phpstan.neon +++ /dev/null @@ -1,18 +0,0 @@ -includes: - - phpstan-baseline.neon - -parameters: - parallel: - # Don't be overly greedy on machines with more CPU's to be a good neighbor especially on CI - maximumNumberOfProcesses: 5 - - level: 1 - - scanDirectories: - - %currentWorkingDirectory%/bin/ - - %currentWorkingDirectory%/src/ - - %currentWorkingDirectory%/tests/ - paths: - - %currentWorkingDirectory%/bin/ - - %currentWorkingDirectory%/src/ - - %currentWorkingDirectory%/tests/ From b65d3646e975bafc767dcf7c44c2704932cfba57 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 27 Jan 2025 19:49:34 +0100 Subject: [PATCH 46/78] [BUGFIX] Create `Size` with correct types in `expandBackgroundShorthand` (#828) This is the v8.x backport of #814. --- CHANGELOG.md | 1 + src/RuleSet/DeclarationBlock.php | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 969e437d..538b5f03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Fixed +- Create `Size` with correct types in `expandBackgroundShorthand` (#814) - Parse `@font-face` `src` property as comma-delimited list (#794) ## 8.7.0: Add support for PHP 8.4 diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 8a1da14e..be22760b 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -432,8 +432,8 @@ public function expandBackgroundShorthand() 'background-repeat' => ['repeat'], 'background-attachment' => ['scroll'], 'background-position' => [ - new Size(0, '%', null, false, $this->iLineNo), - new Size(0, '%', null, false, $this->iLineNo), + new Size(0, '%', false, $this->iLineNo), + new Size(0, '%', false, $this->iLineNo), ], ]; $mRuleValue = $oRule->getValue(); From 35e48cd93d5486116efe18f0451b897cec509d44 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 2 Feb 2025 18:51:09 +0100 Subject: [PATCH 47/78] [TASK] Deprecate `OutputFormat::level()` (#870) This method is redundant with the (magic) getter `Outputformat::getIndentationLevel()`, which follows the common accessor naming conventions. --- CHANGELOG.md | 2 ++ src/OutputFormat.php | 2 ++ src/OutputFormatter.php | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 538b5f03..6d7ac5d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Deprecated +- Deprecate `OutputFormat::level()` (#870) + ### Removed ### Fixed diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 96f26e14..2ce257fb 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -297,6 +297,8 @@ public function getFormatter() /** * @return int + * + * @deprecated #869 since version V8.8.0, will be removed in V9.0.0. Use `getIndentationLevel()` instead. */ public function level() { diff --git a/src/OutputFormatter.php b/src/OutputFormatter.php index 501d15da..7af3af05 100644 --- a/src/OutputFormatter.php +++ b/src/OutputFormatter.php @@ -250,6 +250,6 @@ private function prepareSpace($sSpaceString) */ private function indent() { - return str_repeat($this->oFormat->sIndentation, $this->oFormat->level()); + return str_repeat($this->oFormat->sIndentation, $this->oFormat->getIndentationLevel()); } } From a806d0a34a7e78088362db02dc62f3965877c29f Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Fri, 7 Feb 2025 09:13:17 +0000 Subject: [PATCH 48/78] [CLEANUP] Add separate `OutputFormat` properties for arrays (#883) `SpaceBeforeListArgumentSeparators` and `SpaceAfterListArgumentSeparators` are added to allow for different spacing for different separators. `SpaceBeforeListArgumentSeparator` and `SpaceAfterListArgumentSeparator` will specify the default spacing. Setting these as an array is deprecated. This is the backport of #880. --- CHANGELOG.md | 3 +++ src/OutputFormat.php | 30 ++++++++++++++++++++++++++---- src/OutputFormatter.php | 10 ++++++++++ tests/OutputFormatTest.php | 27 ++++++++++++++++++++++++++- 4 files changed, 65 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d7ac5d5..6a32053c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,13 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Added +- `OutputFormat` properties for space around specific list separators (#880) + ### Changed ### Deprecated +- `OutputFormat` properties for space around list separators as an array (#880) - Deprecate `OutputFormat::level()` (#870) ### Removed diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 2ce257fb..366fca3f 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -96,17 +96,39 @@ class OutputFormat public $sSpaceAfterSelectorSeparator = ' '; /** - * This is what’s printed after the comma of value lists + * This is what’s inserted before the separator in value lists, by default. * - * @var string + * `array` is deprecated in version 8.8.0, and will be removed in version 9.0.0. + * To set the spacing for specific separators, use {@see $aSpaceBeforeListArgumentSeparators} instead. + * + * @var string|array */ public $sSpaceBeforeListArgumentSeparator = ''; /** - * @var string + * Keys are separators (e.g. `,`). Values are the space sequence to insert, or an empty string. + * + * @var array + */ + public $aSpaceBeforeListArgumentSeparators = []; + + /** + * This is what’s inserted after the separator in value lists, by default. + * + * `array` is deprecated in version 8.8.0, and will be removed in version 9.0.0. + * To set the spacing for specific separators, use {@see $aSpaceAfterListArgumentSeparators} instead. + * + * @var string|array */ public $sSpaceAfterListArgumentSeparator = ''; + /** + * Keys are separators (e.g. `,`). Values are the space sequence to insert, or an empty string. + * + * @var array + */ + public $aSpaceAfterListArgumentSeparators = []; + /** * @var string */ @@ -343,7 +365,7 @@ public static function createPretty() $format->set('Space*Rules', "\n") ->set('Space*Blocks', "\n") ->setSpaceBetweenBlocks("\n\n") - ->set('SpaceAfterListArgumentSeparator', ['default' => '', ',' => ' ']) + ->set('SpaceAfterListArgumentSeparators', [',' => ' ']) ->setRenderComments(true); return $format; } diff --git a/src/OutputFormatter.php b/src/OutputFormatter.php index 7af3af05..3844383d 100644 --- a/src/OutputFormatter.php +++ b/src/OutputFormatter.php @@ -117,6 +117,11 @@ public function spaceAfterSelectorSeparator() */ public function spaceBeforeListArgumentSeparator($sSeparator) { + $spaceForSeparator = $this->oFormat->getSpaceBeforeListArgumentSeparators(); + if (isset($spaceForSeparator[$sSeparator])) { + return $spaceForSeparator[$sSeparator]; + } + return $this->space('BeforeListArgumentSeparator', $sSeparator); } @@ -127,6 +132,11 @@ public function spaceBeforeListArgumentSeparator($sSeparator) */ public function spaceAfterListArgumentSeparator($sSeparator) { + $spaceForSeparator = $this->oFormat->getSpaceAfterListArgumentSeparators(); + if (isset($spaceForSeparator[$sSeparator])) { + return $spaceForSeparator[$sSeparator]; + } + return $this->space('AfterListArgumentSeparator', $sSeparator); } diff --git a/tests/OutputFormatTest.php b/tests/OutputFormatTest.php index ff543a9c..2fb6df99 100644 --- a/tests/OutputFormatTest.php +++ b/tests/OutputFormatTest.php @@ -104,8 +104,11 @@ public function spaceAfterListArgumentSeparator() /** * @test + * + * @deprecated since version 8.8.0; will be removed in version 9.0. + * Use `setSpaceAfterListArgumentSeparators()` to set different spacing per separator. */ - public function spaceAfterListArgumentSeparatorComplex() + public function spaceAfterListArgumentSeparatorComplexDeprecated() { $this->setUpTestcase(); @@ -121,6 +124,28 @@ public function spaceAfterListArgumentSeparatorComplex() ); } + /** + * @test + */ + public function spaceAfterListArgumentSeparatorComplex() + { + $this->setUpTestcase(); + + self::assertSame( + '.main, .test {font: italic normal bold 16px/1.2 "Helvetica", Verdana, sans-serif;background: white;}' + . "\n@media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}", + $this->oDocument->render( + OutputFormat::create() + ->setSpaceAfterListArgumentSeparator(' ') + ->setSpaceAfterListArgumentSeparators([ + ',' => "\t", + '/' => '', + ' ' => '', + ]) + ) + ); + } + /** * @test */ From 2783ddeebb1bf7242c1998de3ba72077f570b3f8 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 9 Feb 2025 15:33:42 +0100 Subject: [PATCH 49/78] [TASK] Make all non-private properties `@internal` (#889) This communicates clearly that the properties may be removed, renamed or made `private` at any point, and that they should not be accessed directly, but using the accessors instead. Also add a type annotation that was missing. Fixes #881 This is the V8.x backport of #886. --- CHANGELOG.md | 2 ++ src/CSSList/CSSList.php | 6 ++++ src/Comment/Comment.php | 4 +++ src/OutputFormat.php | 52 +++++++++++++++++++++++++++++++++++ src/Property/CSSNamespace.php | 2 ++ src/Property/Charset.php | 4 +++ src/Property/Import.php | 4 +++ src/Rule/Rule.php | 4 +++ src/RuleSet/RuleSet.php | 4 +++ src/Settings.php | 6 ++++ src/Value/CSSFunction.php | 2 ++ src/Value/Value.php | 2 ++ src/Value/ValueList.php | 4 +++ 13 files changed, 96 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a32053c..9eda84e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Changed +- Make all non-private properties `@internal` (#886) + ### Deprecated - `OutputFormat` properties for space around list separators as an array (#880) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 4bb37589..d0a8bbbc 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -33,16 +33,22 @@ abstract class CSSList implements Renderable, Commentable { /** * @var array + * + * @internal since 8.8.0 */ protected $aComments; /** * @var array + * + * @internal since 8.8.0 */ protected $aContents; /** * @var int + * + * @internal since 8.8.0 */ protected $iLineNo; diff --git a/src/Comment/Comment.php b/src/Comment/Comment.php index e6ffaaf7..8c02f3de 100644 --- a/src/Comment/Comment.php +++ b/src/Comment/Comment.php @@ -9,11 +9,15 @@ class Comment implements Renderable { /** * @var int + * + * @internal since 8.8.0 */ protected $iLineNo; /** * @var string + * + * @internal since 8.8.0 */ protected $sComment; diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 366fca3f..2101c31f 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -14,6 +14,8 @@ class OutputFormat * Value format: `"` means double-quote, `'` means single-quote * * @var string + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $sStringQuotingType = '"'; @@ -21,6 +23,8 @@ class OutputFormat * Output RGB colors in hash notation if possible * * @var string + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $bRGBHashNotation = true; @@ -30,6 +34,8 @@ class OutputFormat * Semicolon after the last rule of a declaration block can be omitted. To do that, set this false. * * @var bool + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $bSemicolonAfterLastRule = true; @@ -38,36 +44,52 @@ class OutputFormat * Note that these strings are not sanity-checked: the value should only consist of whitespace * Any newline character will be indented according to the current level. * The triples (After, Before, Between) can be set using a wildcard (e.g. `$oFormat->set('Space*Rules', "\n");`) + * + * @var string + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $sSpaceAfterRuleName = ' '; /** * @var string + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $sSpaceBeforeRules = ''; /** * @var string + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $sSpaceAfterRules = ''; /** * @var string + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $sSpaceBetweenRules = ''; /** * @var string + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $sSpaceBeforeBlocks = ''; /** * @var string + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $sSpaceAfterBlocks = ''; /** * @var string + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $sSpaceBetweenBlocks = "\n"; @@ -75,11 +97,15 @@ class OutputFormat * Content injected in and around at-rule blocks. * * @var string + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $sBeforeAtRuleBlock = ''; /** * @var string + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $sAfterAtRuleBlock = ''; @@ -87,11 +113,15 @@ class OutputFormat * This is what’s printed before and after the comma if a declaration block contains multiple selectors. * * @var string + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $sSpaceBeforeSelectorSeparator = ''; /** * @var string + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $sSpaceAfterSelectorSeparator = ' '; @@ -102,6 +132,8 @@ class OutputFormat * To set the spacing for specific separators, use {@see $aSpaceBeforeListArgumentSeparators} instead. * * @var string|array + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $sSpaceBeforeListArgumentSeparator = ''; @@ -109,6 +141,8 @@ class OutputFormat * Keys are separators (e.g. `,`). Values are the space sequence to insert, or an empty string. * * @var array + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $aSpaceBeforeListArgumentSeparators = []; @@ -119,6 +153,8 @@ class OutputFormat * To set the spacing for specific separators, use {@see $aSpaceAfterListArgumentSeparators} instead. * * @var string|array + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $sSpaceAfterListArgumentSeparator = ''; @@ -126,11 +162,15 @@ class OutputFormat * Keys are separators (e.g. `,`). Values are the space sequence to insert, or an empty string. * * @var array + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $aSpaceAfterListArgumentSeparators = []; /** * @var string + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $sSpaceBeforeOpeningBrace = ' '; @@ -138,16 +178,22 @@ class OutputFormat * Content injected in and around declaration blocks. * * @var string + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $sBeforeDeclarationBlock = ''; /** * @var string + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $sAfterDeclarationBlockSelectors = ''; /** * @var string + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $sAfterDeclarationBlock = ''; @@ -155,6 +201,8 @@ class OutputFormat * Indentation character(s) per level. Only applicable if newlines are used in any of the spacing settings. * * @var string + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $sIndentation = "\t"; @@ -162,6 +210,8 @@ class OutputFormat * Output exceptions. * * @var bool + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $bIgnoreExceptions = false; @@ -169,6 +219,8 @@ class OutputFormat * Render comments for lists and RuleSets * * @var bool + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $bRenderComments = false; diff --git a/src/Property/CSSNamespace.php b/src/Property/CSSNamespace.php index d1bac4f7..f39c2877 100644 --- a/src/Property/CSSNamespace.php +++ b/src/Property/CSSNamespace.php @@ -27,6 +27,8 @@ class CSSNamespace implements AtRule /** * @var array + * + * @internal since 8.8.0 */ protected $aComments; diff --git a/src/Property/Charset.php b/src/Property/Charset.php index 870380e3..5e25bd4e 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -23,11 +23,15 @@ class Charset implements AtRule /** * @var int + * + * @internal since 8.8.0 */ protected $iLineNo; /** * @var array + * + * @internal since 8.8.0 */ protected $aComments; diff --git a/src/Property/Import.php b/src/Property/Import.php index 2a7cad54..9b40e304 100644 --- a/src/Property/Import.php +++ b/src/Property/Import.php @@ -23,11 +23,15 @@ class Import implements AtRule /** * @var int + * + * @internal since 8.8.0 */ protected $iLineNo; /** * @var array + * + * @internal since 8.8.0 */ protected $aComments; diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index f10dbe66..ae3c595f 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -46,11 +46,15 @@ class Rule implements Renderable, Commentable /** * @var int + * + * @internal since 8.8.0 */ protected $iColNo; /** * @var array + * + * @internal since 8.8.0 */ protected $aComments; diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 8cbbd34f..e90694ae 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -29,11 +29,15 @@ abstract class RuleSet implements Renderable, Commentable /** * @var int + * + * @internal since 8.8.0 */ protected $iLineNo; /** * @var array + * + * @internal since 8.8.0 */ protected $aComments; diff --git a/src/Settings.php b/src/Settings.php index 79d99803..8d4bd468 100644 --- a/src/Settings.php +++ b/src/Settings.php @@ -16,6 +16,8 @@ class Settings * and `mb_strpos` functions. Otherwise, the normal (ASCII-Only) functions will be used. * * @var bool + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $bMultibyteSupport; @@ -23,6 +25,8 @@ class Settings * The default charset for the CSS if no `@charset` declaration is found. Defaults to utf-8. * * @var string + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $sDefaultCharset = 'utf-8'; @@ -30,6 +34,8 @@ class Settings * Whether the parser silently ignore invalid rules instead of choking on them. * * @var bool + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $bLenientParsing = true; diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index 82ffc48c..14992aa8 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -13,6 +13,8 @@ class CSSFunction extends ValueList { /** * @var string + * + * @internal since 8.8.0 */ protected $sName; diff --git a/src/Value/Value.php b/src/Value/Value.php index 4b174db0..6232d126 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -16,6 +16,8 @@ abstract class Value implements Renderable { /** * @var int + * + * @internal since 8.8.0 */ protected $iLineNo; diff --git a/src/Value/ValueList.php b/src/Value/ValueList.php index 80b26f97..9ae3da99 100644 --- a/src/Value/ValueList.php +++ b/src/Value/ValueList.php @@ -14,11 +14,15 @@ abstract class ValueList extends Value { /** * @var array + * + * @internal since 8.8.0 */ protected $aComponents; /** * @var string + * + * @internal since 8.8.0 */ protected $sSeparator; From 980e00d50aae2c0ea47f0e12c00a034045ff8478 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 11 Feb 2025 03:28:52 +0100 Subject: [PATCH 50/78] [BUGFIX] Add missing imports for `@throws` annotations (#900) This is the V8.x backport of #897. --- src/Value/CSSFunction.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index 14992aa8..c7024ea5 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -4,6 +4,9 @@ use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Parsing\ParserState; +use Sabberworm\CSS\Parsing\SourceException; +use Sabberworm\CSS\Parsing\UnexpectedEOFException; +use Sabberworm\CSS\Parsing\UnexpectedTokenException; /** * A `CSSFunction` represents a special kind of value that also contains a function name and where the values are the From b407f21f90318a6679624fe92de688edca8c2790 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 11 Feb 2025 20:01:46 +0100 Subject: [PATCH 51/78] [TASK] Mark `OutputFormat::nextLevel()` as `@internal` (#905) This method is used internally for rendering the CSS, and it is not intended to be called from outside this library. This is the V8.x backport of #901. --- CHANGELOG.md | 1 + src/OutputFormat.php | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9eda84e0..08eb305a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Changed +- Mark `OutputFormat::nextLevel()` as `@internal` (#901) - Make all non-private properties `@internal` (#886) ### Deprecated diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 2101c31f..0d194265 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -339,6 +339,8 @@ public function indentWithSpaces($iNumber = 2) /** * @return OutputFormat + * + * @internal since V8.8.0 */ public function nextLevel() { From c2254ef400f025b8d8033410db648ce48b9a9299 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 11 Feb 2025 20:02:20 +0100 Subject: [PATCH 52/78] [BUGFIX] Fix an incorrect type annotation in `OutputFormat` (#906) This is the V8.x backport of #902. --- src/OutputFormat.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 0d194265..76a4ab9c 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -22,7 +22,7 @@ class OutputFormat /** * Output RGB colors in hash notation if possible * - * @var string + * @var bool * * @internal since 8.8.0, will be made private in 9.0.0 */ From 769e1dfc99841e045b44d7dd9854246e918b58b3 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 13 Feb 2025 23:03:24 +0100 Subject: [PATCH 53/78] [TASK] Mark many parsing-related methods as `@internal` (#913) The only parsing method that is expected to be called from outside of this library is `Parser::parse()`. This is the V8.x backport of #908. --- CHANGELOG.md | 1 + src/CSSList/CSSList.php | 2 ++ src/CSSList/Document.php | 2 ++ src/Parsing/ParserState.php | 4 ++++ src/Rule/Rule.php | 2 ++ src/RuleSet/DeclarationBlock.php | 2 ++ src/RuleSet/RuleSet.php | 2 ++ src/Value/CSSFunction.php | 2 ++ src/Value/CSSString.php | 2 ++ src/Value/CalcFunction.php | 2 ++ src/Value/Color.php | 2 ++ src/Value/LineName.php | 2 ++ src/Value/Size.php | 2 ++ src/Value/URL.php | 2 ++ src/Value/Value.php | 6 ++++++ 15 files changed, 35 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08eb305a..e0b95a16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Changed +- Mark parsing-related methods of most CSS elements as `@internal` (#90) - Mark `OutputFormat::nextLevel()` as `@internal` (#901) - Make all non-private properties `@internal` (#886) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index d0a8bbbc..5b8f41d5 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -67,6 +67,8 @@ public function __construct($iLineNo = 0) * * @throws UnexpectedTokenException * @throws SourceException + * + * @internal since V8.8.0 */ public static function parseList(ParserState $oParserState, CSSList $oList) { diff --git a/src/CSSList/Document.php b/src/CSSList/Document.php index 7d9c6ec0..f74c8964 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -28,6 +28,8 @@ public function __construct($iLineNo = 0) * @return Document * * @throws SourceException + * + * @internal since V8.8.0 */ public static function parse(ParserState $oParserState) { diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 2427c5e1..574f60f3 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -141,6 +141,8 @@ public function setPosition($iPosition) * @return string * * @throws UnexpectedTokenException + * + * @internal since V8.8.0 */ public function parseIdentifier($bIgnoreCase = true) { @@ -172,6 +174,8 @@ public function parseIdentifier($bIgnoreCase = true) * * @throws UnexpectedEOFException * @throws UnexpectedTokenException + * + * @internal since V8.8.0 */ public function parseCharacter($bIsForIdentifier) { diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index ae3c595f..fe706191 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -79,6 +79,8 @@ public function __construct($sRule, $iLineNo = 0, $iColNo = 0) * * @throws UnexpectedEOFException * @throws UnexpectedTokenException + * + * @internal since V8.8.0 */ public static function parse(ParserState $oParserState) { diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index be22760b..60d0b0c4 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -49,6 +49,8 @@ public function __construct($iLineNo = 0) * * @throws UnexpectedTokenException * @throws UnexpectedEOFException + * + * @internal since V8.8.0 */ public static function parse(ParserState $oParserState, $oList = null) { diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index e90694ae..0cf7ac95 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -56,6 +56,8 @@ public function __construct($iLineNo = 0) * * @throws UnexpectedTokenException * @throws UnexpectedEOFException + * + * @internal since V8.8.0 */ public static function parseRuleSet(ParserState $oParserState, RuleSet $oRuleSet) { diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index c7024ea5..de8e558f 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -47,6 +47,8 @@ public function __construct($sName, $aArguments, $sSeparator = ',', $iLineNo = 0 * @throws SourceException * @throws UnexpectedEOFException * @throws UnexpectedTokenException + * + * @internal since V8.8.0 */ public static function parse(ParserState $oParserState, $bIgnoreCase = false) { diff --git a/src/Value/CSSString.php b/src/Value/CSSString.php index c19b238f..0251dc7c 100644 --- a/src/Value/CSSString.php +++ b/src/Value/CSSString.php @@ -36,6 +36,8 @@ public function __construct($sString, $iLineNo = 0) * @throws SourceException * @throws UnexpectedEOFException * @throws UnexpectedTokenException + * + * @internal since V8.8.0 */ public static function parse(ParserState $oParserState) { diff --git a/src/Value/CalcFunction.php b/src/Value/CalcFunction.php index a3db2710..70f2f458 100644 --- a/src/Value/CalcFunction.php +++ b/src/Value/CalcFunction.php @@ -30,6 +30,8 @@ class CalcFunction extends CSSFunction * * @throws UnexpectedTokenException * @throws UnexpectedEOFException + * + * @internal since V8.8.0 */ public static function parse(ParserState $oParserState, $bIgnoreCase = false) { diff --git a/src/Value/Color.php b/src/Value/Color.php index a002760b..fbbf11a1 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -30,6 +30,8 @@ public function __construct(array $aColor, $iLineNo = 0) * * @throws UnexpectedEOFException * @throws UnexpectedTokenException + * + * @internal since V8.8.0 */ public static function parse(ParserState $oParserState, $bIgnoreCase = false) { diff --git a/src/Value/LineName.php b/src/Value/LineName.php index 588cb4cb..5b59fce5 100644 --- a/src/Value/LineName.php +++ b/src/Value/LineName.php @@ -23,6 +23,8 @@ public function __construct(array $aComponents = [], $iLineNo = 0) * * @throws UnexpectedTokenException * @throws UnexpectedEOFException + * + * @internal since V8.8.0 */ public static function parse(ParserState $oParserState) { diff --git a/src/Value/Size.php b/src/Value/Size.php index 8907526a..2e45d272 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -92,6 +92,8 @@ public function __construct($fSize, $sUnit = null, $bIsColorComponent = false, $ * * @throws UnexpectedEOFException * @throws UnexpectedTokenException + * + * @internal since V8.8.0 */ public static function parse(ParserState $oParserState, $bIsColorComponent = false) { diff --git a/src/Value/URL.php b/src/Value/URL.php index 92da9726..7d5bd8e8 100644 --- a/src/Value/URL.php +++ b/src/Value/URL.php @@ -33,6 +33,8 @@ public function __construct(CSSString $oURL, $iLineNo = 0) * @throws SourceException * @throws UnexpectedEOFException * @throws UnexpectedTokenException + * + * @internal since V8.8.0 */ public static function parse(ParserState $oParserState) { diff --git a/src/Value/Value.php b/src/Value/Value.php index 6232d126..3816289d 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -36,6 +36,8 @@ public function __construct($iLineNo = 0) * * @throws UnexpectedTokenException * @throws UnexpectedEOFException + * + * @internal since V8.8.0 */ public static function parseValue(ParserState $oParserState, array $aListDelimiters = []) { @@ -112,6 +114,8 @@ public static function parseValue(ParserState $oParserState, array $aListDelimit * * @throws UnexpectedEOFException * @throws UnexpectedTokenException + * + * @internal since V8.8.0 */ public static function parseIdentifierOrFunction(ParserState $oParserState, $bIgnoreCase = false) { @@ -142,6 +146,8 @@ public static function parseIdentifierOrFunction(ParserState $oParserState, $bIg * @throws UnexpectedEOFException * @throws UnexpectedTokenException * @throws SourceException + * + * @internal since V8.8.0 */ public static function parsePrimitiveValue(ParserState $oParserState) { From 29d6985174ecea33447c844da3836d4b5387d427 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 14 Feb 2025 14:15:53 +0100 Subject: [PATCH 54/78] [DOCS] Fix PR ID in the changelog (#920) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0b95a16..f8350e82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Changed -- Mark parsing-related methods of most CSS elements as `@internal` (#90) +- Mark parsing-related methods of most CSS elements as `@internal` (#908) - Mark `OutputFormat::nextLevel()` as `@internal` (#901) - Make all non-private properties `@internal` (#886) From 64e37b8d14a7305449714da18f58ef8a1bd704cc Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 25 Feb 2025 11:45:47 +0100 Subject: [PATCH 55/78] [TASK] Deprecate the IE hack in `Rule` (#993) With modern browsers, we don't need IE hacks anymore. (Also, this code is not used.) --- CHANGELOG.md | 1 + src/Rule/Rule.php | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8350e82..56e02126 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Deprecated +- Deprecate the IE hack in `Rule` (#993) - `OutputFormat` properties for space around list separators as an array (#880) - Deprecate `OutputFormat::level()` (#870) diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index fe706191..27b3bfc8 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -36,6 +36,8 @@ class Rule implements Renderable, Commentable /** * @var array + * + * @deprecated since V8.8.0, will be removed in V9.0 */ private $aIeHack; @@ -319,6 +321,8 @@ public function addIeHack($iModifier) * @param array $aModifiers * * @return void + * + * @deprecated since V8.8.0, will be removed in V9.0 */ public function setIeHack(array $aModifiers) { @@ -327,6 +331,8 @@ public function setIeHack(array $aModifiers) /** * @return array + * + * @deprecated since V8.8.0, will be removed in V9.0 */ public function getIeHack() { From 5ec46173008edd2c0327931cf6c6858b0cae3900 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 26 Feb 2025 10:52:58 +0100 Subject: [PATCH 56/78] [TASK] Make the deprecation of the IE hack complete (#1003) This is a followup to #993. --- CHANGELOG.md | 2 +- src/Rule/Rule.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56e02126..caad56af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Deprecated -- Deprecate the IE hack in `Rule` (#993) +- Deprecate the IE hack in `Rule` (#993, #1003) - `OutputFormat` properties for space around list separators as an array (#880) - Deprecate `OutputFormat::level()` (#870) diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index 27b3bfc8..f6e291fb 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -36,8 +36,6 @@ class Rule implements Renderable, Commentable /** * @var array - * - * @deprecated since V8.8.0, will be removed in V9.0 */ private $aIeHack; @@ -311,6 +309,8 @@ public function addValue($mValue, $sType = ' ') * @param int $iModifier * * @return void + * + * @deprecated since V8.8.0, will be removed in V9.0 */ public function addIeHack($iModifier) { From a9fede8577c5b85c331b77ad1e5b213b4dfd0917 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 27 Feb 2025 19:49:23 +0100 Subject: [PATCH 57/78] [TASK] Deprecate greedy calculation of selector specificity (#1022) This constructor parameter will no longer be used; having the specificity calculation always done lazily is not a problem. This is the V8.x backport of #1018. --- CHANGELOG.md | 1 + src/Property/Selector.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index caad56af..7eafae61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Deprecated +- Deprecate greedy calculation of selector specificity (#1018) - Deprecate the IE hack in `Rule` (#993, #1003) - `OutputFormat` properties for space around list separators as an array (#880) - Deprecate `OutputFormat::level()` (#870) diff --git a/src/Property/Selector.php b/src/Property/Selector.php index 44355e63..5e2dda30 100644 --- a/src/Property/Selector.php +++ b/src/Property/Selector.php @@ -88,7 +88,7 @@ public static function isValid($sSelector) /** * @param string $sSelector - * @param bool $bCalculateSpecificity + * @param bool $bCalculateSpecificity @deprecated since V8.8.0, will be removed in V9.0.0 */ public function __construct($sSelector, $bCalculateSpecificity = false) { From 1ee8a7411ecc48bc622a397d3461d911098bf3b7 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 1 Mar 2025 01:34:17 +0100 Subject: [PATCH 58/78] [TASK] Mark `Selector::isValid()` as `@internal` (#1040) This is the V8.x backport of #1037. --- CHANGELOG.md | 1 + src/Property/Selector.php | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7eafae61..d1a261b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Changed +- Mark `Selector::isValid()` as `@internal` (#1037) - Mark parsing-related methods of most CSS elements as `@internal` (#908) - Mark `OutputFormat::nextLevel()` as `@internal` (#901) - Make all non-private properties `@internal` (#886) diff --git a/src/Property/Selector.php b/src/Property/Selector.php index 5e2dda30..dd7bd880 100644 --- a/src/Property/Selector.php +++ b/src/Property/Selector.php @@ -80,6 +80,8 @@ class Selector * @param string $sSelector * * @return bool + * + * @internal since V8.8.0 */ public static function isValid($sSelector) { From d882635dc7003262e47affaa4cccb7d2d65317bc Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 3 Mar 2025 14:28:56 +0100 Subject: [PATCH 59/78] [TASK] Deprecate `__toString()` (#1012) Part of #998 This is the V8.x backport of #1006. --- CHANGELOG.md | 1 + src/CSSList/AtRuleBlockList.php | 2 ++ src/CSSList/CSSList.php | 2 ++ src/CSSList/KeyFrame.php | 2 ++ src/Comment/Comment.php | 2 ++ src/Property/CSSNamespace.php | 2 ++ src/Property/Charset.php | 2 ++ src/Property/Import.php | 2 ++ src/Property/Selector.php | 2 ++ src/Renderable.php | 2 ++ src/Rule/Rule.php | 2 ++ src/RuleSet/AtRuleSet.php | 2 ++ src/RuleSet/DeclarationBlock.php | 2 ++ src/RuleSet/RuleSet.php | 2 ++ src/Value/CSSFunction.php | 2 ++ src/Value/CSSString.php | 2 ++ src/Value/Color.php | 2 ++ src/Value/LineName.php | 2 ++ src/Value/Size.php | 2 ++ src/Value/URL.php | 2 ++ src/Value/ValueList.php | 2 ++ 21 files changed, 41 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1a261b4..b02c8ebf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Deprecated +- Deprecate `__toString()` (#1006) - Deprecate greedy calculation of selector specificity (#1018) - Deprecate the IE hack in `Rule` (#993, #1003) - `OutputFormat` properties for space around list separators as an array (#880) diff --git a/src/CSSList/AtRuleBlockList.php b/src/CSSList/AtRuleBlockList.php index 5930b932..a424adbe 100644 --- a/src/CSSList/AtRuleBlockList.php +++ b/src/CSSList/AtRuleBlockList.php @@ -50,6 +50,8 @@ public function atRuleArgs() /** * @return string + * + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. */ public function __toString() { diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 5b8f41d5..0f40d40a 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -416,6 +416,8 @@ public function removeDeclarationBlockBySelector($mSelector, $bRemoveAll = false /** * @return string + * + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. */ public function __toString() { diff --git a/src/CSSList/KeyFrame.php b/src/CSSList/KeyFrame.php index 69e2e4d9..618308a7 100644 --- a/src/CSSList/KeyFrame.php +++ b/src/CSSList/KeyFrame.php @@ -61,6 +61,8 @@ public function getAnimationName() /** * @return string + * + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. */ public function __toString() { diff --git a/src/Comment/Comment.php b/src/Comment/Comment.php index 8c02f3de..1d8810ec 100644 --- a/src/Comment/Comment.php +++ b/src/Comment/Comment.php @@ -59,6 +59,8 @@ public function setComment($sComment) /** * @return string + * + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. */ public function __toString() { diff --git a/src/Property/CSSNamespace.php b/src/Property/CSSNamespace.php index f39c2877..0d90cc3b 100644 --- a/src/Property/CSSNamespace.php +++ b/src/Property/CSSNamespace.php @@ -55,6 +55,8 @@ public function getLineNo() /** * @return string + * + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. */ public function __toString() { diff --git a/src/Property/Charset.php b/src/Property/Charset.php index 5e25bd4e..de9016ad 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -75,6 +75,8 @@ public function getCharset() /** * @return string + * + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. */ public function __toString() { diff --git a/src/Property/Import.php b/src/Property/Import.php index 9b40e304..e3f10474 100644 --- a/src/Property/Import.php +++ b/src/Property/Import.php @@ -76,6 +76,8 @@ public function getLocation() /** * @return string + * + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. */ public function __toString() { diff --git a/src/Property/Selector.php b/src/Property/Selector.php index dd7bd880..a0611688 100644 --- a/src/Property/Selector.php +++ b/src/Property/Selector.php @@ -121,6 +121,8 @@ public function setSelector($sSelector) /** * @return string + * + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. */ public function __toString() { diff --git a/src/Renderable.php b/src/Renderable.php index d7c6aba6..1f1b475d 100644 --- a/src/Renderable.php +++ b/src/Renderable.php @@ -6,6 +6,8 @@ interface Renderable { /** * @return string + * + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. */ public function __toString(); diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index f6e291fb..984071c0 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -359,6 +359,8 @@ public function getIsImportant() /** * @return string + * + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. */ public function __toString() { diff --git a/src/RuleSet/AtRuleSet.php b/src/RuleSet/AtRuleSet.php index 93fd07af..e687cb96 100644 --- a/src/RuleSet/AtRuleSet.php +++ b/src/RuleSet/AtRuleSet.php @@ -53,6 +53,8 @@ public function atRuleArgs() /** * @return string + * + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. */ public function __toString() { diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 60d0b0c4..3b6c8775 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -831,6 +831,8 @@ public function createFontShorthand() * @return string * * @throws OutputException + * + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. */ public function __toString() { diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 0cf7ac95..bc87856b 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -268,6 +268,8 @@ public function removeRule($mRule) /** * @return string + * + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. */ public function __toString() { diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index de8e558f..fcf641d5 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -88,6 +88,8 @@ public function getArguments() /** * @return string + * + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. */ public function __toString() { diff --git a/src/Value/CSSString.php b/src/Value/CSSString.php index 0251dc7c..a6a705b5 100644 --- a/src/Value/CSSString.php +++ b/src/Value/CSSString.php @@ -94,6 +94,8 @@ public function getString() /** * @return string + * + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. */ public function __toString() { diff --git a/src/Value/Color.php b/src/Value/Color.php index fbbf11a1..d4b7caf0 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -155,6 +155,8 @@ public function getColorDescription() /** * @return string + * + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. */ public function __toString() { diff --git a/src/Value/LineName.php b/src/Value/LineName.php index 5b59fce5..effc827c 100644 --- a/src/Value/LineName.php +++ b/src/Value/LineName.php @@ -51,6 +51,8 @@ public static function parse(ParserState $oParserState) /** * @return string + * + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. */ public function __toString() { diff --git a/src/Value/Size.php b/src/Value/Size.php index 2e45d272..98876973 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -222,6 +222,8 @@ public function isRelative() /** * @return string + * + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. */ public function __toString() { diff --git a/src/Value/URL.php b/src/Value/URL.php index 7d5bd8e8..1f2a0af7 100644 --- a/src/Value/URL.php +++ b/src/Value/URL.php @@ -81,6 +81,8 @@ public function getURL() /** * @return string + * + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. */ public function __toString() { diff --git a/src/Value/ValueList.php b/src/Value/ValueList.php index 9ae3da99..61962258 100644 --- a/src/Value/ValueList.php +++ b/src/Value/ValueList.php @@ -90,6 +90,8 @@ public function setListSeparator($sSeparator) /** * @return string + * + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. */ public function __toString() { From 13d7ee66e2edcb393c958315df4cd2cd7eba3991 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 4 Mar 2025 11:14:16 +0100 Subject: [PATCH 60/78] [TASK] Deprecate method forwarding `OutputFormat` to `OutputFormatter` (#1060) This is the V8.x backport of #894. --- CHANGELOG.md | 2 ++ src/OutputFormat.php | 1 + 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b02c8ebf..68569219 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Deprecated +- Deprecate magic method forwarding from `OutputFormat` to `OutputFormatter` + (#894) - Deprecate the expansion of shorthand properties (#719) - Deprecate `Parser::setCharset()` and `Parser::getCharset()` (#703) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 76a4ab9c..60c63a05 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -311,6 +311,7 @@ public function __call($sMethodName, array $aArguments) } elseif (strpos($sMethodName, 'get') === 0) { return $this->get(substr($sMethodName, 3)); } elseif (method_exists(OutputFormatter::class, $sMethodName)) { + // @deprecated since 8.8.0, will be removed in 9.0.0. Call the method on the formatter directly instead. return call_user_func_array([$this->getFormatter(), $sMethodName], $aArguments); } else { throw new \Exception('Unknown OutputFormat method called: ' . $sMethodName); From 88044634ecab956a1e4c3070b6cc16924007ab6b Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 4 Mar 2025 11:16:24 +0100 Subject: [PATCH 61/78] [BUGFIX] Fix a type annotation in `RuleSet` (#1061) This is the V8.x backport of #1051. --- src/RuleSet/RuleSet.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index bc87856b..c01b68b8 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -23,7 +23,10 @@ abstract class RuleSet implements Renderable, Commentable { /** - * @var array + * the rules in this rule set, using the property name as the key, + * with potentially multiple rules per property name. + * + * @var array, Rule>> */ private $aRules; From 3cd1d06334ce4a50d9812b76e6fc31af44c56e8d Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Tue, 4 Mar 2025 12:38:56 +0000 Subject: [PATCH 62/78] [TASK] Allow rules to be in any order in shorthand tests (#1065) This is a pre-patch for #1062. --- tests/ParserTest.php | 5 ++- tests/RuleSet/DeclarationBlockTest.php | 54 ++++++++++++++++++++++++-- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/tests/ParserTest.php b/tests/ParserTest.php index c8e8ac76..59f1bd64 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -19,6 +19,7 @@ use Sabberworm\CSS\RuleSet\DeclarationBlock; use Sabberworm\CSS\RuleSet\RuleSet; use Sabberworm\CSS\Settings; +use Sabberworm\CSS\Tests\RuleSet\DeclarationBlockTest; use Sabberworm\CSS\Value\Color; use Sabberworm\CSS\Value\Size; use Sabberworm\CSS\Value\URL; @@ -511,7 +512,7 @@ public function expandShorthands() . 'font-family: "Trebuchet MS",Georgia,serif;background-color: #ccc;' . 'background-image: url("/images/foo.png");background-repeat: no-repeat;background-attachment: scroll;' . 'background-position: left top;}'; - self::assertSame($sExpected, $oDoc->render()); + DeclarationBlockTest::assertDeclarationBlockEquals($sExpected, $oDoc->render()); } /** @@ -528,7 +529,7 @@ public function createShorthands() $oDoc->createShorthands(); $sExpected = 'body {background: #fff url("foobar.png") repeat-y;margin: 2px 5px 4px 3px;' . 'border: 2px dotted #999;font: bold 2em Helvetica,Arial,sans-serif;}'; - self::assertSame($sExpected, $oDoc->render()); + DeclarationBlockTest::assertDeclarationBlockEquals($sExpected, $oDoc->render()); } /** diff --git a/tests/RuleSet/DeclarationBlockTest.php b/tests/RuleSet/DeclarationBlockTest.php index d37ece18..d4cf31b6 100644 --- a/tests/RuleSet/DeclarationBlockTest.php +++ b/tests/RuleSet/DeclarationBlockTest.php @@ -29,7 +29,7 @@ public function expandBorderShorthand($sCss, $sExpected) foreach ($oDoc->getAllDeclarationBlocks() as $oDeclaration) { $oDeclaration->expandBorderShorthand(); } - self::assertSame(trim((string)$oDoc), $sExpected); + self::assertDeclarationBlockEquals(trim((string)$oDoc), $sExpected); } /** @@ -62,7 +62,7 @@ public function expandFontShorthand($sCss, $sExpected) foreach ($oDoc->getAllDeclarationBlocks() as $oDeclaration) { $oDeclaration->expandFontShorthand(); } - self::assertSame(trim((string)$oDoc), $sExpected); + self::assertDeclarationBlockEquals(trim((string)$oDoc), $sExpected); } /** @@ -118,7 +118,7 @@ public function expandBackgroundShorthand($sCss, $sExpected) foreach ($oDoc->getAllDeclarationBlocks() as $oDeclaration) { $oDeclaration->expandBackgroundShorthand(); } - self::assertSame(trim((string)$oDoc), $sExpected); + self::assertDeclarationBlockEquals(trim((string)$oDoc), $sExpected); } /** @@ -171,7 +171,7 @@ public function expandDimensionsShorthand($sCss, $sExpected) foreach ($oDoc->getAllDeclarationBlocks() as $oDeclaration) { $oDeclaration->expandDimensionsShorthand(); } - self::assertSame(trim((string)$oDoc), $sExpected); + self::assertDeclarationBlockEquals(trim((string)$oDoc), $sExpected); } /** @@ -505,4 +505,50 @@ public function canRemoveCommentsFromRulesUsingStrictParsing( self::assertSame($cssWithoutComments, $renderedDocument); } + + /** + * Asserts two declaration blocks are equivalent, allowing the rules to be in any order. + * + * @param string $expected + * @param string $actual + */ + public static function assertDeclarationBlockEquals($expected, $actual) + { + $normalizedExpected = self::sortRulesInDeclarationBlock($expected); + $normalizedActual = self::sortRulesInDeclarationBlock($actual); + + self::assertSame($normalizedExpected, $normalizedActual); + } + + /** + * Sorts the rules within a declaration block by property name. + * + * @param string $declarationBlock + * + * @return string + */ + private static function sortRulesInDeclarationBlock($declarationBlock) + { + // Match everything between `{` and `}`. + return \preg_replace_callback( + '/(?<=\\{)[^\\}]*+(?=\\})/', + [self::class, 'sortDeclarationBlockRules'], + $declarationBlock + ); + } + + /** + * Sorts rules from within a declaration block by property name. + * + * @param array{0: string} $rulesMatches + * This method is intended as a callback for `preg_replace_callback`. + * + * @return string + */ + private static function sortDeclarationBlockRules($rulesMatches) + { + $rules = \explode(';', $rulesMatches[0]); + \sort($rules); + return \implode(';', $rules); + } } From 6543eb200c6e88d3cf315f683cd1ab34febc5420 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 4 Mar 2025 14:00:03 +0100 Subject: [PATCH 63/78] [BUGFIX] Render rules in line and column number order (#1062) This is the V8.x backport of #1059. --- CHANGELOG.md | 1 + src/RuleSet/RuleSet.php | 28 ++++++------- .../RuleSet/DeclarationBlockTest.php | 41 +++++++++++++++++++ 3 files changed, 55 insertions(+), 15 deletions(-) create mode 100644 tests/Functional/RuleSet/DeclarationBlockTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 68569219..5ea5e489 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Fixed +- Render rules in line and column number order (#1059) - Create `Size` with correct types in `expandBackgroundShorthand` (#814) - Parse `@font-face` `src` property as comma-delimited list (#794) diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index c01b68b8..258fefcf 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -287,22 +287,20 @@ protected function renderRules(OutputFormat $oOutputFormat) $sResult = ''; $bIsFirst = true; $oNextLevel = $oOutputFormat->nextLevel(); - foreach ($this->aRules as $aRules) { - foreach ($aRules as $oRule) { - $sRendered = $oNextLevel->safely(function () use ($oRule, $oNextLevel) { - return $oRule->render($oNextLevel); - }); - if ($sRendered === null) { - continue; - } - if ($bIsFirst) { - $bIsFirst = false; - $sResult .= $oNextLevel->spaceBeforeRules(); - } else { - $sResult .= $oNextLevel->spaceBetweenRules(); - } - $sResult .= $sRendered; + foreach ($this->getRules() as $oRule) { + $sRendered = $oNextLevel->safely(function () use ($oRule, $oNextLevel) { + return $oRule->render($oNextLevel); + }); + if ($sRendered === null) { + continue; + } + if ($bIsFirst) { + $bIsFirst = false; + $sResult .= $oNextLevel->spaceBeforeRules(); + } else { + $sResult .= $oNextLevel->spaceBetweenRules(); } + $sResult .= $sRendered; } if (!$bIsFirst) { diff --git a/tests/Functional/RuleSet/DeclarationBlockTest.php b/tests/Functional/RuleSet/DeclarationBlockTest.php new file mode 100644 index 00000000..5273b5b9 --- /dev/null +++ b/tests/Functional/RuleSet/DeclarationBlockTest.php @@ -0,0 +1,41 @@ +setSelectors([new Selector('.test')]); + + $rule1 = new Rule('background-color'); + $rule1->setValue('transparent'); + $declarationBlock->addRule($rule1); + + $rule2 = new Rule('background'); + $rule2->setValue('#222'); + $declarationBlock->addRule($rule2); + + $rule3 = new Rule('background-color'); + $rule3->setValue('#fff'); + $declarationBlock->addRule($rule3); + + $expectedRendering = 'background-color: transparent;background: #222;background-color: #fff'; + self::assertContains($expectedRendering, $declarationBlock->render(new OutputFormat())); + } +} From 9c48d3a3a2d91266ff750b138a86190afa6261a6 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 4 Mar 2025 19:54:31 +0100 Subject: [PATCH 64/78] [TASK] Mark `OutputFormatter` as `@internal` (#1073) This is the V8.x backport of #896. --- CHANGELOG.md | 1 + src/OutputFormat.php | 3 +++ src/OutputFormatter.php | 3 +++ 3 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ea5e489..e69f6243 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Changed +- Mark `OutputFormatter` as `@internal` (#896) - Mark `Selector::isValid()` as `@internal` (#1037) - Mark parsing-related methods of most CSS elements as `@internal` (#908) - Mark `OutputFormat::nextLevel()` as `@internal` (#901) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 60c63a05..ad82395a 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -363,12 +363,15 @@ public function beLenient() /** * @return OutputFormatter + * + * @internal since 8.8.0 */ public function getFormatter() { if ($this->oFormatter === null) { $this->oFormatter = new OutputFormatter($this); } + return $this->oFormatter; } diff --git a/src/OutputFormatter.php b/src/OutputFormatter.php index 3844383d..a436ee3b 100644 --- a/src/OutputFormatter.php +++ b/src/OutputFormatter.php @@ -5,6 +5,9 @@ use Sabberworm\CSS\Comment\Commentable; use Sabberworm\CSS\Parsing\OutputException; +/** + * @internal since 8.8.0 + */ class OutputFormatter { /** From 21e1a8c2872431c9888894339a9b332883284beb Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Wed, 5 Mar 2025 12:28:13 +0000 Subject: [PATCH 65/78] [TASK] Deprecate support for `-webkit-calc` and `-moz-calc` (#1086) --- CHANGELOG.md | 1 + src/Value/CalcFunction.php | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69f6243..119d0023 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Deprecated +- Deprecate support for `-webkit-calc` and `-moz-calc` (#1086) - Deprecate `__toString()` (#1006) - Deprecate greedy calculation of selector specificity (#1018) - Deprecate the IE hack in `Rule` (#993, #1003) diff --git a/src/Value/CalcFunction.php b/src/Value/CalcFunction.php index 70f2f458..c3ed0a08 100644 --- a/src/Value/CalcFunction.php +++ b/src/Value/CalcFunction.php @@ -6,6 +6,9 @@ use Sabberworm\CSS\Parsing\UnexpectedEOFException; use Sabberworm\CSS\Parsing\UnexpectedTokenException; +/** + * Support for `-webkit-calc` and `-moz-calc` is deprecated in version 8.8.0, and will be removed in version 9.0.0. + */ class CalcFunction extends CSSFunction { /** From efad65a11f58dc403ba85cde6391398fe3f93ff9 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 7 Mar 2025 17:44:59 +0100 Subject: [PATCH 66/78] [TASK] Deprecate `OutputFormat::get()` and `::set()` (#1107) Part of #1103 Co-authored-by: JakeQZ --- CHANGELOG.md | 1 + src/OutputFormat.php | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 119d0023..e744f1e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Deprecated +- Deprecate `OutputFormat::get()` and `::set()` (#1107) - Deprecate support for `-webkit-calc` and `-moz-calc` (#1086) - Deprecate `__toString()` (#1006) - Deprecate greedy calculation of selector specificity (#1018) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index ad82395a..d31ef4ff 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -247,6 +247,8 @@ public function __construct() * @param string $sName * * @return string|null + * + * @deprecated since 8.8.0, will be removed in 9.0.0. Use specific getters instead. */ public function get($sName) { @@ -265,6 +267,8 @@ public function get($sName) * @param mixed $mValue * * @return self|false + * + * @deprecated since 8.8.0, will be removed in 9.0.0. Use specific setters instead. */ public function set($aNames, $mValue) { From 78f18609e0dce9323507a98fb5fa4f337540ab88 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 10 Mar 2025 14:35:30 +0100 Subject: [PATCH 67/78] [TASK] Mark the `OutputFormat` as not extendable (#1133) This is the V8.x backport of #1131. --- CHANGELOG.md | 2 ++ src/OutputFormat.php | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e744f1e2..ea3474c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Changed +- Mark the `OutputFormat` the constructor as `@internal` (#1131) - Mark `OutputFormatter` as `@internal` (#896) - Mark `Selector::isValid()` as `@internal` (#1037) - Mark parsing-related methods of most CSS elements as `@internal` (#908) @@ -19,6 +20,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Deprecated +- Deprecate extending `OutputFormat` (#1131) - Deprecate `OutputFormat::get()` and `::set()` (#1107) - Deprecate support for `-webkit-calc` and `-moz-calc` (#1086) - Deprecate `__toString()` (#1006) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index d31ef4ff..9778e274 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -3,7 +3,7 @@ namespace Sabberworm\CSS; /** - * Class OutputFormat + * Extending this class is deprecated in version 8.8.0; it will be made `final` in version 9.0.0. * * @method OutputFormat setSemicolonAfterLastRule(bool $bSemicolonAfterLastRule) Set whether semicolons are added after * last rule. @@ -239,6 +239,9 @@ class OutputFormat */ private $iIndentationLevel = 0; + /** + * @internal since V8.8.0. Use the factory methods `create()`, `createCompact()`, or `createPretty()` instead. + */ public function __construct() { } From 48705660683019c335d525c0317ebe36e7a2232d Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Mon, 17 Mar 2025 00:10:02 +0000 Subject: [PATCH 68/78] [BUGFIX] Include comments for all rules in declaration block (#1182) This is the v8.x backport of #1169. - `Rule::parse()` will no longer consume anything after the semicolon terminating the rule - it does not belong to that rule; - The whitespace and comments before a rule will be processed by `RuleSet::parseRuleSet()` and passed as a parameter to `Rule::parse()` - - This is only required while 'strict mode' parsing is an option, to avoid having an exception thrown during normal operation (i.e. when `Rule::parse()` encounters normal `}` as opposed to some other junk, which is not distinguished). Fixes #173. See also #663, #672, #741. --- CHANGELOG.md | 1 + src/Rule/Rule.php | 8 ++++---- src/RuleSet/RuleSet.php | 10 +++++++--- tests/ParserTest.php | 40 ++++++++++++++++++++++++++++++++++++++-- 4 files changed, 50 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea3474c8..e90ac7c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Fixed +- Include comments for all rules in declaration block (#1169) - Render rules in line and column number order (#1059) - Create `Size` with correct types in `expandBackgroundShorthand` (#814) - Parse `@font-face` `src` property as comma-delimited list (#794) diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index 984071c0..037132e3 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -75,6 +75,8 @@ public function __construct($sRule, $iLineNo = 0, $iColNo = 0) } /** + * @param array $commentsBeforeRule + * * @return Rule * * @throws UnexpectedEOFException @@ -82,9 +84,9 @@ public function __construct($sRule, $iLineNo = 0, $iColNo = 0) * * @internal since V8.8.0 */ - public static function parse(ParserState $oParserState) + public static function parse(ParserState $oParserState, $commentsBeforeRule = []) { - $aComments = $oParserState->consumeWhiteSpace(); + $aComments = \array_merge($commentsBeforeRule, $oParserState->consumeWhiteSpace()); $oRule = new Rule( $oParserState->parseIdentifier(!$oParserState->comes("--")), $oParserState->currentLine(), @@ -114,8 +116,6 @@ public static function parse(ParserState $oParserState) $oParserState->consume(';'); } - $oParserState->consumeWhiteSpace(); - return $oRule; } diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 258fefcf..c0843389 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -67,11 +67,15 @@ public static function parseRuleSet(ParserState $oParserState, RuleSet $oRuleSet while ($oParserState->comes(';')) { $oParserState->consume(';'); } - while (!$oParserState->comes('}')) { + while (true) { + $commentsBeforeRule = $oParserState->consumeWhiteSpace(); + if ($oParserState->comes('}')) { + break; + } $oRule = null; if ($oParserState->getSettings()->bLenientParsing) { try { - $oRule = Rule::parse($oParserState); + $oRule = Rule::parse($oParserState, $commentsBeforeRule); } catch (UnexpectedTokenException $e) { try { $sConsume = $oParserState->consumeUntil(["\n", ";", '}'], true); @@ -89,7 +93,7 @@ public static function parseRuleSet(ParserState $oParserState, RuleSet $oRuleSet } } } else { - $oRule = Rule::parse($oParserState); + $oRule = Rule::parse($oParserState, $commentsBeforeRule); } if ($oRule) { $oRuleSet->addRule($oRule); diff --git a/tests/ParserTest.php b/tests/ParserTest.php index 59f1bd64..7c9778ab 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -1167,9 +1167,11 @@ public function flatCommentExtractingOneComment() { $parser = new Parser('div {/*Find Me!*/left:10px; text-align:left;}'); $doc = $parser->parse(); + $contents = $doc->getContents(); $divRules = $contents[0]->getRules(); $comments = $divRules[0]->getComments(); + self::assertCount(1, $comments); self::assertSame("Find Me!", $comments[0]->getComment()); } @@ -1177,16 +1179,50 @@ public function flatCommentExtractingOneComment() /** * @test */ - public function flatCommentExtractingTwoComments() + public function flatCommentExtractingTwoConjoinedCommentsForOneRule() { - self::markTestSkipped('This is currently broken.'); + $parser = new Parser('div {/*Find Me!*//*Find Me Too!*/left:10px; text-align:left;}'); + $document = $parser->parse(); + $contents = $document->getContents(); + $divRules = $contents[0]->getRules(); + $comments = $divRules[0]->getComments(); + + self::assertCount(2, $comments); + self::assertSame('Find Me!', $comments[0]->getComment()); + self::assertSame('Find Me Too!', $comments[1]->getComment()); + } + + /** + * @test + */ + public function flatCommentExtractingTwoSpaceSeparatedCommentsForOneRule() + { + $parser = new Parser('div { /*Find Me!*/ /*Find Me Too!*/ left:10px; text-align:left;}'); + $document = $parser->parse(); + + $contents = $document->getContents(); + $divRules = $contents[0]->getRules(); + $comments = $divRules[0]->getComments(); + + self::assertCount(2, $comments); + self::assertSame('Find Me!', $comments[0]->getComment()); + self::assertSame('Find Me Too!', $comments[1]->getComment()); + } + + /** + * @test + */ + public function flatCommentExtractingCommentsForTwoRules() + { $parser = new Parser('div {/*Find Me!*/left:10px; /*Find Me Too!*/text-align:left;}'); $doc = $parser->parse(); + $contents = $doc->getContents(); $divRules = $contents[0]->getRules(); $rule1Comments = $divRules[0]->getComments(); $rule2Comments = $divRules[1]->getComments(); + self::assertCount(1, $rule1Comments); self::assertCount(1, $rule2Comments); self::assertEquals('Find Me!', $rule1Comments[0]->getComment()); From 3de493bdddfd1f051249af725c7e0d2c38fed740 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 23 Mar 2025 18:59:05 +0100 Subject: [PATCH 69/78] [TASK] Prepare release of version V8.8.0 (#1210) --- CHANGELOG.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e90ac7c8..883987e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,18 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Added +### Changed + +### Deprecated + +### Removed + +### Fixed + +## 8.8.0: Bug fixes and deprecations + +### Added + - `OutputFormat` properties for space around specific list separators (#880) ### Changed @@ -29,8 +41,6 @@ This project adheres to [Semantic Versioning](https://semver.org/). - `OutputFormat` properties for space around list separators as an array (#880) - Deprecate `OutputFormat::level()` (#870) -### Removed - ### Fixed - Include comments for all rules in declaration block (#1169) From aa12119afb88ce90d3dbaa84dc232765e7423a34 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sat, 29 Mar 2025 07:32:19 +0000 Subject: [PATCH 70/78] [CLEANUP] Move magic deprecation changelog entry to correct section (#1223) Also remove superfluous 'the' in another entry. --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 883987e0..5cc851c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Changed -- Mark the `OutputFormat` the constructor as `@internal` (#1131) +- Mark the `OutputFormat` constructor as `@internal` (#1131) - Mark `OutputFormatter` as `@internal` (#896) - Mark `Selector::isValid()` as `@internal` (#1037) - Mark parsing-related methods of most CSS elements as `@internal` (#908) @@ -35,6 +35,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). - Deprecate extending `OutputFormat` (#1131) - Deprecate `OutputFormat::get()` and `::set()` (#1107) - Deprecate support for `-webkit-calc` and `-moz-calc` (#1086) +- Deprecate magic method forwarding from `OutputFormat` to `OutputFormatter` + (#894) - Deprecate `__toString()` (#1006) - Deprecate greedy calculation of selector specificity (#1018) - Deprecate the IE hack in `Rule` (#993, #1003) @@ -61,8 +63,6 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Deprecated -- Deprecate magic method forwarding from `OutputFormat` to `OutputFormatter` - (#894) - Deprecate the expansion of shorthand properties (#719) - Deprecate `Parser::setCharset()` and `Parser::getCharset()` (#703) From e100b805ed8db8f130740a78527ca7df47c4e49a Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Mon, 7 Apr 2025 23:24:23 +0100 Subject: [PATCH 71/78] [TASK] Add and implement `Positionable` interface (#1232) This the backport of #1221 and #1225. --- CHANGELOG.md | 19 ++ composer.json | 3 +- src/CSSList/CSSList.php | 23 +- src/Comment/Comment.php | 21 +- src/Parsing/SourceException.php | 20 +- src/Position/Position.php | 72 ++++++ src/Position/Positionable.php | 45 ++++ src/Property/CSSNamespace.php | 16 +- src/Property/Charset.php | 16 +- src/Property/Import.php | 23 +- src/Rule/Rule.php | 51 +--- src/RuleSet/DeclarationBlock.php | 13 +- src/RuleSet/RuleSet.php | 23 +- src/Value/CSSFunction.php | 2 +- src/Value/Value.php | 21 +- .../Position/Fixtures/ConcretePosition.php | 13 + tests/Unit/Position/PositionTest.php | 232 ++++++++++++++++++ .../UnitDeprecated/Position/PositionTest.php | 158 ++++++++++++ 18 files changed, 603 insertions(+), 168 deletions(-) create mode 100644 src/Position/Position.php create mode 100644 src/Position/Positionable.php create mode 100644 tests/Unit/Position/Fixtures/ConcretePosition.php create mode 100644 tests/Unit/Position/PositionTest.php create mode 100644 tests/UnitDeprecated/Position/PositionTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cc851c5..891adc8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,29 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Added +- Methods `getLineNumber` and `getColumnNumber` which return a nullable `int` + for the following classes: + `Comment`, `CSSList`, `SourceException`, `Charset`, `CSSNamespace`, `Import`, + `Rule`, `DeclarationBlock`, `RuleSet`, `CSSFunction`, `Value` (#1225) +- `Positionable` interface for CSS items that may have a position + (line and perhaps column number) in the parsed CSS (#1221) + ### Changed +- Implement `Positionable` in the following CSS item classes: + `Comment`, `CSSList`, `SourceException`, `Charset`, `CSSNamespace`, `Import`, + `Rule`, `DeclarationBlock`, `RuleSet`, `CSSFunction`, `Value` (#1225) + ### Deprecated +- `getLineNo()` is deprecated in these classes (use `getLineNumber()` instead): + `Comment`, `CSSList`, `SourceException`, `Charset`, `CSSNamespace`, `Import`, + `Rule`, `DeclarationBlock`, `RuleSet`, `CSSFunction`, `Value` (#1225, #1233) +- `Rule::getColNo()` is deprecated (use `getColumnNumber()` instead) + (#1225, #1233) +- Providing zero as the line number argument to `Rule::setPosition()` is + deprecated (pass `null` instead if there is no line number) (#1225, #1233) + ### Removed ### Fixed diff --git a/composer.json b/composer.json index acf650a2..e3caf0cc 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,8 @@ "ext-iconv": "*" }, "require-dev": { - "phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.41" + "phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.41", + "rawr/cross-data-providers": "^2.0.0" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 0f40d40a..2156aa11 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -9,6 +9,8 @@ use Sabberworm\CSS\Parsing\SourceException; use Sabberworm\CSS\Parsing\UnexpectedEOFException; use Sabberworm\CSS\Parsing\UnexpectedTokenException; +use Sabberworm\CSS\Position\Position; +use Sabberworm\CSS\Position\Positionable; use Sabberworm\CSS\Property\AtRule; use Sabberworm\CSS\Property\Charset; use Sabberworm\CSS\Property\CSSNamespace; @@ -29,8 +31,10 @@ * * It can also contain `Import` and `Charset` objects stemming from at-rules. */ -abstract class CSSList implements Renderable, Commentable +abstract class CSSList implements Commentable, Positionable, Renderable { + use Position; + /** * @var array * @@ -45,13 +49,6 @@ abstract class CSSList implements Renderable, Commentable */ protected $aContents; - /** - * @var int - * - * @internal since 8.8.0 - */ - protected $iLineNo; - /** * @param int $iLineNo */ @@ -59,7 +56,7 @@ public function __construct($iLineNo = 0) { $this->aComments = []; $this->aContents = []; - $this->iLineNo = $iLineNo; + $this->setPosition($iLineNo); } /** @@ -258,14 +255,6 @@ private static function identifierIs($sIdentifier, $sMatch) ?: preg_match("/^(-\\w+-)?$sMatch$/i", $sIdentifier) === 1; } - /** - * @return int - */ - public function getLineNo() - { - return $this->iLineNo; - } - /** * Prepends an item to the list of contents. * diff --git a/src/Comment/Comment.php b/src/Comment/Comment.php index 1d8810ec..fb571b40 100644 --- a/src/Comment/Comment.php +++ b/src/Comment/Comment.php @@ -4,15 +4,12 @@ use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Renderable; +use Sabberworm\CSS\Position\Position; +use Sabberworm\CSS\Position\Positionable; -class Comment implements Renderable +class Comment implements Positionable, Renderable { - /** - * @var int - * - * @internal since 8.8.0 - */ - protected $iLineNo; + use Position; /** * @var string @@ -28,7 +25,7 @@ class Comment implements Renderable public function __construct($sComment = '', $iLineNo = 0) { $this->sComment = $sComment; - $this->iLineNo = $iLineNo; + $this->setPosition($iLineNo); } /** @@ -39,14 +36,6 @@ public function getComment() return $this->sComment; } - /** - * @return int - */ - public function getLineNo() - { - return $this->iLineNo; - } - /** * @param string $sComment * diff --git a/src/Parsing/SourceException.php b/src/Parsing/SourceException.php index 1ca668a9..1aa27b43 100644 --- a/src/Parsing/SourceException.php +++ b/src/Parsing/SourceException.php @@ -2,12 +2,12 @@ namespace Sabberworm\CSS\Parsing; -class SourceException extends \Exception +use Sabberworm\CSS\Position\Position; +use Sabberworm\CSS\Position\Positionable; + +class SourceException extends \Exception implements Positionable { - /** - * @var int - */ - private $iLineNo; + use Position; /** * @param string $sMessage @@ -15,18 +15,10 @@ class SourceException extends \Exception */ public function __construct($sMessage, $iLineNo = 0) { - $this->iLineNo = $iLineNo; + $this->setPosition($iLineNo); if (!empty($iLineNo)) { $sMessage .= " [line no: $iLineNo]"; } parent::__construct($sMessage); } - - /** - * @return int - */ - public function getLineNo() - { - return $this->iLineNo; - } } diff --git a/src/Position/Position.php b/src/Position/Position.php new file mode 100644 index 00000000..1c4d0df0 --- /dev/null +++ b/src/Position/Position.php @@ -0,0 +1,72 @@ +|null + */ + protected $lineNumber; + + /** + * @var int<0, max>|null + */ + protected $columnNumber; + + /** + * @return int<1, max>|null + */ + public function getLineNumber() + { + return $this->lineNumber; + } + + /** + * @return int<0, max> + */ + public function getLineNo() + { + $lineNumber = $this->getLineNumber(); + + return $lineNumber !== null ? $lineNumber : 0; + } + + /** + * @return int<0, max>|null + */ + public function getColumnNumber() + { + return $this->columnNumber; + } + + /** + * @return int<0, max> + */ + public function getColNo() + { + $columnNumber = $this->getColumnNumber(); + + return $columnNumber !== null ? $columnNumber : 0; + } + + /** + * @param int<0, max>|null $lineNumber + * @param int<0, max>|null $columnNumber + */ + public function setPosition($lineNumber, $columnNumber = null) + { + // The conditional is for backwards compatibility (backcompat); `0` will not be allowed in future. + $this->lineNumber = $lineNumber !== 0 ? $lineNumber : null; + $this->columnNumber = $columnNumber; + } +} diff --git a/src/Position/Positionable.php b/src/Position/Positionable.php new file mode 100644 index 00000000..4539c425 --- /dev/null +++ b/src/Position/Positionable.php @@ -0,0 +1,45 @@ +|null + */ + public function getLineNumber(); + + /** + * @return int<0, max> + * + * @deprecated in version 8.9.0, will be removed in v9.0. Use `getLineNumber()` instead. + */ + public function getLineNo(); + + /** + * @return int<0, max>|null + */ + public function getColumnNumber(); + + /** + * @return int<0, max> + * + * @deprecated in version 8.9.0, will be removed in v9.0. Use `getColumnNumber()` instead. + */ + public function getColNo(); + + /** + * @param int<0, max>|null $lineNumber + * Providing zero for this parameter is deprecated in version 8.9.0, and will not be supported from v9.0. + * Use `null` instead when no line number is available. + * @param int<0, max>|null $columnNumber + */ + public function setPosition($lineNumber, $columnNumber = null); +} diff --git a/src/Property/CSSNamespace.php b/src/Property/CSSNamespace.php index 0d90cc3b..188d3581 100644 --- a/src/Property/CSSNamespace.php +++ b/src/Property/CSSNamespace.php @@ -4,12 +4,16 @@ use Sabberworm\CSS\Comment\Comment; use Sabberworm\CSS\OutputFormat; +use Sabberworm\CSS\Position\Position; +use Sabberworm\CSS\Position\Positionable; /** * `CSSNamespace` represents an `@namespace` rule. */ -class CSSNamespace implements AtRule +class CSSNamespace implements AtRule, Positionable { + use Position; + /** * @var string */ @@ -41,18 +45,10 @@ public function __construct($mUrl, $sPrefix = null, $iLineNo = 0) { $this->mUrl = $mUrl; $this->sPrefix = $sPrefix; - $this->iLineNo = $iLineNo; + $this->setPosition($iLineNo); $this->aComments = []; } - /** - * @return int - */ - public function getLineNo() - { - return $this->iLineNo; - } - /** * @return string * diff --git a/src/Property/Charset.php b/src/Property/Charset.php index de9016ad..1ebff3f3 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -4,6 +4,8 @@ use Sabberworm\CSS\Comment\Comment; use Sabberworm\CSS\OutputFormat; +use Sabberworm\CSS\Position\Position; +use Sabberworm\CSS\Position\Positionable; use Sabberworm\CSS\Value\CSSString; /** @@ -14,8 +16,10 @@ * - May only appear at the very top of a Document’s contents. * - Must not appear more than once. */ -class Charset implements AtRule +class Charset implements AtRule, Positionable { + use Position; + /** * @var CSSString */ @@ -42,18 +46,10 @@ class Charset implements AtRule public function __construct(CSSString $oCharset, $iLineNo = 0) { $this->oCharset = $oCharset; - $this->iLineNo = $iLineNo; + $this->setPosition($iLineNo); $this->aComments = []; } - /** - * @return int - */ - public function getLineNo() - { - return $this->iLineNo; - } - /** * @param string|CSSString $oCharset * diff --git a/src/Property/Import.php b/src/Property/Import.php index e3f10474..5b474493 100644 --- a/src/Property/Import.php +++ b/src/Property/Import.php @@ -4,13 +4,17 @@ use Sabberworm\CSS\Comment\Comment; use Sabberworm\CSS\OutputFormat; +use Sabberworm\CSS\Position\Position; +use Sabberworm\CSS\Position\Positionable; use Sabberworm\CSS\Value\URL; /** * Class representing an `@import` rule. */ -class Import implements AtRule +class Import implements AtRule, Positionable { + use Position; + /** * @var URL */ @@ -21,13 +25,6 @@ class Import implements AtRule */ private $sMediaQuery; - /** - * @var int - * - * @internal since 8.8.0 - */ - protected $iLineNo; - /** * @var array * @@ -44,18 +41,10 @@ public function __construct(URL $oLocation, $sMediaQuery, $iLineNo = 0) { $this->oLocation = $oLocation; $this->sMediaQuery = $sMediaQuery; - $this->iLineNo = $iLineNo; + $this->setPosition($iLineNo); $this->aComments = []; } - /** - * @return int - */ - public function getLineNo() - { - return $this->iLineNo; - } - /** * @param URL $oLocation * diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index 037132e3..e358d93f 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -8,6 +8,8 @@ use Sabberworm\CSS\Parsing\ParserState; use Sabberworm\CSS\Parsing\UnexpectedEOFException; use Sabberworm\CSS\Parsing\UnexpectedTokenException; +use Sabberworm\CSS\Position\Position; +use Sabberworm\CSS\Position\Positionable; use Sabberworm\CSS\Renderable; use Sabberworm\CSS\Value\RuleValueList; use Sabberworm\CSS\Value\Value; @@ -17,8 +19,10 @@ * * In CSS, `Rule`s are expressed as follows: “key: value[0][0] value[0][1], value[1][0] value[1][1];” */ -class Rule implements Renderable, Commentable +class Rule implements Commentable, Positionable, Renderable { + use Position; + /** * @var string */ @@ -39,18 +43,6 @@ class Rule implements Renderable, Commentable */ private $aIeHack; - /** - * @var int - */ - protected $iLineNo; - - /** - * @var int - * - * @internal since 8.8.0 - */ - protected $iColNo; - /** * @var array * @@ -69,8 +61,7 @@ public function __construct($sRule, $iLineNo = 0, $iColNo = 0) $this->mValue = null; $this->bIsImportant = false; $this->aIeHack = []; - $this->iLineNo = $iLineNo; - $this->iColNo = $iColNo; + $this->setPosition($iLineNo, $iColNo); $this->aComments = []; } @@ -142,34 +133,6 @@ private static function listDelimiterForRule($sRule) } } - /** - * @return int - */ - public function getLineNo() - { - return $this->iLineNo; - } - - /** - * @return int - */ - public function getColNo() - { - return $this->iColNo; - } - - /** - * @param int $iLine - * @param int $iColumn - * - * @return void - */ - public function setPosition($iLine, $iColumn) - { - $this->iColNo = $iColumn; - $this->iLineNo = $iLine; - } - /** * @param string $sRule * @@ -295,7 +258,7 @@ public function addValue($mValue, $sType = ' ') } if (!$this->mValue instanceof RuleValueList || $this->mValue->getListSeparator() !== $sType) { $mCurrentValue = $this->mValue; - $this->mValue = new RuleValueList($sType, $this->iLineNo); + $this->mValue = new RuleValueList($sType, $this->getLineNumber()); if ($mCurrentValue) { $this->mValue->addListComponent($mCurrentValue); } diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 3b6c8775..9cc14ebf 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -434,8 +434,8 @@ public function expandBackgroundShorthand() 'background-repeat' => ['repeat'], 'background-attachment' => ['scroll'], 'background-position' => [ - new Size(0, '%', false, $this->iLineNo), - new Size(0, '%', false, $this->iLineNo), + new Size(0, '%', false, $this->getLineNo()), + new Size(0, '%', false, $this->getLineNo()), ], ]; $mRuleValue = $oRule->getValue(); @@ -801,7 +801,7 @@ public function createFontShorthand() $aLHValues = $mRuleValue->getListComponents(); } if ($aLHValues[0] !== 'normal') { - $val = new RuleValueList('/', $this->iLineNo); + $val = new RuleValueList('/', $this->getLineNo()); $val->addListComponent($aFSValues[0]); $val->addListComponent($aLHValues[0]); $oNewRule->addValue($val); @@ -817,7 +817,7 @@ public function createFontShorthand() } else { $aFFValues = $mRuleValue->getListComponents(); } - $oFFValue = new RuleValueList(',', $this->iLineNo); + $oFFValue = new RuleValueList(',', $this->getLineNo()); $oFFValue->setListComponents($aFFValues); $oNewRule->addValue($oFFValue); @@ -851,7 +851,10 @@ public function render($oOutputFormat) $sResult = $oOutputFormat->comments($this); if (count($this->aSelectors) === 0) { // If all the selectors have been removed, this declaration block becomes invalid - throw new OutputException("Attempt to print declaration block with missing selector", $this->iLineNo); + throw new OutputException( + 'Attempt to print declaration block with missing selector', + $this->getLineNumber() + ); } $sResult .= $oOutputFormat->sBeforeDeclarationBlock; $sResult .= $oOutputFormat->implode( diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index c0843389..85b94f26 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -8,6 +8,8 @@ use Sabberworm\CSS\Parsing\ParserState; use Sabberworm\CSS\Parsing\UnexpectedEOFException; use Sabberworm\CSS\Parsing\UnexpectedTokenException; +use Sabberworm\CSS\Position\Position; +use Sabberworm\CSS\Position\Positionable; use Sabberworm\CSS\Renderable; use Sabberworm\CSS\Rule\Rule; @@ -20,8 +22,10 @@ * If you want to manipulate a `RuleSet`, use the methods `addRule(Rule $rule)`, `getRules()` and `removeRule($rule)` * (which accepts either a `Rule` or a rule name; optionally suffixed by a dash to remove all related rules). */ -abstract class RuleSet implements Renderable, Commentable +abstract class RuleSet implements Commentable, Positionable, Renderable { + use Position; + /** * the rules in this rule set, using the property name as the key, * with potentially multiple rules per property name. @@ -30,13 +34,6 @@ abstract class RuleSet implements Renderable, Commentable */ private $aRules; - /** - * @var int - * - * @internal since 8.8.0 - */ - protected $iLineNo; - /** * @var array * @@ -50,7 +47,7 @@ abstract class RuleSet implements Renderable, Commentable public function __construct($iLineNo = 0) { $this->aRules = []; - $this->iLineNo = $iLineNo; + $this->setPosition($iLineNo); $this->aComments = []; } @@ -102,14 +99,6 @@ public static function parseRuleSet(ParserState $oParserState, RuleSet $oRuleSet $oParserState->consume('}'); } - /** - * @return int - */ - public function getLineNo() - { - return $this->iLineNo; - } - /** * @param Rule|null $oSibling * diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index fcf641d5..703f6658 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -34,7 +34,7 @@ public function __construct($sName, $aArguments, $sSeparator = ',', $iLineNo = 0 $aArguments = $aArguments->getListComponents(); } $this->sName = $sName; - $this->iLineNo = $iLineNo; + $this->setPosition($iLineNo); // TODO: redundant? parent::__construct($aArguments, $sSeparator, $iLineNo); } diff --git a/src/Value/Value.php b/src/Value/Value.php index 3816289d..beb74464 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -6,27 +6,24 @@ use Sabberworm\CSS\Parsing\SourceException; use Sabberworm\CSS\Parsing\UnexpectedEOFException; use Sabberworm\CSS\Parsing\UnexpectedTokenException; +use Sabberworm\CSS\Position\Position; +use Sabberworm\CSS\Position\Positionable; use Sabberworm\CSS\Renderable; /** * Abstract base class for specific classes of CSS values: `Size`, `Color`, `CSSString` and `URL`, and another * abstract subclass `ValueList`. */ -abstract class Value implements Renderable +abstract class Value implements Positionable, Renderable { - /** - * @var int - * - * @internal since 8.8.0 - */ - protected $iLineNo; + use Position; /** * @param int $iLineNo */ public function __construct($iLineNo = 0) { - $this->iLineNo = $iLineNo; + $this->setPosition($iLineNo); } /** @@ -218,12 +215,4 @@ private static function parseUnicodeRangeValue(ParserState $oParserState) } while (strlen($sRange) < $iCodepointMaxLength && preg_match("/[A-Fa-f0-9\?-]/", $oParserState->peek())); return "U+{$sRange}"; } - - /** - * @return int - */ - public function getLineNo() - { - return $this->iLineNo; - } } diff --git a/tests/Unit/Position/Fixtures/ConcretePosition.php b/tests/Unit/Position/Fixtures/ConcretePosition.php new file mode 100644 index 00000000..0db38706 --- /dev/null +++ b/tests/Unit/Position/Fixtures/ConcretePosition.php @@ -0,0 +1,13 @@ +subject = new ConcretePosition(); + } + + /** + * @test + */ + public function getLineNumberInitiallyReturnsNull() + { + $this->doSetUp(); + + self::assertNull($this->subject->getLineNumber()); + } + + /** + * @test + */ + public function getColumnNumberInitiallyReturnsNull() + { + $this->doSetUp(); + + self::assertNull($this->subject->getColumnNumber()); + } + + /** + * @return array}> + */ + public function provideLineNumber() + { + return [ + 'line 1' => [1], + 'line 42' => [42], + ]; + } + + /** + * @test + * + * @param int<1, max> $lineNumber + * + * @dataProvider provideLineNumber + */ + public function setPositionOnVirginSetsLineNumber($lineNumber) + { + $this->doSetUp(); + + $this->subject->setPosition($lineNumber); + + self::assertSame($lineNumber, $this->subject->getLineNumber()); + } + + /** + * @test + * + * @param int<1, max> $lineNumber + * + * @dataProvider provideLineNumber + */ + public function setPositionSetsNewLineNumber($lineNumber) + { + $this->doSetUp(); + + $this->subject->setPosition(99); + + $this->subject->setPosition($lineNumber); + + self::assertSame($lineNumber, $this->subject->getLineNumber()); + } + + /** + * @test + */ + public function setPositionWithNullClearsLineNumber() + { + $this->doSetUp(); + + $this->subject->setPosition(99); + + $this->subject->setPosition(null); + + self::assertNull($this->subject->getLineNumber()); + } + + /** + * @return array}> + */ + public function provideColumnNumber() + { + return [ + 'column 0' => [0], + 'column 14' => [14], + 'column 39' => [39], + ]; + } + + /** + * @test + * + * @param int<0, max> $columnNumber + * + * @dataProvider provideColumnNumber + */ + public function setPositionOnVirginSetsColumnNumber($columnNumber) + { + $this->doSetUp(); + + $this->subject->setPosition(1, $columnNumber); + + self::assertSame($columnNumber, $this->subject->getColumnNumber()); + } + + /** + * @test + * + * @param int $columnNumber + * + * @dataProvider provideColumnNumber + */ + public function setPositionSetsNewColumnNumber($columnNumber) + { + $this->doSetUp(); + + $this->subject->setPosition(1, 99); + + $this->subject->setPosition(2, $columnNumber); + + self::assertSame($columnNumber, $this->subject->getColumnNumber()); + } + + /** + * @test + */ + public function setPositionWithoutColumnNumberClearsColumnNumber() + { + $this->doSetUp(); + + $this->subject->setPosition(1, 99); + + $this->subject->setPosition(2); + + self::assertNull($this->subject->getColumnNumber()); + } + + /** + * @test + */ + public function setPositionWithNullForColumnNumberClearsColumnNumber() + { + $this->doSetUp(); + + $this->subject->setPosition(1, 99); + + $this->subject->setPosition(2, null); + + self::assertNull($this->subject->getColumnNumber()); + } + + /** + * @return array, 1: int<0, max>}> + */ + public function provideLineAndColumnNumber() + { + if (!\class_exists(DataProviders::class)) { + self::markTestSkipped('`DataProviders` class is not available'); + return []; + } + + return DataProviders::cross($this->provideLineNumber(), $this->provideColumnNumber()); + } + + /** + * @test + * + * @param int $lineNumber + * @param int $columnNumber + * + * @dataProvider provideLineAndColumnNumber + */ + public function setPositionOnVirginSetsLineAndColumnNumber($lineNumber, $columnNumber) + { + $this->doSetUp(); + + $this->subject->setPosition($lineNumber, $columnNumber); + + self::assertSame($lineNumber, $this->subject->getLineNumber()); + self::assertSame($columnNumber, $this->subject->getColumnNumber()); + } + + /** + * @test + * + * @param int $lineNumber + * @param int $columnNumber + * + * @dataProvider provideLineAndColumnNumber + */ + public function setPositionSetsNewLineAndColumnNumber($lineNumber, $columnNumber) + { + $this->doSetUp(); + + $this->subject->setPosition(98, 99); + + $this->subject->setPosition($lineNumber, $columnNumber); + + self::assertSame($lineNumber, $this->subject->getLineNumber()); + self::assertSame($columnNumber, $this->subject->getColumnNumber()); + } +} diff --git a/tests/UnitDeprecated/Position/PositionTest.php b/tests/UnitDeprecated/Position/PositionTest.php new file mode 100644 index 00000000..f7378e04 --- /dev/null +++ b/tests/UnitDeprecated/Position/PositionTest.php @@ -0,0 +1,158 @@ +subject = new ConcretePosition(); + } + + /** + * @return array}> + */ + public function provideLineNumber() + { + return [ + 'line 1' => [1], + 'line 42' => [42], + ]; + } + + /** + * @return array}> + */ + public function provideColumnNumber() + { + return [ + 'column 0' => [0], + 'column 14' => [14], + 'column 39' => [39], + ]; + } + + /** + * @test + */ + public function getLineNoInitiallyReturnsZero() + { + $this->doSetUp(); + + self::assertSame(0, $this->subject->getLineNo()); + } + + /** + * @test + * + * @paarm int $lineNumber + * + * @dataProvider provideLineNumber + */ + public function getLineNoReturnsLineNumberSet($lineNumber) + { + $this->doSetUp(); + + $this->subject->setPosition($lineNumber); + + self::assertSame($lineNumber, $this->subject->getLineNo()); + } + + /** + * @test + */ + public function getLineNoReturnsZeroAfterLineNumberCleared() + { + $this->doSetUp(); + + $this->subject->setPosition(99); + + $this->subject->setPosition(null); + + self::assertSame(0, $this->subject->getLineNo()); + } + + /** + * @test + */ + public function getColNoInitiallyReturnsZero() + { + $this->doSetUp(); + + self::assertSame(0, $this->subject->getColNo()); + } + + /** + * @test + * + * @param int $columnNumber + * + * @dataProvider provideColumnNumber + */ + public function getColNoReturnsColumnNumberSet($columnNumber) + { + $this->doSetUp(); + + $this->subject->setPosition(1, $columnNumber); + + self::assertSame($columnNumber, $this->subject->getColNo()); + } + + /** + * @test + */ + public function getColNoReturnsZeroAfterColumnNumberCleared() + { + $this->doSetUp(); + + $this->subject->setPosition(1, 99); + + $this->subject->setPosition(2); + + self::assertSame(0, $this->subject->getColNo()); + } + + /** + * @test + */ + public function setPositionWithZeroClearsLineNumber() + { + $this->doSetUp(); + + $this->subject->setPosition(99); + + $this->subject->setPosition(0); + + self::assertNull($this->subject->getLineNumber()); + } + + /** + * @test + */ + public function getLineNoAfterSetPositionWithZeroReturnsZero() + { + $this->doSetUp(); + + $this->subject->setPosition(99); + + $this->subject->setPosition(0); + + self::assertSame(0, $this->subject->getLineNo()); + } +} From f5fc39a4baef45f8fcf5231fa8be266d136f7b46 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sat, 12 Apr 2025 09:34:04 +0100 Subject: [PATCH 72/78] [TASK] Deconflate getAllValues() parameters (#1242) This is the backport of #1231, #1240 and #1241. --- CHANGELOG.md | 9 + src/CSSElement.php | 17 ++ src/CSSList/CSSBlockList.php | 49 ++- src/CSSList/CSSList.php | 3 +- src/CSSList/Document.php | 28 -- src/Rule/Rule.php | 4 +- src/RuleSet/RuleSet.php | 3 +- src/Value/Value.php | 4 +- tests/Unit/CSSList/CSSBlockListTest.php | 279 ++++++++++++++++++ tests/Unit/CSSList/CSSListTest.php | 27 ++ .../CSSList/Fixtures/ConcreteCSSBlockList.php | 29 ++ .../Unit/CSSList/Fixtures/ConcreteCSSList.php | 29 ++ tests/Unit/Rule/RuleTest.php | 13 + .../Unit/RuleSet/Fixtures/ConcreteRuleSet.php | 21 ++ tests/Unit/RuleSet/RuleSetTest.php | 27 ++ tests/Unit/Value/Fixtures/ConcreteValue.php | 29 ++ tests/Unit/Value/ValueTest.php | 28 ++ 17 files changed, 564 insertions(+), 35 deletions(-) create mode 100644 src/CSSElement.php create mode 100644 tests/Unit/CSSList/CSSBlockListTest.php create mode 100644 tests/Unit/CSSList/CSSListTest.php create mode 100644 tests/Unit/CSSList/Fixtures/ConcreteCSSBlockList.php create mode 100644 tests/Unit/CSSList/Fixtures/ConcreteCSSList.php create mode 100644 tests/Unit/RuleSet/Fixtures/ConcreteRuleSet.php create mode 100644 tests/Unit/RuleSet/RuleSetTest.php create mode 100644 tests/Unit/Value/Fixtures/ConcreteValue.php create mode 100644 tests/Unit/Value/ValueTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 891adc8a..b08c38a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Added +- Add Interface `CSSElement` (#1231) - Methods `getLineNumber` and `getColumnNumber` which return a nullable `int` for the following classes: `Comment`, `CSSList`, `SourceException`, `Charset`, `CSSNamespace`, `Import`, @@ -16,12 +17,20 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Changed +- Parameters for `getAllValues()` are deconflated, so it now takes three (all + optional), allowing `$element` and `$ruleSearchPattern` to be specified + separately (#1241) - Implement `Positionable` in the following CSS item classes: `Comment`, `CSSList`, `SourceException`, `Charset`, `CSSNamespace`, `Import`, `Rule`, `DeclarationBlock`, `RuleSet`, `CSSFunction`, `Value` (#1225) ### Deprecated +- Passing a string as the first argument to `getAllValues()` is deprecated; + the search pattern should now be passed as the second argument (#1241) +- Passing a Boolean as the second argument to `getAllValues()` is deprecated; + the flag for searching in function arguments should now be passed as the third + argument (#1241) - `getLineNo()` is deprecated in these classes (use `getLineNumber()` instead): `Comment`, `CSSList`, `SourceException`, `Charset`, `CSSNamespace`, `Import`, `Rule`, `DeclarationBlock`, `RuleSet`, `CSSFunction`, `Value` (#1225, #1233) diff --git a/src/CSSElement.php b/src/CSSElement.php new file mode 100644 index 00000000..944aabe2 --- /dev/null +++ b/src/CSSElement.php @@ -0,0 +1,17 @@ + + * + * @see RuleSet->getRules() + */ + public function getAllValues( + $element = null, + $ruleSearchPatternOrSearchInFunctionArguments = null, + $searchInFunctionArguments = false + ) { + if (\is_bool($ruleSearchPatternOrSearchInFunctionArguments)) { + $searchInFunctionArguments = $ruleSearchPatternOrSearchInFunctionArguments; + $searchString = null; + } else { + $searchString = $ruleSearchPatternOrSearchInFunctionArguments; + } + + if ($element === null) { + $element = $this; + } elseif (\is_string($element)) { + $searchString = $element; + $element = $this; + } + + $result = []; + $this->allValues($element, $result, $searchString, $searchInFunctionArguments); + return $result; + } + + /** + * @param CSSElement|string $oElement * @param array $aResult * @param string|null $sSearchString * @param bool $bSearchInFunctionArguments diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 2156aa11..18d926e1 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -4,6 +4,7 @@ use Sabberworm\CSS\Comment\Comment; use Sabberworm\CSS\Comment\Commentable; +use Sabberworm\CSS\CSSElement; use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Parsing\ParserState; use Sabberworm\CSS\Parsing\SourceException; @@ -31,7 +32,7 @@ * * It can also contain `Import` and `Charset` objects stemming from at-rules. */ -abstract class CSSList implements Commentable, Positionable, Renderable +abstract class CSSList implements Commentable, CSSElement, Positionable { use Position; diff --git a/src/CSSList/Document.php b/src/CSSList/Document.php index f74c8964..ed4b09b3 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -8,7 +8,6 @@ use Sabberworm\CSS\Property\Selector; use Sabberworm\CSS\RuleSet\DeclarationBlock; use Sabberworm\CSS\RuleSet\RuleSet; -use Sabberworm\CSS\Value\Value; /** * This class represents the root of a parsed CSS file. It contains all top-level CSS contents: mostly declaration @@ -77,33 +76,6 @@ public function getAllRuleSets() return $aResult; } - /** - * Returns all `Value` objects found recursively in `Rule`s in the tree. - * - * @param CSSList|RuleSet|string $mElement - * the `CSSList` or `RuleSet` to start the search from (defaults to the whole document). - * If a string is given, it is used as rule name filter. - * @param bool $bSearchInFunctionArguments whether to also return Value objects used as Function arguments. - * - * @return array - * - * @see RuleSet->getRules() - */ - public function getAllValues($mElement = null, $bSearchInFunctionArguments = false) - { - $sSearchString = null; - if ($mElement === null) { - $mElement = $this; - } elseif (is_string($mElement)) { - $sSearchString = $mElement; - $mElement = $this; - } - /** @var array $aResult */ - $aResult = []; - $this->allValues($mElement, $aResult, $sSearchString, $bSearchInFunctionArguments); - return $aResult; - } - /** * Returns all `Selector` objects with the requested specificity found recursively in the tree. * diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index e358d93f..a34018a8 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -4,13 +4,13 @@ use Sabberworm\CSS\Comment\Comment; use Sabberworm\CSS\Comment\Commentable; +use Sabberworm\CSS\CSSElement; use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Parsing\ParserState; use Sabberworm\CSS\Parsing\UnexpectedEOFException; use Sabberworm\CSS\Parsing\UnexpectedTokenException; use Sabberworm\CSS\Position\Position; use Sabberworm\CSS\Position\Positionable; -use Sabberworm\CSS\Renderable; use Sabberworm\CSS\Value\RuleValueList; use Sabberworm\CSS\Value\Value; @@ -19,7 +19,7 @@ * * In CSS, `Rule`s are expressed as follows: “key: value[0][0] value[0][1], value[1][0] value[1][1];” */ -class Rule implements Commentable, Positionable, Renderable +class Rule implements Commentable, CSSElement, Positionable { use Position; diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 85b94f26..12b8157a 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -4,6 +4,7 @@ use Sabberworm\CSS\Comment\Comment; use Sabberworm\CSS\Comment\Commentable; +use Sabberworm\CSS\CSSElement; use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Parsing\ParserState; use Sabberworm\CSS\Parsing\UnexpectedEOFException; @@ -22,7 +23,7 @@ * If you want to manipulate a `RuleSet`, use the methods `addRule(Rule $rule)`, `getRules()` and `removeRule($rule)` * (which accepts either a `Rule` or a rule name; optionally suffixed by a dash to remove all related rules). */ -abstract class RuleSet implements Commentable, Positionable, Renderable +abstract class RuleSet implements CSSElement, Commentable, Positionable { use Position; diff --git a/src/Value/Value.php b/src/Value/Value.php index beb74464..3025566f 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -2,19 +2,19 @@ namespace Sabberworm\CSS\Value; +use Sabberworm\CSS\CSSElement; use Sabberworm\CSS\Parsing\ParserState; use Sabberworm\CSS\Parsing\SourceException; use Sabberworm\CSS\Parsing\UnexpectedEOFException; use Sabberworm\CSS\Parsing\UnexpectedTokenException; use Sabberworm\CSS\Position\Position; use Sabberworm\CSS\Position\Positionable; -use Sabberworm\CSS\Renderable; /** * Abstract base class for specific classes of CSS values: `Size`, `Color`, `CSSString` and `URL`, and another * abstract subclass `ValueList`. */ -abstract class Value implements Positionable, Renderable +abstract class Value implements CSSElement, Positionable { use Position; diff --git a/tests/Unit/CSSList/CSSBlockListTest.php b/tests/Unit/CSSList/CSSBlockListTest.php new file mode 100644 index 00000000..85a2e9e7 --- /dev/null +++ b/tests/Unit/CSSList/CSSBlockListTest.php @@ -0,0 +1,279 @@ +getAllValues()); + } + + /** + * @test + * + * @return void + */ + public function getAllValuesReturnsOneValueDirectlySetAsContent() + { + $subject = new ConcreteCSSBlockList(); + + $value = new CSSString('Superfont'); + + $declarationBlock = new DeclarationBlock(); + $rule = new Rule('font-family'); + $rule->setValue($value); + $declarationBlock->addRule($rule); + $subject->setContents([$declarationBlock]); + + $result = $subject->getAllValues(); + + self::assertSame([$value], $result); + } + + /** + * @test + * + * @return void + */ + public function getAllValuesReturnsMultipleValuesDirectlySetAsContentInOneDeclarationBlock() + { + $subject = new ConcreteCSSBlockList(); + + $value1 = new CSSString('Superfont'); + $value2 = new CSSString('aquamarine'); + + $declarationBlock = new DeclarationBlock(); + $rule1 = new Rule('font-family'); + $rule1->setValue($value1); + $declarationBlock->addRule($rule1); + $rule2 = new Rule('color'); + $rule2->setValue($value2); + $declarationBlock->addRule($rule2); + $subject->setContents([$declarationBlock]); + + $result = $subject->getAllValues(); + + self::assertSame([$value1, $value2], $result); + } + + /** + * @test + * + * @return void + */ + public function getAllValuesReturnsMultipleValuesDirectlySetAsContentInMultipleDeclarationBlocks() + { + $subject = new ConcreteCSSBlockList(); + + $value1 = new CSSString('Superfont'); + $value2 = new CSSString('aquamarine'); + + $declarationBlock1 = new DeclarationBlock(); + $rule1 = new Rule('font-family'); + $rule1->setValue($value1); + $declarationBlock1->addRule($rule1); + $declarationBlock2 = new DeclarationBlock(); + $rule2 = new Rule('color'); + $rule2->setValue($value2); + $declarationBlock2->addRule($rule2); + $subject->setContents([$declarationBlock1, $declarationBlock2]); + + $result = $subject->getAllValues(); + + self::assertSame([$value1, $value2], $result); + } + + /** + * @test + * + * @return void + */ + public function getAllValuesReturnsValuesWithinAtRuleBlockList() + { + $subject = new ConcreteCSSBlockList(); + + $value = new CSSString('Superfont'); + + $declarationBlock = new DeclarationBlock(); + $rule = new Rule('font-family'); + $rule->setValue($value); + $declarationBlock->addRule($rule); + $atRuleBlockList = new AtRuleBlockList('media'); + $atRuleBlockList->setContents([$declarationBlock]); + $subject->setContents([$atRuleBlockList]); + + $result = $subject->getAllValues(); + + self::assertSame([$value], $result); + } + + /** + * @test + * + * @return void + */ + public function getAllValuesWithElementProvidedReturnsOnlyValuesWithinThatElement() + { + $subject = new ConcreteCSSBlockList(); + + $value1 = new CSSString('Superfont'); + $value2 = new CSSString('aquamarine'); + + $declarationBlock1 = new DeclarationBlock(); + $rule1 = new Rule('font-family'); + $rule1->setValue($value1); + $declarationBlock1->addRule($rule1); + $declarationBlock2 = new DeclarationBlock(); + $rule2 = new Rule('color'); + $rule2->setValue($value2); + $declarationBlock2->addRule($rule2); + $subject->setContents([$declarationBlock1, $declarationBlock2]); + + $result = $subject->getAllValues($declarationBlock1); + + self::assertSame([$value1], $result); + } + + /** + * @test + * + * @return void + */ + public function getAllValuesWithSearchStringProvidedReturnsOnlyValuesFromMatchingRules() + { + $subject = new ConcreteCSSBlockList(); + + $value1 = new CSSString('Superfont'); + $value2 = new CSSString('aquamarine'); + + $declarationBlock = new DeclarationBlock(); + $rule1 = new Rule('font-family'); + $rule1->setValue($value1); + $declarationBlock->addRule($rule1); + $rule2 = new Rule('color'); + $rule2->setValue($value2); + $declarationBlock->addRule($rule2); + $subject->setContents([$declarationBlock]); + + $result = $subject->getAllValues('font-'); + + self::assertSame([$value1], $result); + } + + /** + * @test + * + * @return void + */ + public function getAllValuesWithSearchStringProvidedInNewMethodSignatureReturnsOnlyValuesFromMatchingRules() + { + $subject = new ConcreteCSSBlockList(); + + $value1 = new CSSString('Superfont'); + $value2 = new CSSString('aquamarine'); + + $declarationBlock = new DeclarationBlock(); + $rule1 = new Rule('font-family'); + $rule1->setValue($value1); + $declarationBlock->addRule($rule1); + $rule2 = new Rule('color'); + $rule2->setValue($value2); + $declarationBlock->addRule($rule2); + $subject->setContents([$declarationBlock]); + + $result = $subject->getAllValues(null, 'font-'); + + self::assertSame([$value1], $result); + } + + /** + * @test + * + * @return void + */ + public function getAllValuesByDefaultDoesNotReturnValuesInFunctionArguments() + { + $subject = new ConcreteCSSBlockList(); + + $value1 = new Size(10, 'px'); + $value2 = new Size(2, '%'); + + $declarationBlock = new DeclarationBlock(); + $rule = new Rule('margin'); + $rule->setValue(new CSSFunction('max', [$value1, $value2])); + $declarationBlock->addRule($rule); + $subject->setContents([$declarationBlock]); + + $result = $subject->getAllValues(); + + self::assertSame([], $result); + } + + /** + * @test + * + * @return void + */ + public function getAllValuesWithSearchInFunctionArgumentsReturnsValuesInFunctionArguments() + { + $subject = new ConcreteCSSBlockList(); + + $value1 = new Size(10, 'px'); + $value2 = new Size(2, '%'); + + $declarationBlock = new DeclarationBlock(); + $rule = new Rule('margin'); + $rule->setValue(new CSSFunction('max', [$value1, $value2])); + $declarationBlock->addRule($rule); + $subject->setContents([$declarationBlock]); + + $result = $subject->getAllValues(null, true); + + self::assertSame([$value1, $value2], $result); + } + + /** + * @test + * + * @return void + */ + public function getAllValuesWithSearchInFunctionArgumentsInNewMethodSignatureReturnsValuesInFunctionArguments() + { + $subject = new ConcreteCSSBlockList(); + + $value1 = new Size(10, 'px'); + $value2 = new Size(2, '%'); + + $declarationBlock = new DeclarationBlock(); + $rule = new Rule('margin'); + $rule->setValue(new CSSFunction('max', [$value1, $value2])); + $declarationBlock->addRule($rule); + $subject->setContents([$declarationBlock]); + + $result = $subject->getAllValues(null, null, true); + + self::assertSame([$value1, $value2], $result); + } +} diff --git a/tests/Unit/CSSList/CSSListTest.php b/tests/Unit/CSSList/CSSListTest.php new file mode 100644 index 00000000..d7fa79cb --- /dev/null +++ b/tests/Unit/CSSList/CSSListTest.php @@ -0,0 +1,27 @@ +}> */ diff --git a/tests/Unit/RuleSet/Fixtures/ConcreteRuleSet.php b/tests/Unit/RuleSet/Fixtures/ConcreteRuleSet.php new file mode 100644 index 00000000..0aa96669 --- /dev/null +++ b/tests/Unit/RuleSet/Fixtures/ConcreteRuleSet.php @@ -0,0 +1,21 @@ +render(new OutputFormat()); + } +} diff --git a/tests/Unit/Value/ValueTest.php b/tests/Unit/Value/ValueTest.php new file mode 100644 index 00000000..d9ce5a4f --- /dev/null +++ b/tests/Unit/Value/ValueTest.php @@ -0,0 +1,28 @@ + Date: Mon, 5 May 2025 09:19:16 +0100 Subject: [PATCH 73/78] [TASK] Add separate methods for `RuleSet::removeRule()`, with deprecation (#1251) Passing a `string` or `null` to `removeRule()` is deprecated. The new methods handle that functionality. Relates to #1247. This is the backport of #1249. --- CHANGELOG.md | 7 ++ src/RuleSet/RuleSet.php | 60 ++++++---- tests/Unit/RuleSet/RuleSetTest.php | 175 +++++++++++++++++++++++++++++ 3 files changed, 219 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b08c38a2..cab8e1e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Added +- `RuleSet::removeMatchingRules()` method + (for the implementing classes `AtRuleSet` and `DeclarationBlock`) (#1249) +- `RuleSet::removeAllRules()` method + (for the implementing classes `AtRuleSet` and `DeclarationBlock`) (#1249) - Add Interface `CSSElement` (#1231) - Methods `getLineNumber` and `getColumnNumber` which return a nullable `int` for the following classes: @@ -26,6 +30,9 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Deprecated +- Passing a `string` or `null` to `RuleSet::removeRule()` is deprecated + (implementing classes are `AtRuleSet` and `DeclarationBlock`); + use `removeMatchingRules()` or `removeAllRules()` instead (#1249) - Passing a string as the first argument to `getAllValues()` is deprecated; the search pattern should now be passed as the second argument (#1241) - Passing a Boolean as the second argument to `getAllValues()` is deprecated; diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 12b8157a..8cd4904d 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -220,20 +220,12 @@ public function getRulesAssoc($mRule = null) } /** - * Removes a rule from this RuleSet. This accepts all the possible values that `getRules()` accepts. - * - * If given a Rule, it will only remove this particular rule (by identity). - * If given a name, it will remove all rules by that name. - * - * Note: this is different from pre-v.2.0 behaviour of PHP-CSS-Parser, where passing a Rule instance would - * remove all rules with the same name. To get the old behaviour, use `removeRule($oRule->getRule())`. + * Removes a `Rule` from this `RuleSet` by identity. * * @param Rule|string|null $mRule - * pattern to remove. If $mRule is null, all rules are removed. If the pattern ends in a dash, - * all rules starting with the pattern are removed as well as one matching the pattern with the dash - * excluded. Passing a Rule behaves matches by identity. - * - * @return void + * `Rule` to remove. + * Passing a `string` or `null` is deprecated in version 8.9.0, and will no longer work from v9.0. + * Use `removeMatchingRules()` or `removeAllRules()` instead. */ public function removeRule($mRule) { @@ -247,22 +239,44 @@ public function removeRule($mRule) unset($this->aRules[$sRule][$iKey]); } } + } elseif ($mRule !== null) { + $this->removeMatchingRules($mRule); } else { - foreach ($this->aRules as $sName => $aRules) { - // Either no search rule is given or the search rule matches the found rule exactly - // or the search rule ends in “-” and the found rule starts with the search rule or equals it - // (without the trailing dash). - if ( - !$mRule || $sName === $mRule - || (strrpos($mRule, '-') === strlen($mRule) - strlen('-') - && (strpos($sName, $mRule) === 0 || $sName === substr($mRule, 0, -1))) - ) { - unset($this->aRules[$sName]); - } + $this->removeAllRules(); + } + } + + /** + * Removes rules by property name or search pattern. + * + * @param string $searchPattern + * pattern to remove. + * If the pattern ends in a dash, + * all rules starting with the pattern are removed as well as one matching the pattern with the dash + * excluded. + */ + public function removeMatchingRules($searchPattern) + { + foreach ($this->aRules as $propertyName => $rules) { + // Either the search rule matches the found rule exactly + // or the search rule ends in “-” and the found rule starts with the search rule or equals it + // (without the trailing dash). + if ( + $propertyName === $searchPattern + || (\strrpos($searchPattern, '-') === \strlen($searchPattern) - \strlen('-') + && (\strpos($propertyName, $searchPattern) === 0 + || $propertyName === \substr($searchPattern, 0, -1))) + ) { + unset($this->aRules[$propertyName]); } } } + public function removeAllRules() + { + $this->aRules = []; + } + /** * @return string * diff --git a/tests/Unit/RuleSet/RuleSetTest.php b/tests/Unit/RuleSet/RuleSetTest.php index d5231e1f..fada2a28 100644 --- a/tests/Unit/RuleSet/RuleSetTest.php +++ b/tests/Unit/RuleSet/RuleSetTest.php @@ -6,6 +6,7 @@ use PHPUnit\Framework\TestCase; use Sabberworm\CSS\CSSElement; +use Sabberworm\CSS\Rule\Rule; use Sabberworm\CSS\Tests\Unit\RuleSet\Fixtures\ConcreteRuleSet; /** @@ -24,4 +25,178 @@ public function implementsCSSElement() self::assertInstanceOf(CSSElement::class, $subject); } + + /** + * @return array, 1: string, 2: list}> + */ + public static function provideRulesAndPropertyNameToRemoveAndExpectedRemainingPropertyNames() + { + return [ + 'removing single rule' => [ + [new Rule('color')], + 'color', + [], + ], + 'removing first rule' => [ + [new Rule('color'), new Rule('display')], + 'color', + ['display'], + ], + 'removing last rule' => [ + [new Rule('color'), new Rule('display')], + 'display', + ['color'], + ], + 'removing middle rule' => [ + [new Rule('color'), new Rule('display'), new Rule('width')], + 'display', + ['color', 'width'], + ], + 'removing multiple rules' => [ + [new Rule('color'), new Rule('color')], + 'color', + [], + ], + 'removing multiple rules with another kept' => [ + [new Rule('color'), new Rule('color'), new Rule('display')], + 'color', + ['display'], + ], + 'removing nonexistent rule from empty list' => [ + [], + 'color', + [], + ], + 'removing nonexistent rule from nonempty list' => [ + [new Rule('color'), new Rule('display')], + 'width', + ['color', 'display'], + ], + ]; + } + + /** + * @test + * + * @param list $rules + * @param string $propertyName + * @param list $expectedRemainingPropertyNames + * + * @dataProvider provideRulesAndPropertyNameToRemoveAndExpectedRemainingPropertyNames + */ + public function removeMatchingRulesRemovesRulesByPropertyNameAndKeepsOthers( + array $rules, + $propertyName, + array $expectedRemainingPropertyNames + ) { + $subject = new ConcreteRuleSet(); + $subject->setRules($rules); + + $subject->removeMatchingRules($propertyName); + + $remainingRules = $subject->getRulesAssoc(); + self::assertArrayNotHasKey($propertyName, $remainingRules); + foreach ($expectedRemainingPropertyNames as $expectedPropertyName) { + self::assertArrayHasKey($expectedPropertyName, $remainingRules); + } + } + + /** + * @return array, 1: string, 2: list}> + */ + public static function provideRulesAndPropertyNamePrefixToRemoveAndExpectedRemainingPropertyNames() + { + return [ + 'removing shorthand rule' => [ + [new Rule('font')], + 'font', + [], + ], + 'removing longhand rule' => [ + [new Rule('font-size')], + 'font', + [], + ], + 'removing shorthand and longhand rule' => [ + [new Rule('font'), new Rule('font-size')], + 'font', + [], + ], + 'removing shorthand rule with another kept' => [ + [new Rule('font'), new Rule('color')], + 'font', + ['color'], + ], + 'removing longhand rule with another kept' => [ + [new Rule('font-size'), new Rule('color')], + 'font', + ['color'], + ], + 'keeping other rules whose property names begin with the same characters' => [ + [new Rule('contain'), new Rule('container'), new Rule('container-type')], + 'contain', + ['container', 'container-type'], + ], + ]; + } + + /** + * @test + * + * @param list $rules + * @param string $propertyNamePrefix + * @param list $expectedRemainingPropertyNames + * + * @dataProvider provideRulesAndPropertyNamePrefixToRemoveAndExpectedRemainingPropertyNames + */ + public function removeMatchingRulesRemovesRulesByPropertyNamePrefixAndKeepsOthers( + array $rules, + $propertyNamePrefix, + array $expectedRemainingPropertyNames + ) { + $propertyNamePrefixWithHyphen = $propertyNamePrefix . '-'; + $subject = new ConcreteRuleSet(); + $subject->setRules($rules); + + $subject->removeMatchingRules($propertyNamePrefixWithHyphen); + + $remainingRules = $subject->getRulesAssoc(); + self::assertArrayNotHasKey($propertyNamePrefix, $remainingRules); + foreach (\array_keys($remainingRules) as $remainingPropertyName) { + self::assertStringStartsNotWith($propertyNamePrefixWithHyphen, $remainingPropertyName); + } + foreach ($expectedRemainingPropertyNames as $expectedPropertyName) { + self::assertArrayHasKey($expectedPropertyName, $remainingRules); + } + } + + /** + * @return array}> + */ + public static function provideRulesToRemove() + { + return [ + 'no rules' => [[]], + 'one rule' => [[new Rule('color')]], + 'two rules for different properties' => [[new Rule('color'), new Rule('display')]], + 'two rules for the same property' => [[new Rule('color'), new Rule('color')]], + ]; + } + + /** + * @test + * + * @param list $rules + * + * @dataProvider provideRulesToRemove + */ + public function removeAllRulesRemovesAllRules(array $rules) + { + $subject = new ConcreteRuleSet(); + $subject->setRules($rules); + + $subject->removeAllRules(); + + self::assertSame([], $subject->getRules()); + } } From d11477ca6b3e7aa2cdd652d34ca444552d72dafd Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Mon, 5 May 2025 09:20:10 +0100 Subject: [PATCH 74/78] [TASK] Deprecate passing `Rule` to `RuleSet::getRules()` (#1252) And also `getRulesAssoc()`. Relates to #1247. This is the backport of #1248. Co-authored-by: Oliver Klee --- CHANGELOG.md | 3 +++ src/RuleSet/RuleSet.php | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cab8e1e9..7c4d653b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,9 @@ This project adheres to [Semantic Versioning](https://semver.org/). - Passing a `string` or `null` to `RuleSet::removeRule()` is deprecated (implementing classes are `AtRuleSet` and `DeclarationBlock`); use `removeMatchingRules()` or `removeAllRules()` instead (#1249) +- Passing a `Rule` to `RuleSet::getRules()` or `getRulesAssoc()` is deprecated, + affecting the implementing classes `AtRuleSet` and `DeclarationBlock` + (call e.g. `getRules($rule->getRule())` instead) (#1248) - Passing a string as the first argument to `getAllValues()` is deprecated; the search pattern should now be passed as the second argument (#1241) - Passing a Boolean as the second argument to `getAllValues()` is deprecated; diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 8cd4904d..1658154c 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -146,7 +146,8 @@ public function addRule(Rule $oRule, $oSibling = null) * Pattern to search for. If null, returns all rules. * If the pattern ends with a dash, all rules starting with the pattern are returned * as well as one matching the pattern with the dash excluded. - * Passing a Rule behaves like calling `getRules($mRule->getRule())`. + * Passing a `Rule` for this parameter is deprecated in version 8.9.0, and will not work from v9.0. + * Call `getRules($rule->getRule())` instead. * * @return array */ @@ -205,7 +206,9 @@ public function setRules(array $aRules) * @param Rule|string|null $mRule $mRule * Pattern to search for. If null, returns all rules. If the pattern ends with a dash, * all rules starting with the pattern are returned as well as one matching the pattern with the dash - * excluded. Passing a Rule behaves like calling `getRules($mRule->getRule())`. + * excluded. + * Passing a `Rule` for this parameter is deprecated in version 8.9.0, and will not work from v9.0. + * Call `getRulesAssoc($rule->getRule())` instead. * * @return array */ From 009b6ca2990f5224c1e6aabd1031825c9e038151 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sat, 24 May 2025 12:13:50 +0100 Subject: [PATCH 75/78] [TASK] Add changelog note that PHP < 7.2 is deprecated (#1264) --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c4d653b..c1535719 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Deprecated +- Support for PHP < 7.2 is deprecated; version 9.0 will require PHP 7.2 or later + (#1264) - Passing a `string` or `null` to `RuleSet::removeRule()` is deprecated (implementing classes are `AtRuleSet` and `DeclarationBlock`); use `removeMatchingRules()` or `removeAllRules()` instead (#1249) From 4c0e4a8b0bc1f462c1b37b5c934380277eb8b357 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Tue, 3 Jun 2025 14:03:48 +0100 Subject: [PATCH 76/78] [BUGFIX] Don't return objects from data providers (#1267) The same objects may be provided to multiple tests. If a test manipulates an object, it will no longer be in the initial expected state for other tests. This is the backport of #1260. --- tests/Unit/RuleSet/RuleSetTest.php | 92 +++++++++++++++++------------- 1 file changed, 53 insertions(+), 39 deletions(-) diff --git a/tests/Unit/RuleSet/RuleSetTest.php b/tests/Unit/RuleSet/RuleSetTest.php index fada2a28..847078bb 100644 --- a/tests/Unit/RuleSet/RuleSetTest.php +++ b/tests/Unit/RuleSet/RuleSetTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\TestCase; use Sabberworm\CSS\CSSElement; use Sabberworm\CSS\Rule\Rule; +use Sabberworm\CSS\RuleSet\RuleSet; use Sabberworm\CSS\Tests\Unit\RuleSet\Fixtures\ConcreteRuleSet; /** @@ -27,38 +28,38 @@ public function implementsCSSElement() } /** - * @return array, 1: string, 2: list}> + * @return array, 1: string, 2: list}> */ - public static function provideRulesAndPropertyNameToRemoveAndExpectedRemainingPropertyNames() + public static function providePropertyNamesAndPropertyNameToRemoveAndExpectedRemainingPropertyNames() { return [ 'removing single rule' => [ - [new Rule('color')], + ['color'], 'color', [], ], 'removing first rule' => [ - [new Rule('color'), new Rule('display')], + ['color', 'display'], 'color', ['display'], ], 'removing last rule' => [ - [new Rule('color'), new Rule('display')], + ['color', 'display'], 'display', ['color'], ], 'removing middle rule' => [ - [new Rule('color'), new Rule('display'), new Rule('width')], + ['color', 'display', 'width'], 'display', ['color', 'width'], ], 'removing multiple rules' => [ - [new Rule('color'), new Rule('color')], + ['color', 'color'], 'color', [], ], 'removing multiple rules with another kept' => [ - [new Rule('color'), new Rule('color'), new Rule('display')], + ['color', 'color', 'display'], 'color', ['display'], ], @@ -68,7 +69,7 @@ public static function provideRulesAndPropertyNameToRemoveAndExpectedRemainingPr [], ], 'removing nonexistent rule from nonempty list' => [ - [new Rule('color'), new Rule('display')], + ['color', 'display'], 'width', ['color', 'display'], ], @@ -78,62 +79,62 @@ public static function provideRulesAndPropertyNameToRemoveAndExpectedRemainingPr /** * @test * - * @param list $rules - * @param string $propertyName + * @param list $initialPropertyNames + * @param string $propertyNameToRemove * @param list $expectedRemainingPropertyNames * - * @dataProvider provideRulesAndPropertyNameToRemoveAndExpectedRemainingPropertyNames + * @dataProvider providePropertyNamesAndPropertyNameToRemoveAndExpectedRemainingPropertyNames */ public function removeMatchingRulesRemovesRulesByPropertyNameAndKeepsOthers( - array $rules, - $propertyName, + array $initialPropertyNames, + $propertyNameToRemove, array $expectedRemainingPropertyNames ) { $subject = new ConcreteRuleSet(); - $subject->setRules($rules); + self::setRulesFromPropertyNames($subject, $initialPropertyNames); - $subject->removeMatchingRules($propertyName); + $subject->removeMatchingRules($propertyNameToRemove); $remainingRules = $subject->getRulesAssoc(); - self::assertArrayNotHasKey($propertyName, $remainingRules); + self::assertArrayNotHasKey($propertyNameToRemove, $remainingRules); foreach ($expectedRemainingPropertyNames as $expectedPropertyName) { self::assertArrayHasKey($expectedPropertyName, $remainingRules); } } /** - * @return array, 1: string, 2: list}> + * @return array, 1: string, 2: list}> */ - public static function provideRulesAndPropertyNamePrefixToRemoveAndExpectedRemainingPropertyNames() + public static function providePropertyNamesAndPropertyNamePrefixToRemoveAndExpectedRemainingPropertyNames() { return [ 'removing shorthand rule' => [ - [new Rule('font')], + ['font'], 'font', [], ], 'removing longhand rule' => [ - [new Rule('font-size')], + ['font-size'], 'font', [], ], 'removing shorthand and longhand rule' => [ - [new Rule('font'), new Rule('font-size')], + ['font', 'font-size'], 'font', [], ], 'removing shorthand rule with another kept' => [ - [new Rule('font'), new Rule('color')], + ['font', 'color'], 'font', ['color'], ], 'removing longhand rule with another kept' => [ - [new Rule('font-size'), new Rule('color')], + ['font-size', 'color'], 'font', ['color'], ], 'keeping other rules whose property names begin with the same characters' => [ - [new Rule('contain'), new Rule('container'), new Rule('container-type')], + ['contain', 'container', 'container-type'], 'contain', ['container', 'container-type'], ], @@ -143,20 +144,20 @@ public static function provideRulesAndPropertyNamePrefixToRemoveAndExpectedRemai /** * @test * - * @param list $rules + * @param list $initialPropertyNames * @param string $propertyNamePrefix * @param list $expectedRemainingPropertyNames * - * @dataProvider provideRulesAndPropertyNamePrefixToRemoveAndExpectedRemainingPropertyNames + * @dataProvider providePropertyNamesAndPropertyNamePrefixToRemoveAndExpectedRemainingPropertyNames */ public function removeMatchingRulesRemovesRulesByPropertyNamePrefixAndKeepsOthers( - array $rules, + array $initialPropertyNames, $propertyNamePrefix, array $expectedRemainingPropertyNames ) { $propertyNamePrefixWithHyphen = $propertyNamePrefix . '-'; $subject = new ConcreteRuleSet(); - $subject->setRules($rules); + self::setRulesFromPropertyNames($subject, $initialPropertyNames); $subject->removeMatchingRules($propertyNamePrefixWithHyphen); @@ -171,32 +172,45 @@ public function removeMatchingRulesRemovesRulesByPropertyNamePrefixAndKeepsOther } /** - * @return array}> + * @return array}> */ - public static function provideRulesToRemove() + public static function providePropertyNamesToRemove() { return [ - 'no rules' => [[]], - 'one rule' => [[new Rule('color')]], - 'two rules for different properties' => [[new Rule('color'), new Rule('display')]], - 'two rules for the same property' => [[new Rule('color'), new Rule('color')]], + 'no properties' => [[]], + 'one property' => [['color']], + 'two different properties' => [['color', 'display']], + 'two of the same property' => [['color', 'color']], ]; } /** * @test * - * @param list $rules + * @param list $propertyNamesToRemove * - * @dataProvider provideRulesToRemove + * @dataProvider providePropertyNamesToRemove */ - public function removeAllRulesRemovesAllRules(array $rules) + public function removeAllRulesRemovesAllRules(array $propertyNamesToRemove) { $subject = new ConcreteRuleSet(); - $subject->setRules($rules); + self::setRulesFromPropertyNames($subject, $propertyNamesToRemove); $subject->removeAllRules(); self::assertSame([], $subject->getRules()); } + + /** + * @param list $propertyNames + */ + private static function setRulesFromPropertyNames(RuleSet $subject, array $propertyNames) + { + $subject->setRules(\array_map( + function ($propertyName) { + return new Rule($propertyName); + }, + $propertyNames + )); + } } From 7a469b22d7f5163ac639746042243e0115d986cc Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Tue, 3 Jun 2025 22:44:13 +0100 Subject: [PATCH 77/78] [TASK] Add tests for `RuleSet::addRule()` without sibling argument (#1269) Some are currently skipped, pending some minor bug fixes. This is the backport of #1261. --- tests/Unit/RuleSet/RuleSetTest.php | 158 ++++++++++++++++++++++++++--- 1 file changed, 144 insertions(+), 14 deletions(-) diff --git a/tests/Unit/RuleSet/RuleSetTest.php b/tests/Unit/RuleSet/RuleSetTest.php index 847078bb..303e718b 100644 --- a/tests/Unit/RuleSet/RuleSetTest.php +++ b/tests/Unit/RuleSet/RuleSetTest.php @@ -9,6 +9,7 @@ use Sabberworm\CSS\Rule\Rule; use Sabberworm\CSS\RuleSet\RuleSet; use Sabberworm\CSS\Tests\Unit\RuleSet\Fixtures\ConcreteRuleSet; +use TRegx\DataProvider\DataProviders; /** * @covers \Sabberworm\CSS\RuleSet\RuleSet @@ -27,6 +28,148 @@ public function implementsCSSElement() self::assertInstanceOf(CSSElement::class, $subject); } + /** + * @return array}> + */ + public static function providePropertyNamesToBeSetInitially() + { + return [ + 'no properties' => [[]], + 'one property' => [['color']], + 'two different properties' => [['color', 'display']], + 'two of the same property' => [['color', 'color']], + ]; + } + + /** + * @return array + */ + public static function providePropertyNameToAdd() + { + return [ + 'property name `color` maybe matching that of existing declaration' => ['color'], + 'property name `display` maybe matching that of existing declaration' => ['display'], + 'property name `width` not matching that of existing declaration' => ['width'], + ]; + } + + /** + * @return array, 1: string}> + */ + public static function provideInitialPropertyNamesAndPropertyNameToAdd() + { + if (!\class_exists(DataProviders::class)) { + self::markTestSkipped('`DataProviders` class is not available'); + return []; + } + + return DataProviders::cross(self::providePropertyNamesToBeSetInitially(), self::providePropertyNameToAdd()); + } + + /** + * @test + * + * @param list $initialPropertyNames + * + * @dataProvider provideInitialPropertyNamesAndPropertyNameToAdd + */ + public function addRuleWithoutSiblingAddsRuleAfterInitialRulesAndSetsValidLineAndColumnNumbers( + array $initialPropertyNames, + string $propertyNameToAdd + ) { + if ($initialPropertyNames === []) { + self::markTestSkipped('currently broken - first rule added does not have valid line number set'); + } + + $subject = new ConcreteRuleSet(); + $ruleToAdd = new Rule($propertyNameToAdd); + self::setRulesFromPropertyNames($subject, $initialPropertyNames); + + $subject->addRule($ruleToAdd); + + $rules = $subject->getRules(); + self::assertSame($ruleToAdd, \end($rules)); + self::assertInternalType('int', $ruleToAdd->getLineNumber(), 'line number not set'); + self::assertGreaterThanOrEqual(1, $ruleToAdd->getLineNumber(), 'line number not valid'); + self::assertInternalType('int', $ruleToAdd->getColumnNumber(), 'column number not set'); + self::assertGreaterThanOrEqual(0, $ruleToAdd->getColumnNumber(), 'column number not valid'); + } + + /** + * @test + * + * @dataProvider provideInitialPropertyNamesAndPropertyNameToAdd + * + * @param list $initialPropertyNames + */ + public function addRuleWithOnlyLineNumberAddsRuleAndSetsColumnNumberPreservingLineNumber( + array $initialPropertyNames, + string $propertyNameToAdd + ) { + self::markTestSkipped('currently broken - does not set column number'); + + $subject = new ConcreteRuleSet(); + $ruleToAdd = new Rule($propertyNameToAdd); + $ruleToAdd->setPosition(42); + self::setRulesFromPropertyNames($subject, $initialPropertyNames); + + $subject->addRule($ruleToAdd); + + self::assertContains($ruleToAdd, $subject->getRules()); + self::assertInternalType('int', $ruleToAdd->getColumnNumber(), 'column number not set'); + self::assertGreaterThanOrEqual(0, $ruleToAdd->getColumnNumber(), 'column number not valid'); + self::assertSame(42, $ruleToAdd->getLineNumber(), 'line number not preserved'); + } + + /** + * @test + * + * @dataProvider provideInitialPropertyNamesAndPropertyNameToAdd + * + * @param list $initialPropertyNames + */ + public function addRuleWithOnlyColumnNumberAddsRuleAndSetsLineNumberPreservingColumnNumber( + array $initialPropertyNames, + string $propertyNameToAdd + ) { + self::markTestSkipped('currently broken - does not preserve column number'); + + $subject = new ConcreteRuleSet(); + $ruleToAdd = new Rule($propertyNameToAdd); + $ruleToAdd->setPosition(null, 42); + self::setRulesFromPropertyNames($subject, $initialPropertyNames); + + $subject->addRule($ruleToAdd); + + self::assertContains($ruleToAdd, $subject->getRules()); + self::assertInternalType('int', $ruleToAdd->getLineNumber(), 'line number not set'); + self::assertGreaterThanOrEqual(1, $ruleToAdd->getLineNumber(), 'line number not valid'); + self::assertSame(42, $ruleToAdd->getColumnNumber(), 'column number not preserved'); + } + + /** + * @test + * + * @dataProvider provideInitialPropertyNamesAndPropertyNameToAdd + * + * @param list $initialPropertyNames + */ + public function addRuleWithCompletePositionAddsRuleAndPreservesPosition( + array $initialPropertyNames, + string $propertyNameToAdd + ) { + $subject = new ConcreteRuleSet(); + $ruleToAdd = new Rule($propertyNameToAdd); + $ruleToAdd->setPosition(42, 64); + self::setRulesFromPropertyNames($subject, $initialPropertyNames); + + $subject->addRule($ruleToAdd); + + self::assertContains($ruleToAdd, $subject->getRules()); + self::assertSame(42, $ruleToAdd->getLineNumber(), 'line number not preserved'); + self::assertSame(64, $ruleToAdd->getColumnNumber(), 'column number not preserved'); + } + /** * @return array, 1: string, 2: list}> */ @@ -171,25 +314,12 @@ public function removeMatchingRulesRemovesRulesByPropertyNamePrefixAndKeepsOther } } - /** - * @return array}> - */ - public static function providePropertyNamesToRemove() - { - return [ - 'no properties' => [[]], - 'one property' => [['color']], - 'two different properties' => [['color', 'display']], - 'two of the same property' => [['color', 'color']], - ]; - } - /** * @test * * @param list $propertyNamesToRemove * - * @dataProvider providePropertyNamesToRemove + * @dataProvider providePropertyNamesToBeSetInitially */ public function removeAllRulesRemovesAllRules(array $propertyNamesToRemove) { From e0a904e50a2aad46cd22aa37a358c76c7daabf37 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Wed, 4 Jun 2025 09:06:53 +0100 Subject: [PATCH 78/78] [BUGFIX] Ensure valid position after `AddRule` (#1271) This is the backport of #1263, #1262 and #1265. Since `getLineNo()` and `getColNo()`, which always returned an `int`, are deprecated, using their replacements, which may return `null`, for `Rule`s in a `RuleSet` may cause issues. The fixes ensure that such `Rule`s will always have a valid position, so the new methods will not return `null` in that situation, and a straightforward replacement can be done. Part of #974. --- CHANGELOG.md | 6 +++++- src/RuleSet/RuleSet.php | 9 +++++++-- tests/Unit/RuleSet/RuleSetTest.php | 13 +++---------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1535719..74afb6a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). - Methods `getLineNumber` and `getColumnNumber` which return a nullable `int` for the following classes: `Comment`, `CSSList`, `SourceException`, `Charset`, `CSSNamespace`, `Import`, - `Rule`, `DeclarationBlock`, `RuleSet`, `CSSFunction`, `Value` (#1225) + `Rule`, `DeclarationBlock`, `RuleSet`, `CSSFunction`, `Value` (#1225, #1263) - `Positionable` interface for CSS items that may have a position (line and perhaps column number) in the parsed CSS (#1221) @@ -55,6 +55,10 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Fixed +- Set line number when `RuleSet::addRule()` called with only column number set + (#1265) +- Ensure first rule added with `RuleSet::addRule()` has valid position (#1262) + ## 8.8.0: Bug fixes and deprecations ### Added diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 1658154c..0110f50e 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -121,14 +121,19 @@ public function addRule(Rule $oRule, $oSibling = null) $oRule->setPosition($oSibling->getLineNo(), $oSibling->getColNo() - 1); } } - if ($oRule->getLineNo() === 0 && $oRule->getColNo() === 0) { + if ($oRule->getLineNumber() === null) { //this node is added manually, give it the next best line + $columnNumber = $oRule->getColNo(); $rules = $this->getRules(); $pos = count($rules); if ($pos > 0) { $last = $rules[$pos - 1]; - $oRule->setPosition($last->getLineNo() + 1, 0); + $oRule->setPosition($last->getLineNo() + 1, $columnNumber); + } else { + $oRule->setPosition(1, $columnNumber); } + } elseif ($oRule->getColumnNumber() === null) { + $oRule->setPosition($oRule->getLineNumber(), 0); } array_splice($this->aRules[$sRule], $iPosition, 0, [$oRule]); diff --git a/tests/Unit/RuleSet/RuleSetTest.php b/tests/Unit/RuleSet/RuleSetTest.php index 303e718b..baa7d316 100644 --- a/tests/Unit/RuleSet/RuleSetTest.php +++ b/tests/Unit/RuleSet/RuleSetTest.php @@ -77,10 +77,6 @@ public function addRuleWithoutSiblingAddsRuleAfterInitialRulesAndSetsValidLineAn array $initialPropertyNames, string $propertyNameToAdd ) { - if ($initialPropertyNames === []) { - self::markTestSkipped('currently broken - first rule added does not have valid line number set'); - } - $subject = new ConcreteRuleSet(); $ruleToAdd = new Rule($propertyNameToAdd); self::setRulesFromPropertyNames($subject, $initialPropertyNames); @@ -106,8 +102,6 @@ public function addRuleWithOnlyLineNumberAddsRuleAndSetsColumnNumberPreservingLi array $initialPropertyNames, string $propertyNameToAdd ) { - self::markTestSkipped('currently broken - does not set column number'); - $subject = new ConcreteRuleSet(); $ruleToAdd = new Rule($propertyNameToAdd); $ruleToAdd->setPosition(42); @@ -128,12 +122,10 @@ public function addRuleWithOnlyLineNumberAddsRuleAndSetsColumnNumberPreservingLi * * @param list $initialPropertyNames */ - public function addRuleWithOnlyColumnNumberAddsRuleAndSetsLineNumberPreservingColumnNumber( + public function addRuleWithOnlyColumnNumberAddsRuleAfterInitialRulesAndSetsLineNumberPreservingColumnNumber( array $initialPropertyNames, string $propertyNameToAdd ) { - self::markTestSkipped('currently broken - does not preserve column number'); - $subject = new ConcreteRuleSet(); $ruleToAdd = new Rule($propertyNameToAdd); $ruleToAdd->setPosition(null, 42); @@ -141,7 +133,8 @@ public function addRuleWithOnlyColumnNumberAddsRuleAndSetsLineNumberPreservingCo $subject->addRule($ruleToAdd); - self::assertContains($ruleToAdd, $subject->getRules()); + $rules = $subject->getRules(); + self::assertSame($ruleToAdd, \end($rules)); self::assertInternalType('int', $ruleToAdd->getLineNumber(), 'line number not set'); self::assertGreaterThanOrEqual(1, $ruleToAdd->getLineNumber(), 'line number not valid'); self::assertSame(42, $ruleToAdd->getColumnNumber(), 'column number not preserved');