From 798e1ee77ee738a1c12cb6a622eba91db3015fdc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 6 Jul 2024 14:07:56 +0200 Subject: [PATCH 001/555] Update phpstan/phpstan requirement from ^1.11.6 to ^1.11.7 (#644) Updates the requirements on [phpstan/phpstan](https://github.com/phpstan/phpstan) to permit the latest version. - [Release notes](https://github.com/phpstan/phpstan/releases) - [Changelog](https://github.com/phpstan/phpstan/blob/1.11.x/CHANGELOG.md) - [Commits](https://github.com/phpstan/phpstan/compare/1.11.6...1.11.7) --- updated-dependencies: - dependency-name: phpstan/phpstan dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 2f85e556..79a5f624 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "codacy/coverage": "^1.4.3", "php-parallel-lint/php-parallel-lint": "^1.4.0", "phpstan/extension-installer": "^1.4.1", - "phpstan/phpstan": "^1.11.6", + "phpstan/phpstan": "^1.11.7", "phpstan/phpstan-phpunit": "^1.4.0", "phpunit/phpunit": "^8.5.38", "rector/rector": "^1.2.0" From 9dc957bcee6de4340f3d12ee2d819d7b2426ada9 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 7 Jul 2024 18:13:56 +0200 Subject: [PATCH 002/555] [CLEANUP] Move the `nullable_type_declaration` rule (#645) It is now listed in the correct section. Also, as we're using the default configuration for this rule, we can use `true` for this instead of duplicating the default configuration. --- config/php-cs-fixer.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/config/php-cs-fixer.php b/config/php-cs-fixer.php index b91c0a13..f016602c 100644 --- a/config/php-cs-fixer.php +++ b/config/php-cs-fixer.php @@ -41,9 +41,6 @@ // function notation 'native_function_invocation' => ['include' => ['@all']], - 'nullable_type_declaration' => [ - 'syntax' => 'question_mark', - ], 'nullable_type_declaration_for_default_null_value' => true, // import @@ -54,6 +51,7 @@ 'combine_consecutive_unsets' => true, 'dir_constant' => true, 'is_null' => true, + 'nullable_type_declaration' => true, // namespace notation 'no_leading_namespace_whitespace' => true, From 4a20d9cdebd2f8cc3c68a4f583d81bc1ddc5a07e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Jul 2024 13:27:53 +0200 Subject: [PATCH 003/555] Update rector/rector requirement from ^1.2.0 to ^1.2.1 (#648) Updates the requirements on [rector/rector](https://github.com/rectorphp/rector) to permit the latest version. - [Release notes](https://github.com/rectorphp/rector/releases) - [Commits](https://github.com/rectorphp/rector/compare/1.2.0...1.2.1) --- updated-dependencies: - dependency-name: rector/rector dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 79a5f624..8116dce7 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "phpstan/phpstan": "^1.11.7", "phpstan/phpstan-phpunit": "^1.4.0", "phpunit/phpunit": "^8.5.38", - "rector/rector": "^1.2.0" + "rector/rector": "^1.2.1" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" From 05430c83f72cdd0a8ed10e893efd95c87dbb02f1 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 22 Jul 2024 21:18:58 +0200 Subject: [PATCH 004/555] [TASK] Use the new contact email address in the code of conduct (#649) --- CODE_OF_CONDUCT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 552b3aae..a73472e9 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -55,7 +55,7 @@ project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at (emogrifier at myintervals dot com). +reported by contacting the project team at (coc-github at myintervals dot com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an From 9dbf03e7feb0af4088b2de0bf98e73299d0f67af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Jul 2024 09:49:53 +0200 Subject: [PATCH 005/555] Update phpstan/phpstan requirement from ^1.11.7 to ^1.11.8 (#650) Updates the requirements on [phpstan/phpstan](https://github.com/phpstan/phpstan) to permit the latest version. - [Release notes](https://github.com/phpstan/phpstan/releases) - [Changelog](https://github.com/phpstan/phpstan/blob/1.12.x/CHANGELOG.md) - [Commits](https://github.com/phpstan/phpstan/compare/1.11.7...1.11.8) --- updated-dependencies: - dependency-name: phpstan/phpstan dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 8116dce7..692e5ebf 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "codacy/coverage": "^1.4.3", "php-parallel-lint/php-parallel-lint": "^1.4.0", "phpstan/extension-installer": "^1.4.1", - "phpstan/phpstan": "^1.11.7", + "phpstan/phpstan": "^1.11.8", "phpstan/phpstan-phpunit": "^1.4.0", "phpunit/phpunit": "^8.5.38", "rector/rector": "^1.2.1" From f51d645f27a9e4c1335c5d925b0669da5c239907 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Jul 2024 15:05:24 +0200 Subject: [PATCH 006/555] Update rector/rector requirement from ^1.2.1 to ^1.2.2 (#651) Updates the requirements on [rector/rector](https://github.com/rectorphp/rector) to permit the latest version. - [Release notes](https://github.com/rectorphp/rector/releases) - [Commits](https://github.com/rectorphp/rector/compare/1.2.1...1.2.2) --- updated-dependencies: - dependency-name: rector/rector dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 692e5ebf..0e00c3a4 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "phpstan/phpstan": "^1.11.8", "phpstan/phpstan-phpunit": "^1.4.0", "phpunit/phpunit": "^8.5.38", - "rector/rector": "^1.2.1" + "rector/rector": "^1.2.2" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" From 0afc9ebefd6fb60ffb807b56a252c0e68a37d13b Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 30 Jul 2024 00:32:00 +0200 Subject: [PATCH 007/555] [TASK] Update the code of conduct to version 2.1 (#652) Copy version 2.1 of the contributor convenant code of conduct without any changes except those: - use our contact email address - drop links additional resources related to the contributor covenant CoC https://www.contributor-covenant.org/version/2/1/code_of_conduct/ --- CODE_OF_CONDUCT.md | 141 +++++++++++++++++++++++++++++---------------- 1 file changed, 92 insertions(+), 49 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index a73472e9..d50e40b4 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,76 +1,119 @@ -# Contributor Code of Conduct +# Contributor Covenant Code of Conduct ## Our Pledge -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, -body size, disability, ethnicity, gender identity and expression, level of -experience, nationality, personal appearance, race, religion, or sexual +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + ## Our Standards -Examples of behavior that contributes to creating a positive environment -include: +Examples of behavior that contributes to a positive environment for our +community include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community -Examples of unacceptable behavior by participants include: +Examples of unacceptable behavior include: -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission +* Publishing others' private information, such as a physical or email address, + without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting -## Our Responsibilities +## Enforcement Responsibilities -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. ## Scope -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an -appointed representative at an online or offline event. Representation of a -project may be further defined and clarified by project maintainers. +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at (coc-github at myintervals dot com). -All complaints will be reviewed and investigated and will result in a response -that is deemed necessary and appropriate to the circumstances. The project team -is obligated to maintain confidentiality with regard to the reporter of an -incident. Further details of specific enforcement policies may be posted -separately. +reported to the community leaders responsible for enforcement at +(coc-github at myintervals dot com). +All complaints will be reviewed and investigated promptly and fairly. -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. -## Attribution +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 1.4, available at -[http://contributor-covenant.org/version/1/4/][version]. +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), +version 2.1, available at +https://www.contributor-covenant.org/version/2/1/code_of_conduct.html. From 5b689497cb92a1371ad276348e73abf5791a67ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Aug 2024 13:28:37 +0200 Subject: [PATCH 008/555] Update phpstan/phpstan requirement from ^1.11.8 to ^1.11.9 (#653) Updates the requirements on [phpstan/phpstan](https://github.com/phpstan/phpstan) to permit the latest version. - [Release notes](https://github.com/phpstan/phpstan/releases) - [Changelog](https://github.com/phpstan/phpstan/blob/1.12.x/CHANGELOG.md) - [Commits](https://github.com/phpstan/phpstan/compare/1.11.8...1.11.9) --- updated-dependencies: - dependency-name: phpstan/phpstan dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 0e00c3a4..4d4dbaf1 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "codacy/coverage": "^1.4.3", "php-parallel-lint/php-parallel-lint": "^1.4.0", "phpstan/extension-installer": "^1.4.1", - "phpstan/phpstan": "^1.11.8", + "phpstan/phpstan": "^1.11.9", "phpstan/phpstan-phpunit": "^1.4.0", "phpunit/phpunit": "^8.5.38", "rector/rector": "^1.2.2" From 6deca21d36bd4b90434f227af1dfa8463a27cb0b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Aug 2024 19:18:23 +0200 Subject: [PATCH 009/555] Update phpstan/phpstan requirement from ^1.11.9 to ^1.11.10 (#654) Updates the requirements on [phpstan/phpstan](https://github.com/phpstan/phpstan) to permit the latest version. - [Release notes](https://github.com/phpstan/phpstan/releases) - [Changelog](https://github.com/phpstan/phpstan/blob/1.12.x/CHANGELOG.md) - [Commits](https://github.com/phpstan/phpstan/compare/1.11.9...1.11.10) --- updated-dependencies: - dependency-name: phpstan/phpstan dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 4d4dbaf1..3af2c119 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "codacy/coverage": "^1.4.3", "php-parallel-lint/php-parallel-lint": "^1.4.0", "phpstan/extension-installer": "^1.4.1", - "phpstan/phpstan": "^1.11.9", + "phpstan/phpstan": "^1.11.10", "phpstan/phpstan-phpunit": "^1.4.0", "phpunit/phpunit": "^8.5.38", "rector/rector": "^1.2.2" From 6687a6732d88a1ced3d95d9391f8e1ec7c0fdce3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Aug 2024 10:09:16 +0200 Subject: [PATCH 010/555] Update rector/rector requirement from ^1.2.2 to ^1.2.3 (#655) Updates the requirements on [rector/rector](https://github.com/rectorphp/rector) to permit the latest version. - [Release notes](https://github.com/rectorphp/rector/releases) - [Commits](https://github.com/rectorphp/rector/compare/1.2.2...1.2.3) --- updated-dependencies: - dependency-name: rector/rector dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3af2c119..83df17bd 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "phpstan/phpstan": "^1.11.10", "phpstan/phpstan-phpunit": "^1.4.0", "phpunit/phpunit": "^8.5.38", - "rector/rector": "^1.2.2" + "rector/rector": "^1.2.3" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" From daafb959d0133ed071f266e17b858fc998ab9880 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Aug 2024 14:41:20 +0200 Subject: [PATCH 011/555] Update phpstan/phpstan requirement from ^1.11.10 to ^1.11.11 (#656) Updates the requirements on [phpstan/phpstan](https://github.com/phpstan/phpstan) to permit the latest version. - [Release notes](https://github.com/phpstan/phpstan/releases) - [Changelog](https://github.com/phpstan/phpstan/blob/1.12.x/CHANGELOG.md) - [Commits](https://github.com/phpstan/phpstan/compare/1.11.10...1.11.11) --- updated-dependencies: - dependency-name: phpstan/phpstan dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 83df17bd..266416fb 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "codacy/coverage": "^1.4.3", "php-parallel-lint/php-parallel-lint": "^1.4.0", "phpstan/extension-installer": "^1.4.1", - "phpstan/phpstan": "^1.11.10", + "phpstan/phpstan": "^1.11.11", "phpstan/phpstan-phpunit": "^1.4.0", "phpunit/phpunit": "^8.5.38", "rector/rector": "^1.2.3" From 5a25712156bbc6240d81fadedcecc4dce226bff3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Aug 2024 13:49:38 +0200 Subject: [PATCH 012/555] Update rector/rector requirement from ^1.2.3 to ^1.2.4 (#658) Updates the requirements on [rector/rector](https://github.com/rectorphp/rector) to permit the latest version. - [Release notes](https://github.com/rectorphp/rector/releases) - [Commits](https://github.com/rectorphp/rector/compare/1.2.3...1.2.4) --- updated-dependencies: - dependency-name: rector/rector dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 266416fb..ddc7eb13 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "phpstan/phpstan": "^1.11.11", "phpstan/phpstan-phpunit": "^1.4.0", "phpunit/phpunit": "^8.5.38", - "rector/rector": "^1.2.3" + "rector/rector": "^1.2.4" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" From e4c66f62c5df3f53f48d82cb30b20b3007af7574 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 25 Aug 2024 23:25:13 +0200 Subject: [PATCH 013/555] [BUGFIX] Fix comment parsing to support multiple comments (#672) 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 Co-authored-by: Daniel Ziegenberg --- CHANGELOG.md | 1 + src/Rule/Rule.php | 5 ++++- tests/ParserTest.php | 18 +++++++++++++++++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed390901..e4386c42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Fixed +- Fix comment parsing to support multiple comments (#672) - Fix undefined local variable in `CalcFunction::parse()` (#593) - 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) diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index ca9f380c..9514be29 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -105,7 +105,10 @@ public static function parse(ParserState $oParserState): Rule while ($oParserState->comes(';')) { $oParserState->consume(';'); } - $oParserState->consumeWhiteSpace(); + + while (\preg_match('/\\s/isSu', $oParserState->peek()) === 1) { + $oParserState->consume(1); + } return $oRule; } diff --git a/tests/ParserTest.php b/tests/ParserTest.php index 2c1c7287..dde4aaca 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -1159,7 +1159,7 @@ public function commentExtracting(): void /** * @test */ - public function flatCommentExtracting(): void + public function flatCommentExtractingOneComment(): void { $parser = new Parser('div {/*Find Me!*/left:10px; text-align:left;}'); $doc = $parser->parse(); @@ -1169,6 +1169,22 @@ public function flatCommentExtracting(): void self::assertCount(1, $comments); self::assertSame('Find Me!', $comments[0]->getComment()); } + /** + * @test + */ + public function flatCommentExtractingTwoComments(): void + { + $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 893d2046b3252cc6c1000746fe7a499204a3b883 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 26 Aug 2024 00:38:07 +0200 Subject: [PATCH 014/555] [FEATURE] Add PHP 8.4 to the CI matrix (#657) --- .github/workflows/ci.yml | 4 ++-- CHANGELOG.md | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 76a2d355..960f491a 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: [ '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3' ] + php-version: [ '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4' ] steps: - name: Checkout @@ -59,7 +59,7 @@ jobs: strategy: fail-fast: false matrix: - php-version: [ '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3' ] + php-version: [ '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 e4386c42..08e271b2 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 official support for PHP 8.4 (#657) - Support arithmetic operators in CSS function arguments (#607) - Add support for inserting an item in a CSS list (#545) - Add a class diagram to the README (#482) From a2aca28dfc958842c9d2c9ba700b5c01b216212d Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 26 Aug 2024 00:41:02 +0200 Subject: [PATCH 015/555] [BUGFIX] Do not try to cover an interface in tests (#660) Only classes and traits can be covered by code coverage. Trying to cover an interface causes PHPUnit to issue a warning. --- tests/Comment/CommentTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Comment/CommentTest.php b/tests/Comment/CommentTest.php index 79374afb..e860cda8 100644 --- a/tests/Comment/CommentTest.php +++ b/tests/Comment/CommentTest.php @@ -10,7 +10,6 @@ /** * @covers \Sabberworm\CSS\Comment\Comment - * @covers \Sabberworm\CSS\Comment\Commentable * @covers \Sabberworm\CSS\OutputFormat * @covers \Sabberworm\CSS\OutputFormatter */ From 0bf5fe907389fa2e5627859258a716f77ecc62b5 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 26 Aug 2024 00:53:07 +0200 Subject: [PATCH 016/555] [TASK] Link the contribution guidelines from the README (#661) Part of #489 --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 597949e6..6810169b 100644 --- a/README.md +++ b/README.md @@ -779,6 +779,13 @@ classDiagram ValueList --> "*" Value : aComponents ``` +## Contributing + +Contributions in the form of bug reports, feature requests, or pull requests are +more than welcome. :pray: Please have a look at our +[contribution guidelines](.github/CONTRIBUTING.md) to learn more about how to +contribute to PHP-CSS-Parser. + ## Contributors/Thanks to * [oliverklee](https://github.com/oliverklee) for lots of refactorings, code modernizations and CI integrations From 43b02473bdaf0284a4d04323707d961120527b32 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 26 Aug 2024 02:28:47 +0200 Subject: [PATCH 017/555] [TASK] Avoid Hungarian notation in a TestCase (#666) --- tests/Comment/CommentTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Comment/CommentTest.php b/tests/Comment/CommentTest.php index e860cda8..32cf9195 100644 --- a/tests/Comment/CommentTest.php +++ b/tests/Comment/CommentTest.php @@ -153,7 +153,7 @@ public function keepCommentsInOutput(): void */ public function stripCommentsFromOutput(): void { - $oCss = TestsParserTest::parsedStructureForFile('comments'); + $css = TestsParserTest::parsedStructureForFile('comments'); self::assertSame(' @import url("some/url.css") screen; @@ -166,12 +166,12 @@ public function stripCommentsFromOutput(): void position: absolute; } } -', $oCss->render(OutputFormat::createPretty()->setRenderComments(false))); +', $css->render(OutputFormat::createPretty()->setRenderComments(false))); self::assertSame( '@import url("some/url.css") screen;' . '.foo,#bar{background-color:#000;}' . '@media screen{#foo.bar{position:absolute;}}', - $oCss->render(OutputFormat::createCompact()) + $css->render(OutputFormat::createCompact()) ); } } From 0d43c786c7ba2a52c12e67213e78fda5f88db0bc Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 26 Aug 2024 02:36:05 +0200 Subject: [PATCH 018/555] [TASK] Fix typos in comments and names and improve some comments (#667) --- src/CSSList/CSSList.php | 4 ++-- src/Value/Value.php | 2 +- tests/CSSList/AtRuleBlockListTest.php | 6 +++--- tests/CSSList/DocumentTest.php | 4 ++-- tests/ParserTest.php | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index a99a6049..5d22bbe0 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -306,8 +306,8 @@ public function insertBefore($item, $sibling): void * Removes an item from the CSS list. * * @param RuleSet|Import|Charset|CSSList $oItemToRemove - * May be a RuleSet (most likely a DeclarationBlock), a Import, - * a Charset or another CSSList (most likely a MediaQuery) + * May be a `RuleSet` (most likely a `DeclarationBlock`), an `Import`, + * a `Charset` or another `CSSList` (most likely a `MediaQuery`) * * @return bool whether the item was removed */ diff --git a/src/Value/Value.php b/src/Value/Value.php index 5fb6e3d0..39637f3d 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -200,7 +200,7 @@ private static function parseUnicodeRangeValue(ParserState $oParserState): strin $oParserState->consume('U+'); do { if ($oParserState->comes('-')) { - $iCodepointMaxLength = 13; // Max length is 2 six digit code points + the dash(-) between them + $iCodepointMaxLength = 13; // Max length is 2 six-digit code points + the dash(-) between them } $sRange .= $oParserState->consume(1); } while (\strlen($sRange) < $iCodepointMaxLength && \preg_match('/[A-Fa-f0-9\\?-]/', $oParserState->peek())); diff --git a/tests/CSSList/AtRuleBlockListTest.php b/tests/CSSList/AtRuleBlockListTest.php index 5398bfba..33c88bb6 100644 --- a/tests/CSSList/AtRuleBlockListTest.php +++ b/tests/CSSList/AtRuleBlockListTest.php @@ -28,7 +28,7 @@ public static function provideMinWidthMediaRule(): array /** * @return array */ - public static function provideSyntacticlyCorrectAtRule(): array + public static function provideSyntacticallyCorrectAtRule(): array { return [ 'media print' => ['@media print { html { background: white; color: black; } }'], @@ -107,9 +107,9 @@ public function parsesArgumentsOfMediaQueries(string $css): void * @test * * @dataProvider provideMinWidthMediaRule - * @dataProvider provideSyntacticlyCorrectAtRule + * @dataProvider provideSyntacticallyCorrectAtRule */ - public function parsesSyntacticlyCorrectAtRuleInStrictMode(string $css): void + public function parsesSyntacticallyCorrectAtRuleInStrictMode(string $css): void { $contents = (new Parser($css, Settings::create()->beStrict()))->parse()->getContents(); diff --git a/tests/CSSList/DocumentTest.php b/tests/CSSList/DocumentTest.php index e700edba..8a641167 100644 --- a/tests/CSSList/DocumentTest.php +++ b/tests/CSSList/DocumentTest.php @@ -89,7 +89,7 @@ public function setContentsReplacesContentsSetInPreviousCall(): void /** * @test */ - public function insertContentBeforeInsertsContentBeforeSibbling(): void + public function insertContentBeforeInsertsContentBeforeSibling(): void { $bogusOne = new DeclarationBlock(); $bogusOne->setSelectors('.bogus-one'); @@ -115,7 +115,7 @@ public function insertContentBeforeInsertsContentBeforeSibbling(): void /** * @test */ - public function insertContentBeforeAppendsIfSibblingNotFound(): void + public function insertContentBeforeAppendsIfSiblingNotFound(): void { $bogusOne = new DeclarationBlock(); $bogusOne->setSelectors('.bogus-one'); diff --git a/tests/ParserTest.php b/tests/ParserTest.php index dde4aaca..2262224f 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -71,7 +71,7 @@ public function files(): void } if (\strpos($sFileName, '-') === 0) { // Either a file which SHOULD fail (at least in strict mode) - // or a future test of a as-of-now missing feature + // or a future test of an as-of-now missing feature continue; } $oParser = new Parser(\file_get_contents($sDirectory . '/' . $sFileName)); From f16060757cd8471e5193fccdeb9ffcb3d7330108 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 13:43:29 +0200 Subject: [PATCH 019/555] Update phpstan/extension-installer requirement from ^1.4.1 to ^1.4.2 (#673) Updates the requirements on [phpstan/extension-installer](https://github.com/phpstan/extension-installer) to permit the latest version. - [Release notes](https://github.com/phpstan/extension-installer/releases) - [Commits](https://github.com/phpstan/extension-installer/compare/1.4.1...1.4.2) --- updated-dependencies: - dependency-name: phpstan/extension-installer dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index ddc7eb13..2456f7e6 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "require-dev": { "codacy/coverage": "^1.4.3", "php-parallel-lint/php-parallel-lint": "^1.4.0", - "phpstan/extension-installer": "^1.4.1", + "phpstan/extension-installer": "^1.4.2", "phpstan/phpstan": "^1.11.11", "phpstan/phpstan-phpunit": "^1.4.0", "phpunit/phpunit": "^8.5.38", From 88ddf9441bbc9d47fb2b8f8a0594b9a7d29216a4 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 27 Aug 2024 02:00:10 +0200 Subject: [PATCH 020/555] [BUGFIX] Fix type errors in PHP strict mode (#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 08e271b2..088c8550 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Fixed +- Fix type errors in PHP strict mode (#664) - Fix comment parsing to support multiple comments (#672) - Fix undefined local variable in `CalcFunction::parse()` (#593) - Fix PHP notice caused by parsing invalid color values having less than 6 characters (#485) diff --git a/src/Value/Size.php b/src/Value/Size.php index bf40f4a0..179e739f 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -204,7 +204,7 @@ public function render(OutputFormat $oOutputFormat): string $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 ?? ''); } diff --git a/tests/ParserTest.php b/tests/ParserTest.php index 2262224f..e25c789d 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -278,7 +278,7 @@ public function specificity(): void 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 8daa01abf6eb42f1febdfae56a4bdd1a9655d57c Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 27 Aug 2024 02:34:00 +0200 Subject: [PATCH 021/555] [TASK] Avoid Hungarian notation in a testcase (#678) Also rename some variables to be more precise and/or avoid abbreviation. --- tests/RuleSet/DeclarationBlockTest.php | 174 ++++++++++++------------- 1 file changed, 87 insertions(+), 87 deletions(-) diff --git a/tests/RuleSet/DeclarationBlockTest.php b/tests/RuleSet/DeclarationBlockTest.php index e94f1c54..f565aa3c 100644 --- a/tests/RuleSet/DeclarationBlockTest.php +++ b/tests/RuleSet/DeclarationBlockTest.php @@ -19,12 +19,12 @@ final class DeclarationBlockTest extends TestCase */ public function expandBorderShorthand(string $sCss, string $sExpected): void { - $oParser = new Parser($sCss); - $oDoc = $oParser->parse(); - foreach ($oDoc->getAllDeclarationBlocks() as $oDeclaration) { - $oDeclaration->expandBorderShorthand(); + $parser = new Parser($sCss); + $document = $parser->parse(); + foreach ($document->getAllDeclarationBlocks() as $declarationBlock) { + $declarationBlock->expandBorderShorthand(); } - self::assertSame(\trim((string) $oDoc), $sExpected); + self::assertSame(\trim((string) $document), $sExpected); } /** @@ -49,12 +49,12 @@ public static function expandBorderShorthandProvider(): array */ public function expandFontShorthand(string $sCss, string $sExpected): void { - $oParser = new Parser($sCss); - $oDoc = $oParser->parse(); - foreach ($oDoc->getAllDeclarationBlocks() as $oDeclaration) { - $oDeclaration->expandFontShorthand(); + $parser = new Parser($sCss); + $document = $parser->parse(); + foreach ($document->getAllDeclarationBlocks() as $declarationBlock) { + $declarationBlock->expandFontShorthand(); } - self::assertSame(\trim((string) $oDoc), $sExpected); + self::assertSame(\trim((string) $document), $sExpected); } /** @@ -102,12 +102,12 @@ public static function expandFontShorthandProvider(): array */ public function expandBackgroundShorthand(string $sCss, string $sExpected): void { - $oParser = new Parser($sCss); - $oDoc = $oParser->parse(); - foreach ($oDoc->getAllDeclarationBlocks() as $oDeclaration) { - $oDeclaration->expandBackgroundShorthand(); + $parser = new Parser($sCss); + $document = $parser->parse(); + foreach ($document->getAllDeclarationBlocks() as $declarationBlock) { + $declarationBlock->expandBackgroundShorthand(); } - self::assertSame(\trim((string) $oDoc), $sExpected); + self::assertSame(\trim((string) $document), $sExpected); } /** @@ -152,12 +152,12 @@ public static function expandBackgroundShorthandProvider(): array */ public function expandDimensionsShorthand(string $sCss, string $sExpected): void { - $oParser = new Parser($sCss); - $oDoc = $oParser->parse(); - foreach ($oDoc->getAllDeclarationBlocks() as $oDeclaration) { - $oDeclaration->expandDimensionsShorthand(); + $parser = new Parser($sCss); + $document = $parser->parse(); + foreach ($document->getAllDeclarationBlocks() as $declarationBlock) { + $declarationBlock->expandDimensionsShorthand(); } - self::assertSame(\trim((string) $oDoc), $sExpected); + self::assertSame(\trim((string) $document), $sExpected); } /** @@ -187,12 +187,12 @@ public static function expandDimensionsShorthandProvider(): array */ public function createBorderShorthand(string $sCss, string $sExpected): void { - $oParser = new Parser($sCss); - $oDoc = $oParser->parse(); - foreach ($oDoc->getAllDeclarationBlocks() as $oDeclaration) { - $oDeclaration->createBorderShorthand(); + $parser = new Parser($sCss); + $document = $parser->parse(); + foreach ($document->getAllDeclarationBlocks() as $declarationBlock) { + $declarationBlock->createBorderShorthand(); } - self::assertSame(\trim((string) $oDoc), $sExpected); + self::assertSame(\trim((string) $document), $sExpected); } /** @@ -215,12 +215,12 @@ public static function createBorderShorthandProvider(): array */ public function createFontShorthand(string $sCss, string $sExpected): void { - $oParser = new Parser($sCss); - $oDoc = $oParser->parse(); - foreach ($oDoc->getAllDeclarationBlocks() as $oDeclaration) { - $oDeclaration->createFontShorthand(); + $parser = new Parser($sCss); + $document = $parser->parse(); + foreach ($document->getAllDeclarationBlocks() as $declarationBlock) { + $declarationBlock->createFontShorthand(); } - self::assertSame(\trim((string) $oDoc), $sExpected); + self::assertSame(\trim((string) $document), $sExpected); } /** @@ -255,12 +255,12 @@ public static function createFontShorthandProvider(): array */ public function createDimensionsShorthand(string $sCss, string $sExpected): void { - $oParser = new Parser($sCss); - $oDoc = $oParser->parse(); - foreach ($oDoc->getAllDeclarationBlocks() as $oDeclaration) { - $oDeclaration->createDimensionsShorthand(); + $parser = new Parser($sCss); + $document = $parser->parse(); + foreach ($document->getAllDeclarationBlocks() as $declarationBlock) { + $declarationBlock->createDimensionsShorthand(); } - self::assertSame(\trim((string) $oDoc), $sExpected); + self::assertSame(\trim((string) $document), $sExpected); } /** @@ -290,12 +290,12 @@ public static function createDimensionsShorthandProvider(): array */ public function createBackgroundShorthand(string $sCss, string $sExpected): void { - $oParser = new Parser($sCss); - $oDoc = $oParser->parse(); - foreach ($oDoc->getAllDeclarationBlocks() as $oDeclaration) { - $oDeclaration->createBackgroundShorthand(); + $parser = new Parser($sCss); + $document = $parser->parse(); + foreach ($document->getAllDeclarationBlocks() as $declarationBlock) { + $declarationBlock->createBackgroundShorthand(); } - self::assertSame(\trim((string) $oDoc), $sExpected); + self::assertSame(\trim((string) $document), $sExpected); } /** @@ -337,20 +337,20 @@ public static function createBackgroundShorthandProvider(): array public function overrideRules(): void { $sCss = '.wrapper { left: 10px; text-align: left; }'; - $oParser = new Parser($sCss); - $oDoc = $oParser->parse(); - $oRule = new Rule('right'); - $oRule->setValue('-10px'); - $aContents = $oDoc->getContents(); - $oWrapper = $aContents[0]; - - self::assertCount(2, $oWrapper->getRules()); - $aContents[0]->setRules([$oRule]); - - $aRules = $oWrapper->getRules(); - self::assertCount(1, $aRules); - self::assertSame('right', $aRules[0]->getRule()); - self::assertSame('-10px', $aRules[0]->getValue()); + $parser = new Parser($sCss); + $document = $parser->parse(); + $rule = new Rule('right'); + $rule->setValue('-10px'); + $contents = $document->getContents(); + $wrapper = $contents[0]; + + self::assertCount(2, $wrapper->getRules()); + $contents[0]->setRules([$rule]); + + $rules = $wrapper->getRules(); + self::assertCount(1, $rules); + self::assertSame('right', $rules[0]->getRule()); + self::assertSame('-10px', $rules[0]->getValue()); } /** @@ -359,43 +359,43 @@ public function overrideRules(): void public function ruleInsertion(): void { $sCss = '.wrapper { left: 10px; text-align: left; }'; - $oParser = new Parser($sCss); - $oDoc = $oParser->parse(); - $aContents = $oDoc->getContents(); - $oWrapper = $aContents[0]; + $parser = new Parser($sCss); + $document = $parser->parse(); + $contents = $document->getContents(); + $wrapper = $contents[0]; - $oFirst = $oWrapper->getRules('left'); - self::assertCount(1, $oFirst); - $oFirst = $oFirst[0]; + $leftRules = $wrapper->getRules('left'); + self::assertCount(1, $leftRules); + $firstLeftRule = $leftRules[0]; - $oSecond = $oWrapper->getRules('text-'); - self::assertCount(1, $oSecond); - $oSecond = $oSecond[0]; + $textRules = $wrapper->getRules('text-'); + self::assertCount(1, $textRules); + $firstTextRule = $textRules[0]; - $oBefore = new Rule('left'); - $oBefore->setValue(new Size(16, 'em')); + $leftPrefixRule = new Rule('left'); + $leftPrefixRule->setValue(new Size(16, 'em')); - $oMiddle = new Rule('text-align'); - $oMiddle->setValue(new Size(1)); + $textAlignRule = new Rule('text-align'); + $textAlignRule->setValue(new Size(1)); - $oAfter = new Rule('border-bottom-width'); - $oAfter->setValue(new Size(1, 'px')); + $borderBottomRule = new Rule('border-bottom-width'); + $borderBottomRule->setValue(new Size(1, 'px')); - $oWrapper->addRule($oAfter); - $oWrapper->addRule($oBefore, $oFirst); - $oWrapper->addRule($oMiddle, $oSecond); + $wrapper->addRule($borderBottomRule); + $wrapper->addRule($leftPrefixRule, $firstLeftRule); + $wrapper->addRule($textAlignRule, $firstTextRule); - $aRules = $oWrapper->getRules(); + $rules = $wrapper->getRules(); - self::assertSame($oBefore, $aRules[0]); - self::assertSame($oFirst, $aRules[1]); - self::assertSame($oMiddle, $aRules[2]); - self::assertSame($oSecond, $aRules[3]); - self::assertSame($oAfter, $aRules[4]); + self::assertSame($leftPrefixRule, $rules[0]); + self::assertSame($firstLeftRule, $rules[1]); + self::assertSame($textAlignRule, $rules[2]); + self::assertSame($firstTextRule, $rules[3]); + self::assertSame($borderBottomRule, $rules[4]); self::assertSame( '.wrapper {left: 16em;left: 10px;text-align: 1;text-align: left;border-bottom-width: 1px;}', - $oDoc->render() + $document->render() ); } @@ -407,14 +407,14 @@ public function ruleInsertion(): void public function orderOfElementsMatchingOriginalOrderAfterExpandingShorthands(): void { $sCss = '.rule{padding:5px;padding-top: 20px}'; - $oParser = new Parser($sCss); - $oDoc = $oParser->parse(); - $aDocs = $oDoc->getAllDeclarationBlocks(); + $parser = new Parser($sCss); + $document = $parser->parse(); + $declarationBlocks = $document->getAllDeclarationBlocks(); - self::assertCount(1, $aDocs); + self::assertCount(1, $declarationBlocks); - $oDeclaration = \array_pop($aDocs); - $oDeclaration->expandShorthands(); + $lastDeclarationBlock = \array_pop($declarationBlocks); + $lastDeclarationBlock->expandShorthands(); self::assertEquals( [ @@ -423,7 +423,7 @@ public function orderOfElementsMatchingOriginalOrderAfterExpandingShorthands(): 'padding-bottom' => 'padding-bottom: 5px;', 'padding-left' => 'padding-left: 5px;', ], - \array_map('strval', $oDeclaration->getRulesAssoc()) + \array_map('strval', $lastDeclarationBlock->getRulesAssoc()) ); } } From a8e91c9dcba54c69b22d3266a568eea817f93b7a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Aug 2024 13:43:36 +0200 Subject: [PATCH 022/555] Update phpstan/phpstan requirement from ^1.11.11 to ^1.12.0 (#685) Updates the requirements on [phpstan/phpstan](https://github.com/phpstan/phpstan) to permit the latest version. - [Release notes](https://github.com/phpstan/phpstan/releases) - [Changelog](https://github.com/phpstan/phpstan/blob/1.12.x/CHANGELOG.md) - [Commits](https://github.com/phpstan/phpstan/compare/1.11.11...1.12.0) --- updated-dependencies: - dependency-name: phpstan/phpstan dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 2456f7e6..050b9cb3 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "codacy/coverage": "^1.4.3", "php-parallel-lint/php-parallel-lint": "^1.4.0", "phpstan/extension-installer": "^1.4.2", - "phpstan/phpstan": "^1.11.11", + "phpstan/phpstan": "^1.12.0", "phpstan/phpstan-phpunit": "^1.4.0", "phpunit/phpunit": "^8.5.38", "rector/rector": "^1.2.4" From b0d6c4e5d9e78cd7545570a3b02ca6ef0d641c62 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 28 Aug 2024 01:22:05 +0200 Subject: [PATCH 023/555] [TASK] Document the general contribution workflow (#662) Part of #489 --- .github/CONTRIBUTING.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 19269ae9..fd092560 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -10,3 +10,26 @@ When you contribute, please take the following things into account: Please note that this project is released with a [Contributor Code of Conduct](../CODE_OF_CONDUCT.md). By participating in this project, you agree to abide by its terms. + +## General workflow + +This is the workflow for contributing changes to this project:: + +1. [Fork the Git repository](https://docs.github.com/en/get-started/exploring-projects-on-github/contributing-to-a-project). +1. Clone your forked repository locally and install the development + dependencies. +1. Create a local branch for your changes. +1. Add unit tests for your changes. + These tests should fail without your changes. +1. Add your changes. Your added unit tests now should pass, and no other tests + should be broken. Check that your changes follow the same coding style as the + rest of the project. +1. Add a changelog entry, newest on top. +1. Commit and push your changes. +1. [Create a pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests) + for your changes. +1. Check that the CI build is green. (If it is not, fix the problems listed.) + Please note that for first-time contributors, you will need to wait for a + maintainer to allow your CI build to run. +1. Wait for a review by the maintainers. +1. Polish your changes as needed until they are ready to be merged. From f587f1ab5d78bdaf7963a410b16b5283d7632ade Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 28 Aug 2024 02:18:21 +0200 Subject: [PATCH 024/555] [TASK] Avoid Hungarian notation in a testcase (#684) --- tests/RuleSet/LenientParsingTest.php | 70 ++++++++++++++-------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/tests/RuleSet/LenientParsingTest.php b/tests/RuleSet/LenientParsingTest.php index 3d670f74..c2209a7e 100644 --- a/tests/RuleSet/LenientParsingTest.php +++ b/tests/RuleSet/LenientParsingTest.php @@ -29,9 +29,9 @@ public function faultToleranceOff(): void { $this->expectException(UnexpectedTokenException::class); - $sFile = __DIR__ . '/../fixtures/-fault-tolerance.css'; - $oParser = new Parser(\file_get_contents($sFile), Settings::create()->beStrict()); - $oParser->parse(); + $pathToFile = __DIR__ . '/../fixtures/-fault-tolerance.css'; + $parser = new Parser(\file_get_contents($pathToFile), Settings::create()->beStrict()); + $parser->parse(); } /** @@ -39,13 +39,13 @@ public function faultToleranceOff(): void */ public function faultToleranceOn(): void { - $sFile = __DIR__ . '/../fixtures/-fault-tolerance.css'; - $oParser = new Parser(\file_get_contents($sFile), Settings::create()->withLenientParsing(true)); - $oResult = $oParser->parse(); + $pathToFile = __DIR__ . '/../fixtures/-fault-tolerance.css'; + $parser = new Parser(\file_get_contents($pathToFile), Settings::create()->withLenientParsing(true)); + $result = $parser->parse(); self::assertSame( '.test1 {}' . "\n" . '.test2 {hello: 2.2;hello: 2000000000000.2;}' . "\n" . '#test {}' . "\n" . '#test2 {help: none;}', - $oResult->render() + $result->render() ); } @@ -56,9 +56,9 @@ public function endToken(): void { $this->expectException(UnexpectedTokenException::class); - $sFile = __DIR__ . '/../fixtures/-end-token.css'; - $oParser = new Parser(\file_get_contents($sFile), Settings::create()->beStrict()); - $oParser->parse(); + $pathToFile = __DIR__ . '/../fixtures/-end-token.css'; + $parser = new Parser(\file_get_contents($pathToFile), Settings::create()->beStrict()); + $parser->parse(); } /** @@ -68,9 +68,9 @@ public function endToken2(): void { $this->expectException(UnexpectedTokenException::class); - $sFile = __DIR__ . '/../fixtures/-end-token-2.css'; - $oParser = new Parser(\file_get_contents($sFile), Settings::create()->beStrict()); - $oParser->parse(); + $pathToFile = __DIR__ . '/../fixtures/-end-token-2.css'; + $parser = new Parser(\file_get_contents($pathToFile), Settings::create()->beStrict()); + $parser->parse(); } /** @@ -78,10 +78,10 @@ public function endToken2(): void */ public function endTokenPositive(): void { - $sFile = __DIR__ . '/../fixtures/-end-token.css'; - $oParser = new Parser(\file_get_contents($sFile), Settings::create()->withLenientParsing(true)); - $oResult = $oParser->parse(); - self::assertSame('', $oResult->render()); + $pathToFile = __DIR__ . '/../fixtures/-end-token.css'; + $parser = new Parser(\file_get_contents($pathToFile), Settings::create()->withLenientParsing(true)); + $result = $parser->parse(); + self::assertSame('', $result->render()); } /** @@ -89,12 +89,12 @@ public function endTokenPositive(): void */ public function endToken2Positive(): void { - $sFile = __DIR__ . '/../fixtures/-end-token-2.css'; - $oParser = new Parser(\file_get_contents($sFile), Settings::create()->withLenientParsing(true)); - $oResult = $oParser->parse(); + $pathToFile = __DIR__ . '/../fixtures/-end-token-2.css'; + $parser = new Parser(\file_get_contents($pathToFile), Settings::create()->withLenientParsing(true)); + $result = $parser->parse(); self::assertSame( '#home .bg-layout {background-image: url("/bundles/main/img/bg1.png?5");}', - $oResult->render() + $result->render() ); } @@ -104,13 +104,13 @@ public function endToken2Positive(): void public function localeTrap(): void { \setlocale(LC_ALL, 'pt_PT', 'no'); - $sFile = __DIR__ . '/../fixtures/-fault-tolerance.css'; - $oParser = new Parser(\file_get_contents($sFile), Settings::create()->withLenientParsing(true)); - $oResult = $oParser->parse(); + $pathToFile = __DIR__ . '/../fixtures/-fault-tolerance.css'; + $parser = new Parser(\file_get_contents($pathToFile), Settings::create()->withLenientParsing(true)); + $result = $parser->parse(); self::assertSame( '.test1 {}' . "\n" . '.test2 {hello: 2.2;hello: 2000000000000.2;}' . "\n" . '#test {}' . "\n" . '#test2 {help: none;}', - $oResult->render() + $result->render() ); } @@ -119,9 +119,9 @@ public function localeTrap(): void */ public function caseInsensitivity(): void { - $sFile = __DIR__ . '/../fixtures/case-insensitivity.css'; - $oParser = new Parser(\file_get_contents($sFile)); - $oResult = $oParser->parse(); + $pathToFile = __DIR__ . '/../fixtures/case-insensitivity.css'; + $parser = new Parser(\file_get_contents($pathToFile)); + $result = $parser->parse(); self::assertSame( '@charset "utf-8";' . "\n" @@ -129,7 +129,7 @@ public function caseInsensitivity(): void . "\n@media screen {}" . "\n#myid {case: insensitive !important;frequency: 30Hz;font-size: 1em;color: #ff0;" . 'color: hsl(40,40%,30%);font-family: Arial;}', - $oResult->render() + $result->render() ); } @@ -138,9 +138,9 @@ public function caseInsensitivity(): void */ public function cssWithInvalidColorStillGetsParsedAsDocument(): void { - $sFile = __DIR__ . '/../fixtures/invalid-color.css'; - $oParser = new Parser(\file_get_contents($sFile), Settings::create()->withLenientParsing(true)); - $result = $oParser->parse(); + $pathToFile = __DIR__ . '/../fixtures/invalid-color.css'; + $parser = new Parser(\file_get_contents($pathToFile), Settings::create()->withLenientParsing(true)); + $result = $parser->parse(); self::assertInstanceOf(Document::class, $result); } @@ -152,8 +152,8 @@ public function invalidColorStrict(): void { $this->expectException(UnexpectedTokenException::class); - $sFile = __DIR__ . '/../fixtures/invalid-color.css'; - $oParser = new Parser(\file_get_contents($sFile), Settings::create()->beStrict()); - $oParser->parse(); + $pathToFile = __DIR__ . '/../fixtures/invalid-color.css'; + $parser = new Parser(\file_get_contents($pathToFile), Settings::create()->beStrict()); + $parser->parse(); } } From 0792d334ffe3e940e24610d39b11a4699d1d4eb6 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 28 Aug 2024 02:33:59 +0200 Subject: [PATCH 025/555] [TASK] Be less strict about the `@covers` annotation (#686) We still want to require the `@covers` annotation to be used for testcase (`forceCoversAnnotation="true"`), but we also want to be able to indirectly execute code that is not referenced via an `@covers` annotation. --- phpunit.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/phpunit.xml b/phpunit.xml index 1060f329..96c9fdfa 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -2,7 +2,6 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.5/phpunit.xsd" beStrictAboutChangesToGlobalState="true" - beStrictAboutCoversAnnotation="true" cacheResult="false" colors="true" forceCoversAnnotation="true" From ba3948ea0ea3882763ecdddbd75a474acb048f98 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 28 Aug 2024 03:56:31 +0200 Subject: [PATCH 026/555] [TASK] Switch our code coverage CI from Codacy to Coveralls (#659) Coveralls does not require an API key, provides a nice GitHub Action, and it can post code coverage comments to PRs. Also add a code coverage badge to the README. Also align the GitHub Action workflow with that of our sister project Emogrifier. Also add a Composer script for running the tests with coverage. Fixes #535 Fixes #299 --- .github/workflows/codecoverage.yml | 35 +++++++++++++++++++++--------- .gitignore | 1 + README.md | 1 + composer.json | 3 ++- 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/.github/workflows/codecoverage.yml b/.github/workflows/codecoverage.yml index 7a094fdc..3ff93394 100644 --- a/.github/workflows/codecoverage.yml +++ b/.github/workflows/codecoverage.yml @@ -15,8 +15,12 @@ jobs: runs-on: ubuntu-22.04 strategy: + fail-fast: false matrix: - php-version: [ '7.4' ] + php-version: + - '7.4' + dependencies: + - highest steps: - name: Checkout @@ -30,6 +34,9 @@ jobs: tools: composer:v2 coverage: xdebug + - name: Show the Composer version + run: composer --version + - name: Show the Composer configuration run: composer config --global --list @@ -37,21 +44,29 @@ jobs: uses: actions/cache@v4 with: path: ~/.cache/composer - key: php${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.json') }} + key: php${{ matrix.php-version }}-${{ matrix.dependencies }}-composer-${{ hashFiles('**/composer.json') }} restore-keys: | - php${{ matrix.php-version }}-composer- + php${{ matrix.php-version }}-${{ matrix.dependencies }}-composer- - name: Install Composer dependencies run: | - composer update --with-dependencies --no-progress; + if [[ "${{ matrix.dependencies }}" == 'lowest' ]]; then + DEPENDENCIES='--prefer-lowest'; + else + DEPENDENCIES=''; + fi; + composer install --no-progress; + composer update --with-dependencies --no-progress "${DEPENDENCIES}"; composer show; - name: Run Tests - run: ./vendor/bin/phpunit --coverage-clover build/coverage/xml + run: composer ci:tests:coverage + + - name: Show generated coverage files + run: ls -lah - - name: Upload coverage results to Codacy + - name: Upload coverage results to Coveralls + uses: coverallsapp/github-action@v2 env: - CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }} - if: "${{ env.CODACY_PROJECT_TOKEN != '' }}" - run: | - ./vendor/bin/codacycoverage clover build/coverage/xml + github-token: ${{ secrets.GITHUB_TOKEN }} + file: coverage.xml diff --git a/.gitignore b/.gitignore index acf0d9d8..8bdbea99 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ /.php_cs.cache /.phpunit.result.cache /composer.lock +/coverage.xml /phpstan.neon /vendor/ !/.phive/phars.xml diff --git a/README.md b/README.md index 6810169b..ec7c7ba8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # PHP CSS Parser [![Build Status](https://github.com/MyIntervals/PHP-CSS-Parser/workflows/CI/badge.svg?branch=main)](https://github.com/MyIntervals/PHP-CSS-Parser/actions/) +[![Coverage Status](https://coveralls.io/repos/github/MyIntervals/PHP-CSS-Parser/badge.svg?branch=main)](https://coveralls.io/github/MyIntervals/PHP-CSS-Parser?branch=main) A Parser for CSS Files written in PHP. Allows extraction of CSS files into a data structure, manipulation of said structure and output as (optimized) CSS. diff --git a/composer.json b/composer.json index 050b9cb3..b61d7646 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,6 @@ "ext-iconv": "*" }, "require-dev": { - "codacy/coverage": "^1.4.3", "php-parallel-lint/php-parallel-lint": "^1.4.0", "phpstan/extension-installer": "^1.4.2", "phpstan/phpstan": "^1.12.0", @@ -83,6 +82,7 @@ "ci:tests": [ "@ci:tests:unit" ], + "ci:tests:coverage": "phpunit --do-not-cache-result --coverage-clover=coverage.xml", "ci:tests:sof": "phpunit --stop-on-failure --do-not-cache-result", "ci:tests:unit": "phpunit --do-not-cache-result", "fix:php": [ @@ -102,6 +102,7 @@ "ci:php:rector": "Checks the code for possible code updates and refactoring.", "ci:static": "Runs all static code analysis checks for the code.", "ci:tests": "Runs all dynamic tests (i.e., currently, the unit tests).", + "ci:tests:coverage": "Runs the unit tests with code coverage.", "ci:tests:sof": "Runs the unit tests and stops at the first failure.", "ci:tests:unit": "Runs all unit tests.", "fix:php": "Autofixes all autofixable issues in the PHP code.", From 781021342d31b4e3489fe512edd881fac85f3fe0 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 28 Aug 2024 23:20:39 +0200 Subject: [PATCH 027/555] [TASK] Avoid Hungarian notation in a testcase (#690) --- tests/OutputFormatTest.php | 62 +++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/tests/OutputFormatTest.php b/tests/OutputFormatTest.php index 37fc14f8..8a45fbe0 100644 --- a/tests/OutputFormatTest.php +++ b/tests/OutputFormatTest.php @@ -36,17 +36,17 @@ final class OutputFormatTest extends TestCase /** * @var Parser */ - private $oParser; + private $parser; /** * @var Document */ - private $oDocument; + private $document; protected function setUp(): void { - $this->oParser = new Parser(self::TEST_CSS); - $this->oDocument = $this->oParser->parse(); + $this->parser = new Parser(self::TEST_CSS); + $this->document = $this->parser->parse(); } /** @@ -57,7 +57,7 @@ public function plain(): void 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;}}', - $this->oDocument->render() + $this->document->render() ); } @@ -69,7 +69,7 @@ public function compact(): void 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;}}', - $this->oDocument->render(OutputFormat::createCompact()) + $this->document->render(OutputFormat::createCompact()) ); } @@ -78,7 +78,7 @@ public function compact(): void */ public function pretty(): void { - self::assertSame(self::TEST_CSS, $this->oDocument->render(OutputFormat::createPretty())); + self::assertSame(self::TEST_CSS, $this->document->render(OutputFormat::createPretty())); } /** @@ -90,7 +90,7 @@ public function spaceAfterListArgumentSeparator(): void '.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(' ')) + $this->document->render(OutputFormat::create()->setSpaceAfterListArgumentSeparator(' ')) ); } @@ -102,7 +102,7 @@ public function spaceAfterListArgumentSeparatorComplex(): void 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([ + $this->document->render(OutputFormat::create()->setSpaceAfterListArgumentSeparator([ 'default' => ' ', ',' => "\t", '/' => '', @@ -120,7 +120,7 @@ public function spaceAfterSelectorSeparator(): void '.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;}}', - $this->oDocument->render(OutputFormat::create()->setSpaceAfterSelectorSeparator("\n")) + $this->document->render(OutputFormat::create()->setSpaceAfterSelectorSeparator("\n")) ); } @@ -132,7 +132,7 @@ public function stringQuotingType(): void 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;}}', - $this->oDocument->render(OutputFormat::create()->setStringQuotingType("'")) + $this->document->render(OutputFormat::create()->setStringQuotingType("'")) ); } @@ -144,7 +144,7 @@ public function rGBHashNotation(): void 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);}}', - $this->oDocument->render(OutputFormat::create()->setRGBHashNotation(false)) + $this->document->render(OutputFormat::create()->setRGBHashNotation(false)) ); } @@ -156,7 +156,7 @@ public function semicolonAfterLastRule(): void 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}}', - $this->oDocument->render(OutputFormat::create()->setSemicolonAfterLastRule(false)) + $this->document->render(OutputFormat::create()->setSemicolonAfterLastRule(false)) ); } @@ -168,7 +168,7 @@ public function spaceAfterRuleName(): void 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;}}', - $this->oDocument->render(OutputFormat::create()->setSpaceAfterRuleName("\t")) + $this->document->render(OutputFormat::create()->setSpaceAfterRuleName("\t")) ); } @@ -185,7 +185,7 @@ public function spaceRules(): void background-size: 100% 100%; font-size: 1.3em; background-color: #fff; - }}', $this->oDocument->render(OutputFormat::create()->set('Space*Rules', "\n"))); + }}', $this->document->render(OutputFormat::create()->set('Space*Rules', "\n"))); } /** @@ -198,7 +198,7 @@ public function spaceBlocks(): void @media screen { .main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff;} } -', $this->oDocument->render(OutputFormat::create()->set('Space*Blocks', "\n"))); +', $this->document->render(OutputFormat::create()->set('Space*Blocks', "\n"))); } /** @@ -218,7 +218,7 @@ public function spaceBoth(): void background-color: #fff; } } -', $this->oDocument->render(OutputFormat::create()->set('Space*Rules', "\n")->set('Space*Blocks', "\n"))); +', $this->document->render(OutputFormat::create()->set('Space*Rules', "\n")->set('Space*Blocks', "\n"))); } /** @@ -229,7 +229,7 @@ public function spaceBetweenBlocks(): void 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;}}', - $this->oDocument->render(OutputFormat::create()->setSpaceBetweenBlocks('')) + $this->document->render(OutputFormat::create()->setSpaceBetweenBlocks('')) ); } @@ -250,7 +250,7 @@ public function indentation(): void background-color: #fff; } } -', $this->oDocument->render(OutputFormat::create() +', $this->document->render(OutputFormat::create() ->set('Space*Rules', "\n") ->set('Space*Blocks', "\n") ->setIndentation(''))); @@ -264,7 +264,7 @@ public function spaceBeforeBraces(): void 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;}}', - $this->oDocument->render(OutputFormat::create()->setSpaceBeforeOpeningBrace('')) + $this->document->render(OutputFormat::create()->setSpaceBeforeOpeningBrace('')) ); } @@ -275,16 +275,16 @@ public function ignoreExceptionsOff(): void { $this->expectException(OutputException::class); - $aBlocks = $this->oDocument->getAllDeclarationBlocks(); - $oFirstBlock = $aBlocks[0]; - $oFirstBlock->removeSelector('.main'); + $declarationBlocks = $this->document->getAllDeclarationBlocks(); + $firstDeclarationBlock = $declarationBlocks[0]; + $firstDeclarationBlock->removeSelector('.main'); self::assertSame( '.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;}}', - $this->oDocument->render(OutputFormat::create()->setIgnoreExceptions(false)) + $this->document->render(OutputFormat::create()->setIgnoreExceptions(false)) ); - $oFirstBlock->removeSelector('.test'); - $this->oDocument->render(OutputFormat::create()->setIgnoreExceptions(false)); + $firstDeclarationBlock->removeSelector('.test'); + $this->document->render(OutputFormat::create()->setIgnoreExceptions(false)); } /** @@ -292,13 +292,13 @@ public function ignoreExceptionsOff(): void */ public function ignoreExceptionsOn(): void { - $aBlocks = $this->oDocument->getAllDeclarationBlocks(); - $oFirstBlock = $aBlocks[0]; - $oFirstBlock->removeSelector('.main'); - $oFirstBlock->removeSelector('.test'); + $declarationBlocks = $this->document->getAllDeclarationBlocks(); + $firstDeclarationBlock = $declarationBlocks[0]; + $firstDeclarationBlock->removeSelector('.main'); + $firstDeclarationBlock->removeSelector('.test'); self::assertSame( '@media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}', - $this->oDocument->render(OutputFormat::create()->setIgnoreExceptions(true)) + $this->document->render(OutputFormat::create()->setIgnoreExceptions(true)) ); } } From e12c9f18af2aae98ae198912f9ab83fbaf504cc7 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 28 Aug 2024 23:32:17 +0200 Subject: [PATCH 028/555] [TASK] Block installations on unsupported higher PHP versions (#691) 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. --- CHANGELOG.md | 1 + composer.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 088c8550..9429b04e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Changed +- Block installations on unsupported higher PHP versions (#691) - Improve performance of Value::parseValue with many delimiters by refactoring to remove array_search() (#413) - Add visibility to all class/interface constants (#469) diff --git a/composer.json b/composer.json index b61d7646..28414b53 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ } ], "require": { - "php": ">=7.2.0", + "php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", "ext-iconv": "*" }, "require-dev": { From a64c7520c0f5019892c72b86daaae72ff22effb5 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 28 Aug 2024 23:37:19 +0200 Subject: [PATCH 029/555] [BUGFIX] Fix the image URL of the CI status badge (#694) https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/monitoring-workflows/adding-a-workflow-status-badge Fixes #665 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ec7c7ba8..45b558b1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # PHP CSS Parser -[![Build Status](https://github.com/MyIntervals/PHP-CSS-Parser/workflows/CI/badge.svg?branch=main)](https://github.com/MyIntervals/PHP-CSS-Parser/actions/) +[![Build Status](https://github.com/MyIntervals/PHP-CSS-Parser/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/MyIntervals/PHP-CSS-Parser/actions/) [![Coverage Status](https://coveralls.io/repos/github/MyIntervals/PHP-CSS-Parser/badge.svg?branch=main)](https://coveralls.io/github/MyIntervals/PHP-CSS-Parser?branch=main) A Parser for CSS Files written in PHP. Allows extraction of CSS files into a data structure, manipulation of said structure and output as (optimized) CSS. From 8fd2eb3925551dfb367bb8fb696dea90f35ce59c Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 29 Aug 2024 00:27:27 +0200 Subject: [PATCH 030/555] [TASK] Improve formatting in the changelog (#689) --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9429b04e..d43bfaf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Changed - Block installations on unsupported higher PHP versions (#691) -- 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) - Add visibility to all class/interface constants (#469) ### Deprecated From 8ccd0bbc8dc8b748db5d84aa476dfd4758d158c3 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 29 Aug 2024 00:32:50 +0200 Subject: [PATCH 031/555] [TASK] Make all PHP files strict (#641) --- bin/quickdump.php | 2 ++ config/php-cs-fixer.php | 2 +- src/CSSList/AtRuleBlockList.php | 2 ++ src/CSSList/CSSBlockList.php | 2 ++ src/CSSList/CSSList.php | 2 ++ src/CSSList/Document.php | 2 ++ src/CSSList/KeyFrame.php | 2 ++ src/Comment/Comment.php | 2 ++ src/Comment/Commentable.php | 2 ++ src/OutputFormat.php | 2 ++ src/OutputFormatter.php | 2 ++ src/Parser.php | 2 ++ src/Parsing/Anchor.php | 2 ++ src/Parsing/OutputException.php | 2 ++ src/Parsing/ParserState.php | 2 ++ src/Parsing/SourceException.php | 2 ++ src/Parsing/UnexpectedEOFException.php | 2 ++ src/Parsing/UnexpectedTokenException.php | 2 ++ src/Property/AtRule.php | 2 ++ src/Property/CSSNamespace.php | 2 ++ src/Property/Charset.php | 2 ++ src/Property/Import.php | 2 ++ src/Property/KeyframeSelector.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/Settings.php | 2 ++ src/Value/CSSFunction.php | 2 ++ src/Value/CSSString.php | 2 ++ src/Value/CalcFunction.php | 2 ++ src/Value/CalcRuleValueList.php | 2 ++ src/Value/Color.php | 2 ++ src/Value/LineName.php | 2 ++ src/Value/PrimitiveValue.php | 2 ++ src/Value/RuleValueList.php | 2 ++ src/Value/Size.php | 2 ++ src/Value/URL.php | 2 ++ src/Value/Value.php | 2 ++ src/Value/ValueList.php | 2 ++ 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 ++ tests/Value/SizeTest.php | 2 ++ 52 files changed, 103 insertions(+), 1 deletion(-) diff --git a/bin/quickdump.php b/bin/quickdump.php index fa622abd..c759d028 100755 --- a/bin/quickdump.php +++ b/bin/quickdump.php @@ -1,6 +1,8 @@ #!/usr/bin/env php true, // strict - // 'declare_strict_types' => true, // Note: We'll need to add some casts first. + 'declare_strict_types' => true, 'strict_param' => true, // string notation diff --git a/src/CSSList/AtRuleBlockList.php b/src/CSSList/AtRuleBlockList.php index cd5f27ab..2ac5776b 100644 --- a/src/CSSList/AtRuleBlockList.php +++ b/src/CSSList/AtRuleBlockList.php @@ -1,5 +1,7 @@ Date: Tue, 3 Sep 2024 00:45:17 +0200 Subject: [PATCH 032/555] [TASK] Drop the highest/lowest Composer version switch (#698) As we don't have any direct production dependencies, it does not make sense to test both with highest and lowest Composer versions on CI. We accidentally added this highest/lowest switch with the new coverage job. This commit removes this again, allowing for sharing the Composer caches with the main CI job. --- .github/workflows/codecoverage.yml | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/.github/workflows/codecoverage.yml b/.github/workflows/codecoverage.yml index 3ff93394..dff38197 100644 --- a/.github/workflows/codecoverage.yml +++ b/.github/workflows/codecoverage.yml @@ -19,8 +19,6 @@ jobs: matrix: php-version: - '7.4' - dependencies: - - highest steps: - name: Checkout @@ -44,19 +42,13 @@ jobs: uses: actions/cache@v4 with: path: ~/.cache/composer - key: php${{ matrix.php-version }}-${{ matrix.dependencies }}-composer-${{ hashFiles('**/composer.json') }} + key: php${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.json') }} restore-keys: | - php${{ matrix.php-version }}-${{ matrix.dependencies }}-composer- + php${{ matrix.php-version }}-composer- - name: Install Composer dependencies run: | - if [[ "${{ matrix.dependencies }}" == 'lowest' ]]; then - DEPENDENCIES='--prefer-lowest'; - else - DEPENDENCIES=''; - fi; - composer install --no-progress; - composer update --with-dependencies --no-progress "${DEPENDENCIES}"; + composer update --with-dependencies --no-progress; composer show; - name: Run Tests From caea22c31564d724ec3aa7eb6d30bfa7189ad906 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 3 Sep 2024 01:02:40 +0200 Subject: [PATCH 033/555] [TASK] Move the contribution guidelines to the project root (#699) This hopefully makes them a bit easier to find. Fixes #676 --- .gitattributes | 1 + .github/CONTRIBUTING.md => CONTRIBUTING.md | 0 README.md | 2 +- 3 files changed, 2 insertions(+), 1 deletion(-) rename .github/CONTRIBUTING.md => CONTRIBUTING.md (100%) diff --git a/.gitattributes b/.gitattributes index 4bf8ac39..70a6d5e1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,6 +4,7 @@ /.gitignore export-ignore /.phive/ export-ignore /CODE_OF_CONDUCT.md export-ignore +/CONTRIBUTING.md export-ignore /bin/ export-ignore /config/ export-ignore /phpunit.xml export-ignore diff --git a/.github/CONTRIBUTING.md b/CONTRIBUTING.md similarity index 100% rename from .github/CONTRIBUTING.md rename to CONTRIBUTING.md diff --git a/README.md b/README.md index 45b558b1..6bbefade 100644 --- a/README.md +++ b/README.md @@ -784,7 +784,7 @@ classDiagram Contributions in the form of bug reports, feature requests, or pull requests are more than welcome. :pray: Please have a look at our -[contribution guidelines](.github/CONTRIBUTING.md) to learn more about how to +[contribution guidelines](CONTRIBUTING.md) to learn more about how to contribute to PHP-CSS-Parser. ## Contributors/Thanks to From 1be844b9b02ed8fdfe474e41fcc028637e3822a8 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 3 Sep 2024 01:11:19 +0200 Subject: [PATCH 034/555] [TASK] Deprecate `Parser::setCharset()` and `Parser::getCharset()` (#688) Fixes #681 --- CHANGELOG.md | 1 + src/Parser.php | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d43bfaf6..9460e317 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Deprecated +- Deprecate `Parser::setCharset()` and `Parser::getCharset()` (#688) - Deprecate `DeclarationBlock::createBorderShorthand()` (#578) - Deprecate `DeclarationBlock::createFontShorthand()` (#580) - Deprecate `DeclarationBlock::createDimensionsShorthand()` (#579) diff --git a/src/Parser.php b/src/Parser.php index e6664539..72f343b6 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -35,6 +35,8 @@ public function __construct($sText, ?Settings $oParserSettings = null, $iLineNo * Sets the charset to be used if the CSS does not contain an `@charset` declaration. * * @param string $sCharset + * + * @deprecated will be removed in version 9.0.0 with #687 */ public function setCharset($sCharset): void { @@ -43,6 +45,8 @@ public function setCharset($sCharset): void /** * Returns the charset that is used if the CSS does not contain an `@charset` declaration. + * + * @deprecated will be removed in version 9.0.0 with #687 */ public function getCharset(): void { From 0a5a654537afa4acc928c303530ac10e132306c4 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 3 Sep 2024 01:42:56 +0200 Subject: [PATCH 035/555] [TASK] Update PHP-CS-Fixer (#702) --- .phive/phars.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.phive/phars.xml b/.phive/phars.xml index a19b8834..b7baec8f 100644 --- a/.phive/phars.xml +++ b/.phive/phars.xml @@ -1,4 +1,4 @@ - + From a816e41725fc32d874afae25fd8e4b99bbc3a11e Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 3 Sep 2024 18:03:32 +0200 Subject: [PATCH 036/555] [TASK] Avoid Hungarian notation in a testcase (#700) --- tests/ParserTest.php | 718 +++++++++++++++++++++---------------------- 1 file changed, 359 insertions(+), 359 deletions(-) diff --git a/tests/ParserTest.php b/tests/ParserTest.php index c40a9397..8bbbd69d 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -61,29 +61,29 @@ public function parseForOneRuleSetReturnsDocumentWithOneRuleSet(): void */ public function files(): void { - $sDirectory = __DIR__ . '/fixtures'; - if ($rHandle = \opendir($sDirectory)) { + $directory = __DIR__ . '/fixtures'; + if ($directoryHandle = \opendir($directory)) { /* This is the correct way to loop over the directory. */ - while (false !== ($sFileName = \readdir($rHandle))) { - if (\strpos($sFileName, '.') === 0) { + while (false !== ($filename = \readdir($directoryHandle))) { + if (\strpos($filename, '.') === 0) { continue; } - if (\strrpos($sFileName, '.css') !== \strlen($sFileName) - \strlen('.css')) { + if (\strrpos($filename, '.css') !== \strlen($filename) - \strlen('.css')) { continue; } - if (\strpos($sFileName, '-') === 0) { + if (\strpos($filename, '-') === 0) { // Either a file which SHOULD fail (at least in strict mode) // or a future test of an as-of-now missing feature continue; } - $oParser = new Parser(\file_get_contents($sDirectory . '/' . $sFileName)); + $parser = new Parser(\file_get_contents($directory . '/' . $filename)); try { - self::assertNotEquals('', $oParser->parse()->render()); + self::assertNotEquals('', $parser->parse()->render()); } catch (\Exception $e) { self::fail($e); } } - \closedir($rHandle); + \closedir($directoryHandle); } } @@ -94,65 +94,65 @@ public function files(): void */ public function colorParsing(): void { - $oDoc = self::parsedStructureForFile('colortest'); - foreach ($oDoc->getAllRuleSets() as $oRuleSet) { - if (!$oRuleSet instanceof DeclarationBlock) { + $document = self::parsedStructureForFile('colortest'); + foreach ($document->getAllRuleSets() as $ruleSet) { + if (!$ruleSet instanceof DeclarationBlock) { continue; } - $sSelector = $oRuleSet->getSelectors(); - $sSelector = $sSelector[0]->getSelector(); - if ($sSelector === '#mine') { - $aColorRule = $oRuleSet->getRules('color'); - $oColor = $aColorRule[0]->getValue(); - self::assertSame('red', $oColor); - $aColorRule = $oRuleSet->getRules('background-'); - $oColor = $aColorRule[0]->getValue(); + $selectors = $ruleSet->getSelectors(); + $selector = $selectors[0]->getSelector(); + if ($selector === '#mine') { + $colorRules = $ruleSet->getRules('color'); + $colorRuleValue = $colorRules[0]->getValue(); + self::assertSame('red', $colorRuleValue); + $colorRules = $ruleSet->getRules('background-'); + $colorRuleValue = $colorRules[0]->getValue(); self::assertEquals([ - 'r' => new Size(35.0, null, true, $oColor->getLineNo()), - 'g' => new Size(35.0, null, true, $oColor->getLineNo()), - 'b' => new Size(35.0, null, true, $oColor->getLineNo()), - ], $oColor->getColor()); - $aColorRule = $oRuleSet->getRules('border-color'); - $oColor = $aColorRule[0]->getValue(); + 'r' => new Size(35.0, null, true, $colorRuleValue->getLineNo()), + 'g' => new Size(35.0, null, true, $colorRuleValue->getLineNo()), + 'b' => new Size(35.0, null, true, $colorRuleValue->getLineNo()), + ], $colorRuleValue->getColor()); + $colorRules = $ruleSet->getRules('border-color'); + $colorRuleValue = $colorRules[0]->getValue(); self::assertEquals([ - 'r' => new Size(10.0, null, true, $oColor->getLineNo()), - 'g' => new Size(100.0, null, true, $oColor->getLineNo()), - 'b' => new Size(230.0, null, true, $oColor->getLineNo()), - ], $oColor->getColor()); - $oColor = $aColorRule[1]->getValue(); + 'r' => new Size(10.0, null, true, $colorRuleValue->getLineNo()), + 'g' => new Size(100.0, null, true, $colorRuleValue->getLineNo()), + 'b' => new Size(230.0, null, true, $colorRuleValue->getLineNo()), + ], $colorRuleValue->getColor()); + $colorRuleValue = $colorRules[1]->getValue(); self::assertEquals([ - 'r' => new Size(10.0, null, true, $oColor->getLineNo()), - 'g' => new Size(100.0, null, true, $oColor->getLineNo()), - 'b' => new Size(231.0, null, true, $oColor->getLineNo()), - 'a' => new Size('0000.3', null, true, $oColor->getLineNo()), - ], $oColor->getColor()); - $aColorRule = $oRuleSet->getRules('outline-color'); - $oColor = $aColorRule[0]->getValue(); + 'r' => new Size(10.0, null, true, $colorRuleValue->getLineNo()), + 'g' => new Size(100.0, null, true, $colorRuleValue->getLineNo()), + 'b' => new Size(231.0, null, true, $colorRuleValue->getLineNo()), + 'a' => new Size('0000.3', null, true, $colorRuleValue->getLineNo()), + ], $colorRuleValue->getColor()); + $colorRules = $ruleSet->getRules('outline-color'); + $colorRuleValue = $colorRules[0]->getValue(); self::assertEquals([ - 'r' => new Size(34.0, null, true, $oColor->getLineNo()), - 'g' => new Size(34.0, null, true, $oColor->getLineNo()), - 'b' => new Size(34.0, null, true, $oColor->getLineNo()), - ], $oColor->getColor()); - } elseif ($sSelector === '#yours') { - $aColorRule = $oRuleSet->getRules('background-color'); - $oColor = $aColorRule[0]->getValue(); + 'r' => new Size(34.0, null, true, $colorRuleValue->getLineNo()), + 'g' => new Size(34.0, null, true, $colorRuleValue->getLineNo()), + 'b' => new Size(34.0, null, true, $colorRuleValue->getLineNo()), + ], $colorRuleValue->getColor()); + } elseif ($selector === '#yours') { + $colorRules = $ruleSet->getRules('background-color'); + $colorRuleValue = $colorRules[0]->getValue(); self::assertEquals([ - 'h' => new Size(220.0, null, true, $oColor->getLineNo()), - 's' => new Size(10.0, '%', true, $oColor->getLineNo()), - 'l' => new Size(220.0, '%', true, $oColor->getLineNo()), - ], $oColor->getColor()); - $oColor = $aColorRule[1]->getValue(); + 'h' => new Size(220.0, null, true, $colorRuleValue->getLineNo()), + 's' => new Size(10.0, '%', true, $colorRuleValue->getLineNo()), + 'l' => new Size(220.0, '%', true, $colorRuleValue->getLineNo()), + ], $colorRuleValue->getColor()); + $colorRuleValue = $colorRules[1]->getValue(); self::assertEquals([ - 'h' => new Size(220.0, null, true, $oColor->getLineNo()), - 's' => new Size(10.0, '%', true, $oColor->getLineNo()), - '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); + 'h' => new Size(220.0, null, true, $colorRuleValue->getLineNo()), + 's' => new Size(10.0, '%', true, $colorRuleValue->getLineNo()), + 'l' => new Size(220.0, '%', true, $colorRuleValue->getLineNo()), + 'a' => new Size(0000.3, null, true, $colorRuleValue->getLineNo()), + ], $colorRuleValue->getColor()); + $colorRules = $ruleSet->getRules('outline-color'); + self::assertEmpty($colorRules); } } - foreach ($oDoc->getAllValues('color') as $sColor) { + foreach ($document->getAllValues('color') as $sColor) { self::assertSame('red', $sColor); } self::assertSame( @@ -167,7 +167,7 @@ public function colorParsing(): void . "\n" . '#variables-alpha {background-color: rgba(var(--some-rgb),.1);' . 'background-color: rgba(var(--some-rg),255,.1);background-color: hsla(var(--some-hsl),.1);}', - $oDoc->render() + $document->render() ); } @@ -176,47 +176,47 @@ public function colorParsing(): void */ public function unicodeParsing(): void { - $oDoc = self::parsedStructureForFile('unicode'); - foreach ($oDoc->getAllDeclarationBlocks() as $oRuleSet) { - $sSelector = $oRuleSet->getSelectors(); - $sSelector = $sSelector[0]->getSelector(); - if (\substr($sSelector, 0, \strlen('.test-')) !== '.test-') { + $document = self::parsedStructureForFile('unicode'); + foreach ($document->getAllDeclarationBlocks() as $ruleSet) { + $selectors = $ruleSet->getSelectors(); + $selector = $selectors[0]->getSelector(); + if (\substr($selector, 0, \strlen('.test-')) !== '.test-') { continue; } - $aContentRules = $oRuleSet->getRules('content'); - $sString = $aContentRules[0]->getValue()->__toString(); - if ($sSelector == '.test-1') { - self::assertSame('" "', $sString); + $contentRules = $ruleSet->getRules('content'); + $firstContentRuleAsString = $contentRules[0]->getValue()->__toString(); + if ($selector == '.test-1') { + self::assertSame('" "', $firstContentRuleAsString); } - if ($sSelector == '.test-2') { - self::assertSame('"é"', $sString); + if ($selector == '.test-2') { + self::assertSame('"é"', $firstContentRuleAsString); } - if ($sSelector == '.test-3') { - self::assertSame('" "', $sString); + if ($selector == '.test-3') { + self::assertSame('" "', $firstContentRuleAsString); } - if ($sSelector == '.test-4') { - self::assertSame('"𝄞"', $sString); + if ($selector == '.test-4') { + self::assertSame('"𝄞"', $firstContentRuleAsString); } - if ($sSelector == '.test-5') { - self::assertSame('"水"', $sString); + if ($selector == '.test-5') { + self::assertSame('"水"', $firstContentRuleAsString); } - if ($sSelector == '.test-6') { - self::assertSame('"¥"', $sString); + if ($selector == '.test-6') { + self::assertSame('"¥"', $firstContentRuleAsString); } - if ($sSelector == '.test-7') { - self::assertSame('"\\A"', $sString); + if ($selector == '.test-7') { + self::assertSame('"\\A"', $firstContentRuleAsString); } - if ($sSelector == '.test-8') { - self::assertSame('"\\"\\""', $sString); + if ($selector == '.test-8') { + self::assertSame('"\\"\\""', $firstContentRuleAsString); } - if ($sSelector == '.test-9') { - self::assertSame('"\\"\\\'"', $sString); + if ($selector == '.test-9') { + self::assertSame('"\\"\\\'"', $firstContentRuleAsString); } - if ($sSelector == '.test-10') { - self::assertSame('"\\\'\\\\"', $sString); + if ($selector == '.test-10') { + self::assertSame('"\\\'\\\\"', $firstContentRuleAsString); } - if ($sSelector == '.test-11') { - self::assertSame('"test"', $sString); + if ($selector == '.test-11') { + self::assertSame('"test"', $firstContentRuleAsString); } } } @@ -226,9 +226,9 @@ public function unicodeParsing(): void */ public function unicodeRangeParsing(): void { - $oDoc = self::parsedStructureForFile('unicode-range'); - $sExpected = '@font-face {unicode-range: U+0100-024F,U+0259,U+1E??-2EFF,U+202F;}'; - self::assertSame($sExpected, $oDoc->render()); + $document = self::parsedStructureForFile('unicode-range'); + $expected = '@font-face {unicode-range: U+0100-024F,U+0259,U+1E??-2EFF,U+202F;}'; + self::assertSame($expected, $document->render()); } /** @@ -236,51 +236,51 @@ public function unicodeRangeParsing(): void */ public function specificity(): void { - $oDoc = self::parsedStructureForFile('specificity'); - $oDeclarationBlock = $oDoc->getAllDeclarationBlocks(); - $oDeclarationBlock = $oDeclarationBlock[0]; - $aSelectors = $oDeclarationBlock->getSelectors(); - foreach ($aSelectors as $oSelector) { - switch ($oSelector->getSelector()) { + $document = self::parsedStructureForFile('specificity'); + $declarationBlocks = $document->getAllDeclarationBlocks(); + $declarationBlock = $declarationBlocks[0]; + $selectors = $declarationBlock->getSelectors(); + foreach ($selectors as $selector) { + switch ($selector->getSelector()) { case '#test .help': - self::assertSame(110, $oSelector->getSpecificity()); + self::assertSame(110, $selector->getSpecificity()); break; case '#file': - self::assertSame(100, $oSelector->getSpecificity()); + self::assertSame(100, $selector->getSpecificity()); break; case '.help:hover': - self::assertSame(20, $oSelector->getSpecificity()); + self::assertSame(20, $selector->getSpecificity()); break; case 'ol li::before': - self::assertSame(3, $oSelector->getSpecificity()); + self::assertSame(3, $selector->getSpecificity()); break; case 'li.green': - self::assertSame(11, $oSelector->getSpecificity()); + self::assertSame(11, $selector->getSpecificity()); break; default: - self::fail('specificity: untested selector ' . $oSelector->getSelector()); + self::fail('specificity: untested selector ' . $selector->getSelector()); } } - self::assertEquals([new Selector('#test .help', true)], $oDoc->getSelectorsBySpecificity('> 100')); + self::assertEquals([new Selector('#test .help', true)], $document->getSelectorsBySpecificity('> 100')); self::assertEquals( [new Selector('#test .help', true), new Selector('#file', true)], - $oDoc->getSelectorsBySpecificity('>= 100') + $document->getSelectorsBySpecificity('>= 100') ); - self::assertEquals([new Selector('#file', true)], $oDoc->getSelectorsBySpecificity('=== 100')); - self::assertEquals([new Selector('#file', true)], $oDoc->getSelectorsBySpecificity('== 100')); + self::assertEquals([new Selector('#file', true)], $document->getSelectorsBySpecificity('=== 100')); + self::assertEquals([new Selector('#file', true)], $document->getSelectorsBySpecificity('== 100')); self::assertEquals([ new Selector('#file', true), new Selector('.help:hover', true), new Selector('li.green', true), new Selector('ol li::before', true), - ], $oDoc->getSelectorsBySpecificity('<= 100')); + ], $document->getSelectorsBySpecificity('<= 100')); self::assertEquals([ new Selector('.help:hover', true), new Selector('li.green', true), 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')); + ], $document->getSelectorsBySpecificity('< 100')); + self::assertEquals([new Selector('li.green', true)], $document->getSelectorsBySpecificity('11')); + self::assertEquals([new Selector('ol li::before', true)], $document->getSelectorsBySpecificity('3')); } /** @@ -288,7 +288,7 @@ public function specificity(): void */ public function manipulation(): void { - $oDoc = self::parsedStructureForFile('atrules'); + $document = self::parsedStructureForFile('atrules'); self::assertSame( '@charset "utf-8";' . "\n" @@ -320,12 +320,12 @@ public function manipulation(): void . '@media screen and (orientation: landscape) {@-ms-viewport {width: 1024px;height: 768px;}}' . "\n" . '@region-style #intro {p {color: blue;}}', - $oDoc->render() + $document->render() ); - foreach ($oDoc->getAllDeclarationBlocks() as $oBlock) { - foreach ($oBlock->getSelectors() as $oSelector) { + foreach ($document->getAllDeclarationBlocks() as $declarationBlock) { + foreach ($declarationBlock->getSelectors() as $selector) { //Loop over all selector parts (the comma-separated strings in a selector) and prepend the id - $oSelector->setSelector('#my_id ' . $oSelector->getSelector()); + $selector->setSelector('#my_id ' . $selector->getSelector()); } } self::assertSame( @@ -359,33 +359,33 @@ public function manipulation(): void . '@media screen and (orientation: landscape) {@-ms-viewport {width: 1024px;height: 768px;}}' . "\n" . '@region-style #intro {#my_id p {color: blue;}}', - $oDoc->render(OutputFormat::create()->setRenderComments(false)) + $document->render(OutputFormat::create()->setRenderComments(false)) ); - $oDoc = self::parsedStructureForFile('values'); + $document = self::parsedStructureForFile('values'); self::assertSame( '#header {margin: 10px 2em 1cm 2%;font-family: Verdana,Helvetica,"Gill Sans",sans-serif;' . 'font-size: 10px;color: red !important;background-color: green;' . 'background-color: rgba(0,128,0,.7);frequency: 30Hz;transform: rotate(1turn);} body {color: green;font: 75% "Lucida Grande","Trebuchet MS",Verdana,sans-serif;}', - $oDoc->render() + $document->render() ); - foreach ($oDoc->getAllRuleSets() as $oRuleSet) { - $oRuleSet->removeRule('font-'); + foreach ($document->getAllRuleSets() as $ruleSet) { + $ruleSet->removeRule('font-'); } self::assertSame( '#header {margin: 10px 2em 1cm 2%;color: red !important;background-color: green;' . 'background-color: rgba(0,128,0,.7);frequency: 30Hz;transform: rotate(1turn);} body {color: green;}', - $oDoc->render() + $document->render() ); - foreach ($oDoc->getAllRuleSets() as $oRuleSet) { - $oRuleSet->removeRule('background-'); + foreach ($document->getAllRuleSets() as $ruleSet) { + $ruleSet->removeRule('background-'); } self::assertSame( '#header {margin: 10px 2em 1cm 2%;color: red !important;frequency: 30Hz;transform: rotate(1turn);} body {color: green;}', - $oDoc->render() + $document->render() ); } @@ -394,22 +394,22 @@ public function manipulation(): void */ public function ruleGetters(): void { - $oDoc = self::parsedStructureForFile('values'); - $aBlocks = $oDoc->getAllDeclarationBlocks(); - $oHeaderBlock = $aBlocks[0]; - $oBodyBlock = $aBlocks[1]; - $aHeaderRules = $oHeaderBlock->getRules('background-'); - self::assertCount(2, $aHeaderRules); - self::assertSame('background-color', $aHeaderRules[0]->getRule()); - self::assertSame('background-color', $aHeaderRules[1]->getRule()); - $aHeaderRules = $oHeaderBlock->getRulesAssoc('background-'); - self::assertCount(1, $aHeaderRules); - self::assertTrue($aHeaderRules['background-color']->getValue() instanceof Color); - self::assertSame('rgba', $aHeaderRules['background-color']->getValue()->getColorDescription()); - $oHeaderBlock->removeRule($aHeaderRules['background-color']); - $aHeaderRules = $oHeaderBlock->getRules('background-'); - self::assertCount(1, $aHeaderRules); - self::assertSame('green', $aHeaderRules[0]->getValue()); + $document = self::parsedStructureForFile('values'); + $declarationBlocks = $document->getAllDeclarationBlocks(); + $headerBlock = $declarationBlocks[0]; + $bodyBlock = $declarationBlocks[1]; + $backgroundHeaderRules = $headerBlock->getRules('background-'); + self::assertCount(2, $backgroundHeaderRules); + self::assertSame('background-color', $backgroundHeaderRules[0]->getRule()); + self::assertSame('background-color', $backgroundHeaderRules[1]->getRule()); + $backgroundHeaderRules = $headerBlock->getRulesAssoc('background-'); + self::assertCount(1, $backgroundHeaderRules); + self::assertTrue($backgroundHeaderRules['background-color']->getValue() instanceof Color); + self::assertSame('rgba', $backgroundHeaderRules['background-color']->getValue()->getColorDescription()); + $headerBlock->removeRule($backgroundHeaderRules['background-color']); + $backgroundHeaderRules = $headerBlock->getRules('background-'); + self::assertCount(1, $backgroundHeaderRules); + self::assertSame('green', $backgroundHeaderRules[0]->getValue()); } /** @@ -417,39 +417,39 @@ public function ruleGetters(): void */ public function slashedValues(): void { - $oDoc = self::parsedStructureForFile('slashed'); + $document = self::parsedStructureForFile('slashed'); self::assertSame( '.test {font: 12px/1.5 Verdana,Arial,sans-serif;border-radius: 5px 10px 5px 10px/10px 5px 10px 5px;}', - $oDoc->render() + $document->render() ); - foreach ($oDoc->getAllValues(null) as $mValue) { - if ($mValue instanceof Size && $mValue->isSize() && !$mValue->isRelative()) { - $mValue->setSize($mValue->getSize() * 3); + foreach ($document->getAllValues(null) as $value) { + if ($value instanceof Size && $value->isSize() && !$value->isRelative()) { + $value->setSize($value->getSize() * 3); } } - foreach ($oDoc->getAllDeclarationBlocks() as $oBlock) { - $oRule = $oBlock->getRules('font'); - $oRule = $oRule[0]; - $oSpaceList = $oRule->getValue(); - self::assertSame(' ', $oSpaceList->getListSeparator()); - $oSlashList = $oSpaceList->getListComponents(); - $oCommaList = $oSlashList[1]; - $oSlashList = $oSlashList[0]; - self::assertSame(',', $oCommaList->getListSeparator()); - self::assertSame('/', $oSlashList->getListSeparator()); - $oRule = $oBlock->getRules('border-radius'); - $oRule = $oRule[0]; - $oSlashList = $oRule->getValue(); - self::assertSame('/', $oSlashList->getListSeparator()); - $oSpaceList1 = $oSlashList->getListComponents(); - $oSpaceList2 = $oSpaceList1[1]; - $oSpaceList1 = $oSpaceList1[0]; - self::assertSame(' ', $oSpaceList1->getListSeparator()); - self::assertSame(' ', $oSpaceList2->getListSeparator()); + foreach ($document->getAllDeclarationBlocks() as $declarationBlock) { + $fontRules = $declarationBlock->getRules('font'); + $fontRule = $fontRules[0]; + $fontRuleValue = $fontRule->getValue(); + self::assertSame(' ', $fontRuleValue->getListSeparator()); + $fontRuleValueComponents = $fontRuleValue->getListComponents(); + $commaList = $fontRuleValueComponents[1]; + $slashList = $fontRuleValueComponents[0]; + self::assertSame(',', $commaList->getListSeparator()); + self::assertSame('/', $slashList->getListSeparator()); + $borderRadiusRules = $declarationBlock->getRules('border-radius'); + $borderRadiusRule = $borderRadiusRules[0]; + $slashList = $borderRadiusRule->getValue(); + self::assertSame('/', $slashList->getListSeparator()); + $slashListComponents = $slashList->getListComponents(); + $secondSlashListComponent = $slashListComponents[1]; + $firstSlashListComponent = $slashListComponents[0]; + self::assertSame(' ', $firstSlashListComponent->getListSeparator()); + self::assertSame(' ', $secondSlashListComponent->getListSeparator()); } self::assertSame( '.test {font: 36px/1.5 Verdana,Arial,sans-serif;border-radius: 15px 30px 15px 30px/30px 15px 30px 15px;}', - $oDoc->render() + $document->render() ); } @@ -458,8 +458,8 @@ public function slashedValues(): void */ public function functionSyntax(): void { - $oDoc = self::parsedStructureForFile('functions'); - $sExpected = 'div.main {background-image: linear-gradient(#000,#fff);}' + $document = self::parsedStructureForFile('functions'); + $expected = 'div.main {background-image: linear-gradient(#000,#fff);}' . "\n" . '.collapser::before, .collapser::-moz-before, .collapser::-webkit-before {content: "»";font-size: 1.2em;' . 'margin-right: .2em;-moz-transition-property: -moz-transform;-moz-transition-duration: .2s;' @@ -472,23 +472,23 @@ public function functionSyntax(): void . '-moz-transition-duration: .3s;}' . "\n" . '.collapser.expanded + * {height: auto;}'; - self::assertSame($sExpected, $oDoc->render()); + self::assertSame($expected, $document->render()); - foreach ($oDoc->getAllValues(null, true) as $mValue) { + foreach ($document->getAllValues(null, true) as $mValue) { if ($mValue instanceof Size && $mValue->isSize()) { $mValue->setSize($mValue->getSize() * 3); } } - $sExpected = \str_replace(['1.2em', '.2em', '60%'], ['3.6em', '.6em', '180%'], $sExpected); - self::assertSame($sExpected, $oDoc->render()); + $expected = \str_replace(['1.2em', '.2em', '60%'], ['3.6em', '.6em', '180%'], $expected); + self::assertSame($expected, $document->render()); - foreach ($oDoc->getAllValues(null, true) as $mValue) { - if ($mValue instanceof Size && !$mValue->isRelative() && !$mValue->isColorComponent()) { - $mValue->setSize($mValue->getSize() * 2); + foreach ($document->getAllValues(null, true) as $value) { + if ($value instanceof Size && !$value->isRelative() && !$value->isColorComponent()) { + $value->setSize($value->getSize() * 2); } } - $sExpected = \str_replace(['.2s', '.3s', '90deg'], ['.4s', '.6s', '180deg'], $sExpected); - self::assertSame($sExpected, $oDoc->render()); + $expected = \str_replace(['.2s', '.3s', '90deg'], ['.4s', '.6s', '180deg'], $expected); + self::assertSame($expected, $document->render()); } /** @@ -496,13 +496,13 @@ public function functionSyntax(): void */ public function expandShorthands(): void { - $oDoc = self::parsedStructureForFile('expand-shorthands'); - $sExpected = 'body {font: italic 500 14px/1.618 "Trebuchet MS",Georgia,serif;border: 2px solid #f0f;' + $document = self::parsedStructureForFile('expand-shorthands'); + $expected = 'body {font: italic 500 14px/1.618 "Trebuchet MS",Georgia,serif;border: 2px solid #f0f;' . 'background: #ccc url("/images/foo.png") no-repeat left top;margin: 1em !important;' . 'padding: 2px 6px 3px;}'; - self::assertSame($sExpected, $oDoc->render()); - $oDoc->expandShorthands(); - $sExpected = 'body {margin-top: 1em !important;margin-right: 1em !important;margin-bottom: 1em !important;' + self::assertSame($expected, $document->render()); + $document->expandShorthands(); + $expected = 'body {margin-top: 1em !important;margin-right: 1em !important;margin-bottom: 1em !important;' . 'margin-left: 1em !important;padding-top: 2px;padding-right: 6px;padding-bottom: 3px;' . 'padding-left: 6px;border-top-color: #f0f;border-right-color: #f0f;border-bottom-color: #f0f;' . 'border-left-color: #f0f;border-top-style: solid;border-right-style: solid;' @@ -512,7 +512,7 @@ public function expandShorthands(): void . '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()); + self::assertSame($expected, $document->render()); } /** @@ -520,16 +520,16 @@ public function expandShorthands(): void */ public function createShorthands(): void { - $oDoc = self::parsedStructureForFile('create-shorthands'); - $sExpected = 'body {font-size: 2em;font-family: Helvetica,Arial,sans-serif;font-weight: bold;' + $document = self::parsedStructureForFile('create-shorthands'); + $expected = 'body {font-size: 2em;font-family: Helvetica,Arial,sans-serif;font-weight: bold;' . 'border-width: 2px;border-color: #999;border-style: dotted;background-color: #fff;' . 'background-image: url("foobar.png");background-repeat: repeat-y;margin-top: 2px;margin-right: 3px;' . 'margin-bottom: 4px;margin-left: 5px;}'; - self::assertSame($sExpected, $oDoc->render()); - $oDoc->createShorthands(); - $sExpected = 'body {background: #fff url("foobar.png") repeat-y;margin: 2px 5px 4px 3px;' + self::assertSame($expected, $document->render()); + $document->createShorthands(); + $expected = '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()); + self::assertSame($expected, $document->render()); } /** @@ -537,14 +537,14 @@ public function createShorthands(): void */ public function namespaces(): void { - $oDoc = self::parsedStructureForFile('namespaces'); - $sExpected = '@namespace toto "http://toto.example.org"; + $document = self::parsedStructureForFile('namespaces'); + $expected = '@namespace toto "http://toto.example.org"; @namespace "http://example.com/foo"; @namespace foo url("http://www.example.com/"); @namespace foo url("http://www.example.com/"); foo|test {gaga: 1;} |test {gaga: 2;}'; - self::assertSame($sExpected, $oDoc->render()); + self::assertSame($expected, $document->render()); } /** @@ -552,9 +552,9 @@ public function namespaces(): void */ public function innerColors(): void { - $oDoc = self::parsedStructureForFile('inner-color'); - $sExpected = 'test {background: -webkit-gradient(linear,0 0,0 bottom,from(#006cad),to(hsl(202,100%,49%)));}'; - self::assertSame($sExpected, $oDoc->render()); + $document = self::parsedStructureForFile('inner-color'); + $expected = 'test {background: -webkit-gradient(linear,0 0,0 bottom,from(#006cad),to(hsl(202,100%,49%)));}'; + self::assertSame($expected, $document->render()); } /** @@ -562,9 +562,9 @@ public function innerColors(): void */ public function prefixedGradient(): void { - $oDoc = self::parsedStructureForFile('webkit'); - $sExpected = '.test {background: -webkit-linear-gradient(top right,white,black);}'; - self::assertSame($sExpected, $oDoc->render()); + $document = self::parsedStructureForFile('webkit'); + $expected = '.test {background: -webkit-linear-gradient(top right,white,black);}'; + self::assertSame($expected, $document->render()); } /** @@ -572,36 +572,36 @@ public function prefixedGradient(): void */ public function listValueRemoval(): void { - $oDoc = self::parsedStructureForFile('atrules'); - foreach ($oDoc->getContents() as $oItem) { - if ($oItem instanceof AtRule) { - $oDoc->remove($oItem); + $document = self::parsedStructureForFile('atrules'); + foreach ($document->getContents() as $contentItem) { + if ($contentItem instanceof AtRule) { + $document->remove($contentItem); continue; } } - self::assertSame('html, body {font-size: -.6em;}', $oDoc->render()); + self::assertSame('html, body {font-size: -.6em;}', $document->render()); - $oDoc = self::parsedStructureForFile('nested'); - foreach ($oDoc->getAllDeclarationBlocks() as $oBlock) { - $oDoc->removeDeclarationBlockBySelector($oBlock, false); + $document = self::parsedStructureForFile('nested'); + foreach ($document->getAllDeclarationBlocks() as $declarationBlock) { + $document->removeDeclarationBlockBySelector($declarationBlock, false); break; } self::assertSame( 'html {some-other: -test(val1);} @media screen {html {some: -test(val2);}} #unrelated {other: yes;}', - $oDoc->render() + $document->render() ); - $oDoc = self::parsedStructureForFile('nested'); - foreach ($oDoc->getAllDeclarationBlocks() as $oBlock) { - $oDoc->removeDeclarationBlockBySelector($oBlock, true); + $document = self::parsedStructureForFile('nested'); + foreach ($document->getAllDeclarationBlocks() as $declarationBlock) { + $document->removeDeclarationBlockBySelector($declarationBlock, true); break; } self::assertSame( '@media screen {html {some: -test(val2);}} #unrelated {other: yes;}', - $oDoc->render() + $document->render() ); } @@ -612,18 +612,18 @@ public function selectorRemoval(): void { $this->expectException(OutputException::class); - $oDoc = self::parsedStructureForFile('1readme'); - $aBlocks = $oDoc->getAllDeclarationBlocks(); - $oBlock1 = $aBlocks[0]; - self::assertTrue($oBlock1->removeSelector('html')); - $sExpected = '@charset "utf-8"; + $document = self::parsedStructureForFile('1readme'); + $declarationsBlocks = $document->getAllDeclarationBlocks(); + $declarationBlock = $declarationsBlocks[0]; + self::assertTrue($declarationBlock->removeSelector('html')); + $expected = '@charset "utf-8"; @font-face {font-family: "CrassRoots";src: url("../media/cr.ttf");} body {font-size: 1.6em;}'; - self::assertSame($sExpected, $oDoc->render()); - self::assertFalse($oBlock1->removeSelector('html')); - self::assertTrue($oBlock1->removeSelector('body')); + self::assertSame($expected, $document->render()); + self::assertFalse($declarationBlock->removeSelector('html')); + self::assertTrue($declarationBlock->removeSelector('body')); // This tries to output a declaration block without a selector and throws. - $oDoc->render(); + $document->render(); } /** @@ -631,13 +631,13 @@ public function selectorRemoval(): void */ public function comments(): void { - $oDoc = self::parsedStructureForFile('comments'); - $sExpected = <<render()); + self::assertSame($expected, $document->render()); } /** @@ -645,10 +645,10 @@ public function comments(): void */ public function urlInFile(): void { - $oDoc = self::parsedStructureForFile('url', Settings::create()->withMultibyteSupport(true)); - $sExpected = 'body {background: #fff url("https://somesite.com/images/someimage.gif") repeat top center;} + $document = self::parsedStructureForFile('url', Settings::create()->withMultibyteSupport(true)); + $expected = 'body {background: #fff url("https://somesite.com/images/someimage.gif") repeat top center;} body {background-url: url("https://somesite.com/images/someimage.gif");}'; - self::assertSame($sExpected, $oDoc->render()); + self::assertSame($expected, $document->render()); } /** @@ -656,10 +656,10 @@ public function urlInFile(): void */ public function hexAlphaInFile(): void { - $oDoc = self::parsedStructureForFile('hex-alpha', Settings::create()->withMultibyteSupport(true)); - $sExpected = 'div {background: rgba(17,34,51,.27);} + $document = self::parsedStructureForFile('hex-alpha', Settings::create()->withMultibyteSupport(true)); + $expected = 'div {background: rgba(17,34,51,.27);} div {background: rgba(17,34,51,.27);}'; - self::assertSame($sExpected, $oDoc->render()); + self::assertSame($expected, $document->render()); } /** @@ -667,12 +667,12 @@ public function hexAlphaInFile(): void */ public function calcInFile(): void { - $oDoc = self::parsedStructureForFile('calc', Settings::create()->withMultibyteSupport(true)); - $sExpected = 'div {width: calc(100% / 4);} + $document = self::parsedStructureForFile('calc', Settings::create()->withMultibyteSupport(true)); + $expected = 'div {width: calc(100% / 4);} div {margin-top: calc(-120% - 4px);} div {height: -webkit-calc(9 / 16 * 100%) !important;width: -moz-calc(( 50px - 50% ) * 2);} div {width: calc(50% - ( ( 4% ) * .5 ));}'; - self::assertSame($sExpected, $oDoc->render()); + self::assertSame($expected, $document->render()); } /** @@ -680,9 +680,9 @@ public function calcInFile(): void */ public function calcNestedInFile(): void { - $oDoc = self::parsedStructureForFile('calc-nested', Settings::create()->withMultibyteSupport(true)); - $sExpected = '.test {font-size: calc(( 3 * 4px ) + -2px);top: calc(200px - calc(20 * 3px));}'; - self::assertSame($sExpected, $oDoc->render()); + $document = self::parsedStructureForFile('calc-nested', Settings::create()->withMultibyteSupport(true)); + $expected = '.test {font-size: calc(( 3 * 4px ) + -2px);top: calc(200px - calc(20 * 3px));}'; + self::assertSame($expected, $document->render()); } /** @@ -690,13 +690,13 @@ public function calcNestedInFile(): void */ public function invalidCalcInFile(): void { - $oDoc = self::parsedStructureForFile('calc-invalid', Settings::create()->withMultibyteSupport(true)); - $sExpected = 'div {} + $document = self::parsedStructureForFile('calc-invalid', Settings::create()->withMultibyteSupport(true)); + $expected = 'div {} div {} div {} div {height: -moz-calc;} div {height: calc;}'; - self::assertSame($sExpected, $oDoc->render()); + self::assertSame($expected, $document->render()); } /** @@ -705,28 +705,28 @@ public function invalidCalcInFile(): void public function invalidCalc(): void { $parser = new Parser('div { height: calc(100px'); - $oDoc = $parser->parse(); - self::assertSame('div {height: calc(100px);}', $oDoc->render()); + $document = $parser->parse(); + self::assertSame('div {height: calc(100px);}', $document->render()); $parser = new Parser('div { height: calc(100px)'); - $oDoc = $parser->parse(); - self::assertSame('div {height: calc(100px);}', $oDoc->render()); + $document = $parser->parse(); + self::assertSame('div {height: calc(100px);}', $document->render()); $parser = new Parser('div { height: calc(100px);'); - $oDoc = $parser->parse(); - self::assertSame('div {height: calc(100px);}', $oDoc->render()); + $document = $parser->parse(); + self::assertSame('div {height: calc(100px);}', $document->render()); $parser = new Parser('div { height: calc(100px}'); - $oDoc = $parser->parse(); - self::assertSame('div {}', $oDoc->render()); + $document = $parser->parse(); + self::assertSame('div {}', $document->render()); $parser = new Parser('div { height: calc(100px;'); - $oDoc = $parser->parse(); - self::assertSame('div {}', $oDoc->render()); + $document = $parser->parse(); + self::assertSame('div {}', $document->render()); $parser = new Parser('div { height: calc(100px;}'); - $oDoc = $parser->parse(); - self::assertSame('div {}', $oDoc->render()); + $document = $parser->parse(); + self::assertSame('div {}', $document->render()); } /** @@ -734,10 +734,10 @@ public function invalidCalc(): void */ public function gridLineNameInFile(): void { - $oDoc = self::parsedStructureForFile('grid-linename', Settings::create()->withMultibyteSupport(true)); - $sExpected = "div {grid-template-columns: [linename] 100px;}\n" + $document = self::parsedStructureForFile('grid-linename', Settings::create()->withMultibyteSupport(true)); + $expected = "div {grid-template-columns: [linename] 100px;}\n" . 'span {grid-template-columns: [linename1 linename2] 100px;}'; - self::assertSame($sExpected, $oDoc->render()); + self::assertSame($expected, $document->render()); } /** @@ -745,9 +745,9 @@ public function gridLineNameInFile(): void */ public function emptyGridLineNameLenientInFile(): void { - $oDoc = self::parsedStructureForFile('empty-grid-linename'); - $sExpected = '.test {grid-template-columns: [] 100px;}'; - self::assertSame($sExpected, $oDoc->render()); + $document = self::parsedStructureForFile('empty-grid-linename'); + $expected = '.test {grid-template-columns: [] 100px;}'; + self::assertSame($expected, $document->render()); } /** @@ -755,9 +755,9 @@ public function emptyGridLineNameLenientInFile(): void */ public function invalidGridLineNameInFile(): void { - $oDoc = self::parsedStructureForFile('invalid-grid-linename', Settings::create()->withMultibyteSupport(true)); - $sExpected = 'div {}'; - self::assertSame($sExpected, $oDoc->render()); + $document = self::parsedStructureForFile('invalid-grid-linename', Settings::create()->withMultibyteSupport(true)); + $expected = 'div {}'; + self::assertSame($expected, $document->render()); } /** @@ -765,9 +765,9 @@ public function invalidGridLineNameInFile(): void */ public function unmatchedBracesInFile(): void { - $oDoc = self::parsedStructureForFile('unmatched_braces', Settings::create()->withMultibyteSupport(true)); - $sExpected = 'button, input, checkbox, textarea {outline: 0;margin: 0;}'; - self::assertSame($sExpected, $oDoc->render()); + $document = self::parsedStructureForFile('unmatched_braces', Settings::create()->withMultibyteSupport(true)); + $expected = 'button, input, checkbox, textarea {outline: 0;margin: 0;}'; + self::assertSame($expected, $document->render()); } /** @@ -775,20 +775,20 @@ public function unmatchedBracesInFile(): void */ public function invalidSelectorsInFile(): void { - $oDoc = self::parsedStructureForFile('invalid-selectors', Settings::create()->withMultibyteSupport(true)); - $sExpected = '@keyframes mymove {from {top: 0px;}} + $document = self::parsedStructureForFile('invalid-selectors', Settings::create()->withMultibyteSupport(true)); + $expected = '@keyframes mymove {from {top: 0px;}} #test {color: white;background: green;} #test {display: block;background: white;color: black;}'; - self::assertSame($sExpected, $oDoc->render()); + self::assertSame($expected, $document->render()); - $oDoc = self::parsedStructureForFile('invalid-selectors-2', Settings::create()->withMultibyteSupport(true)); - $sExpected = '@media only screen and (max-width: 1215px) {.breadcrumb {padding-left: 10px;} + $document = self::parsedStructureForFile('invalid-selectors-2', Settings::create()->withMultibyteSupport(true)); + $expected = '@media only screen and (max-width: 1215px) {.breadcrumb {padding-left: 10px;} .super-menu > li:first-of-type {border-left-width: 0;} .super-menu > li:last-of-type {border-right-width: 0;} html[dir="rtl"] .super-menu > li:first-of-type {border-left-width: 1px;border-right-width: 0;} html[dir="rtl"] .super-menu > li:last-of-type {border-left-width: 0;}} body {background-color: red;}'; - self::assertSame($sExpected, $oDoc->render()); + self::assertSame($expected, $document->render()); } /** @@ -796,19 +796,19 @@ public function invalidSelectorsInFile(): void */ public function selectorEscapesInFile(): void { - $oDoc = self::parsedStructureForFile('selector-escapes', Settings::create()->withMultibyteSupport(true)); - $sExpected = '#\\# {color: red;} + $document = self::parsedStructureForFile('selector-escapes', Settings::create()->withMultibyteSupport(true)); + $expected = '#\\# {color: red;} .col-sm-1\\/5 {width: 20%;}'; - self::assertSame($sExpected, $oDoc->render()); + self::assertSame($expected, $document->render()); - $oDoc = self::parsedStructureForFile('invalid-selectors-2', Settings::create()->withMultibyteSupport(true)); - $sExpected = '@media only screen and (max-width: 1215px) {.breadcrumb {padding-left: 10px;} + $document = self::parsedStructureForFile('invalid-selectors-2', Settings::create()->withMultibyteSupport(true)); + $expected = '@media only screen and (max-width: 1215px) {.breadcrumb {padding-left: 10px;} .super-menu > li:first-of-type {border-left-width: 0;} .super-menu > li:last-of-type {border-right-width: 0;} html[dir="rtl"] .super-menu > li:first-of-type {border-left-width: 1px;border-right-width: 0;} html[dir="rtl"] .super-menu > li:last-of-type {border-left-width: 0;}} body {background-color: red;}'; - self::assertSame($sExpected, $oDoc->render()); + self::assertSame($expected, $document->render()); } /** @@ -816,10 +816,10 @@ public function selectorEscapesInFile(): void */ public function identifierEscapesInFile(): void { - $oDoc = self::parsedStructureForFile('identifier-escapes', Settings::create()->withMultibyteSupport(true)); - $sExpected = 'div {font: 14px Font Awesome\\ 5 Pro;font: 14px Font Awesome\\} 5 Pro;' + $document = self::parsedStructureForFile('identifier-escapes', Settings::create()->withMultibyteSupport(true)); + $expected = 'div {font: 14px Font Awesome\\ 5 Pro;font: 14px Font Awesome\\} 5 Pro;' . 'font: 14px Font Awesome\\; 5 Pro;f\\;ont: 14px Font Awesome\\; 5 Pro;}'; - self::assertSame($sExpected, $oDoc->render()); + self::assertSame($expected, $document->render()); } /** @@ -827,13 +827,13 @@ public function identifierEscapesInFile(): void */ public function selectorIgnoresInFile(): void { - $oDoc = self::parsedStructureForFile('selector-ignores', Settings::create()->withMultibyteSupport(true)); - $sExpected = '.some[selectors-may=\'contain-a-{\'] {}' + $document = self::parsedStructureForFile('selector-ignores', Settings::create()->withMultibyteSupport(true)); + $expected = '.some[selectors-may=\'contain-a-{\'] {}' . "\n" . '.this-selector .valid {width: 100px;}' . "\n" . '@media only screen and (min-width: 200px) {.test {prop: val;}}'; - self::assertSame($sExpected, $oDoc->render()); + self::assertSame($expected, $document->render()); } /** @@ -841,16 +841,16 @@ public function selectorIgnoresInFile(): void */ public function keyframeSelectors(): void { - $oDoc = self::parsedStructureForFile( + $document = self::parsedStructureForFile( 'keyframe-selector-validation', Settings::create()->withMultibyteSupport(true) ); - $sExpected = '@-webkit-keyframes zoom {0% {-webkit-transform: scale(1,1);}' + $expected = '@-webkit-keyframes zoom {0% {-webkit-transform: scale(1,1);}' . "\n\t" . '50% {-webkit-transform: scale(1.2,1.2);}' . "\n\t" . '100% {-webkit-transform: scale(1,1);}}'; - self::assertSame($sExpected, $oDoc->render()); + self::assertSame($expected, $document->render()); } /** @@ -878,11 +878,11 @@ public function calcFailure(): void */ public function urlInFileMbOff(): void { - $oDoc = self::parsedStructureForFile('url', Settings::create()->withMultibyteSupport(false)); - $sExpected = 'body {background: #fff url("https://somesite.com/images/someimage.gif") repeat top center;}' + $document = self::parsedStructureForFile('url', Settings::create()->withMultibyteSupport(false)); + $expected = 'body {background: #fff url("https://somesite.com/images/someimage.gif") repeat top center;}' . "\n" . 'body {background-url: url("https://somesite.com/images/someimage.gif");}'; - self::assertSame($sExpected, $oDoc->render()); + self::assertSame($expected, $document->render()); } /** @@ -890,9 +890,9 @@ public function urlInFileMbOff(): void */ public function emptyFile(): void { - $oDoc = self::parsedStructureForFile('-empty', Settings::create()->withMultibyteSupport(true)); - $sExpected = ''; - self::assertSame($sExpected, $oDoc->render()); + $document = self::parsedStructureForFile('-empty', Settings::create()->withMultibyteSupport(true)); + $expected = ''; + self::assertSame($expected, $document->render()); } /** @@ -900,9 +900,9 @@ public function emptyFile(): void */ public function emptyFileMbOff(): void { - $oDoc = self::parsedStructureForFile('-empty', Settings::create()->withMultibyteSupport(false)); - $sExpected = ''; - self::assertSame($sExpected, $oDoc->render()); + $document = self::parsedStructureForFile('-empty', Settings::create()->withMultibyteSupport(false)); + $expected = ''; + self::assertSame($expected, $document->render()); } /** @@ -910,9 +910,9 @@ public function emptyFileMbOff(): void */ public function charsetLenient1(): void { - $oDoc = self::parsedStructureForFile('-charset-after-rule', Settings::create()->withLenientParsing(true)); - $sExpected = '#id {prop: var(--val);}'; - self::assertSame($sExpected, $oDoc->render()); + $document = self::parsedStructureForFile('-charset-after-rule', Settings::create()->withLenientParsing(true)); + $expected = '#id {prop: var(--val);}'; + self::assertSame($expected, $document->render()); } /** @@ -920,9 +920,9 @@ public function charsetLenient1(): void */ public function charsetLenient2(): void { - $oDoc = self::parsedStructureForFile('-charset-in-block', Settings::create()->withLenientParsing(true)); - $sExpected = '@media print {}'; - self::assertSame($sExpected, $oDoc->render()); + $document = self::parsedStructureForFile('-charset-in-block', Settings::create()->withLenientParsing(true)); + $expected = '@media print {}'; + self::assertSame($expected, $document->render()); } /** @@ -930,9 +930,9 @@ public function charsetLenient2(): void */ public function trailingWhitespace(): void { - $oDoc = self::parsedStructureForFile('trailing-whitespace', Settings::create()->withLenientParsing(false)); - $sExpected = 'div {width: 200px;}'; - self::assertSame($sExpected, $oDoc->render()); + $document = self::parsedStructureForFile('trailing-whitespace', Settings::create()->withLenientParsing(false)); + $expected = 'div {width: 200px;}'; + self::assertSame($expected, $document->render()); } /** @@ -1004,14 +1004,14 @@ public function missingPropertyValueLenient(): void /** * Parses structure for file. * - * @param string $sFileName + * @param string $filename * @param Settings|null $oSettings */ - public static function parsedStructureForFile($sFileName, $oSettings = null): Document + public static function parsedStructureForFile($filename, $oSettings = null): Document { - $sFile = __DIR__ . "/fixtures/$sFileName.css"; - $oParser = new Parser(\file_get_contents($sFile), $oSettings); - return $oParser->parse(); + $filename = __DIR__ . "/fixtures/$filename.css"; + $parser = new Parser(\file_get_contents($filename), $oSettings); + return $parser->parse(); } /** @@ -1021,9 +1021,9 @@ public static function parsedStructureForFile($sFileName, $oSettings = null): Do */ public function lineNumbersParsing(): void { - $oDoc = self::parsedStructureForFile('line-numbers'); + $document = self::parsedStructureForFile('line-numbers'); // array key is the expected line number - $aExpected = [ + $expected = [ 1 => [Charset::class], 3 => [CSSNamespace::class], 5 => [AtRuleSet::class], @@ -1034,42 +1034,42 @@ public function lineNumbersParsing(): void 25 => [DeclarationBlock::class], ]; - $aActual = []; - foreach ($oDoc->getContents() as $oContent) { - $aActual[$oContent->getLineNo()] = [\get_class($oContent)]; - if ($oContent instanceof KeyFrame) { - foreach ($oContent->getContents() as $block) { - $aActual[$oContent->getLineNo()][] = $block->getLineNo(); + $actual = []; + foreach ($document->getContents() as $contentItem) { + $actual[$contentItem->getLineNo()] = [\get_class($contentItem)]; + if ($contentItem instanceof KeyFrame) { + foreach ($contentItem->getContents() as $block) { + $actual[$contentItem->getLineNo()][] = $block->getLineNo(); } } } - $aUrlExpected = [7, 26]; // expected line numbers - $aUrlActual = []; - foreach ($oDoc->getAllValues() as $oValue) { - if ($oValue instanceof URL) { - $aUrlActual[] = $oValue->getLineNo(); + $expectedLineNumbers = [7, 26]; + $actualLineNumbers = []; + foreach ($document->getAllValues() as $value) { + if ($value instanceof URL) { + $actualLineNumbers[] = $value->getLineNo(); } } // Checking for the multiline color rule lines 27-31 - $aExpectedColorLines = [28, 29, 30]; - $aDeclBlocks = $oDoc->getAllDeclarationBlocks(); + $expectedColorLineNumbers = [28, 29, 30]; + $declarationBlocks = $document->getAllDeclarationBlocks(); // Choose the 2nd one - $oDeclBlock = $aDeclBlocks[1]; - $aRules = $oDeclBlock->getRules(); + $secondDeclarationBlock = $declarationBlocks[1]; + $rules = $secondDeclarationBlock->getRules(); // Choose the 2nd one - $oColor = $aRules[1]->getValue(); - self::assertSame(27, $aRules[1]->getLineNo()); + $valueOfSecondRule = $rules[1]->getValue(); + self::assertSame(27, $rules[1]->getLineNo()); - $aActualColorLines = []; - foreach ($oColor->getColor() as $oSize) { - $aActualColorLines[] = $oSize->getLineNo(); + $actualColorLineNumbers = []; + foreach ($valueOfSecondRule->getColor() as $oSize) { + $actualColorLineNumbers[] = $oSize->getLineNo(); } - self::assertSame($aExpectedColorLines, $aActualColorLines); - self::assertSame($aUrlExpected, $aUrlActual); - self::assertSame($aExpected, $aActual); + self::assertSame($expectedColorLineNumbers, $actualColorLineNumbers); + self::assertSame($expectedLineNumbers, $actualLineNumbers); + self::assertSame($expected, $actual); } /** @@ -1079,9 +1079,9 @@ public function unexpectedTokenExceptionLineNo(): void { $this->expectException(UnexpectedTokenException::class); - $oParser = new Parser("\ntest: 1;", Settings::create()->beStrict()); + $parser = new Parser("\ntest: 1;", Settings::create()->beStrict()); try { - $oParser->parse(); + $parser->parse(); } catch (UnexpectedTokenException $e) { self::assertSame(2, $e->getLineNo()); throw $e; @@ -1104,10 +1104,10 @@ public function ieHacksStrictParsing(): void */ public function ieHacksParsing(): void { - $oDoc = self::parsedStructureForFile('ie-hacks', Settings::create()->withLenientParsing(true)); - $sExpected = 'p {padding-right: .75rem \\9;background-image: none \\9;color: red \\9\\0;' + $document = self::parsedStructureForFile('ie-hacks', Settings::create()->withLenientParsing(true)); + $expected = 'p {padding-right: .75rem \\9;background-image: none \\9;color: red \\9\\0;' . 'background-color: red \\9\\0;background-color: red \\9\\0 !important;content: "red \\0";content: "red઼";}'; - self::assertSame($sExpected, $oDoc->render()); + self::assertSame($expected, $document->render()); } /** @@ -1117,8 +1117,8 @@ public function ieHacksParsing(): void */ public function commentExtracting(): void { - $oDoc = self::parsedStructureForFile('comments'); - $aNodes = $oDoc->getContents(); + $document = self::parsedStructureForFile('comments'); + $aNodes = $document->getContents(); // Import property. $importComments = $aNodes[0]->getComments(); @@ -1164,8 +1164,8 @@ public function commentExtracting(): void public function flatCommentExtractingOneComment(): void { $parser = new Parser('div {/*Find Me!*/left:10px; text-align:left;}'); - $doc = $parser->parse(); - $contents = $doc->getContents(); + $document = $parser->parse(); + $contents = $document->getContents(); $divRules = $contents[0]->getRules(); $comments = $divRules[0]->getComments(); self::assertCount(1, $comments); @@ -1177,8 +1177,8 @@ public function flatCommentExtractingOneComment(): void public function flatCommentExtractingTwoComments(): void { $parser = new Parser('div {/*Find Me!*/left:10px; /*Find Me Too!*/text-align:left;}'); - $doc = $parser->parse(); - $contents = $doc->getContents(); + $document = $parser->parse(); + $contents = $document->getContents(); $divRules = $contents[0]->getRules(); $rule1Comments = $divRules[0]->getComments(); $rule2Comments = $divRules[1]->getComments(); @@ -1194,8 +1194,8 @@ public function flatCommentExtractingTwoComments(): void public function topLevelCommentExtracting(): void { $parser = new Parser('/*Find Me!*/div {left:10px; text-align:left;}'); - $doc = $parser->parse(); - $contents = $doc->getContents(); + $document = $parser->parse(); + $contents = $document->getContents(); $comments = $contents[0]->getComments(); self::assertCount(1, $comments); self::assertSame('Find Me!', $comments[0]->getComment()); @@ -1208,7 +1208,7 @@ public function microsoftFilterStrictParsing(): void { $this->expectException(UnexpectedTokenException::class); - $oDoc = self::parsedStructureForFile('ms-filter', Settings::create()->beStrict()); + $document = self::parsedStructureForFile('ms-filter', Settings::create()->beStrict()); } /** @@ -1216,10 +1216,10 @@ public function microsoftFilterStrictParsing(): void */ public function microsoftFilterParsing(): void { - $oDoc = self::parsedStructureForFile('ms-filter'); - $sExpected = '.test {filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#80000000",' + $document = self::parsedStructureForFile('ms-filter'); + $expected = '.test {filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#80000000",' . 'endColorstr="#00000000",GradientType=1);}'; - self::assertSame($sExpected, $oDoc->render()); + self::assertSame($expected, $document->render()); } /** @@ -1227,9 +1227,9 @@ public function microsoftFilterParsing(): void */ public function largeSizeValuesInFile(): void { - $oDoc = self::parsedStructureForFile('large-z-index', Settings::create()->withMultibyteSupport(false)); - $sExpected = '.overlay {z-index: 10000000000000000000000;}'; - self::assertSame($sExpected, $oDoc->render()); + $document = self::parsedStructureForFile('large-z-index', Settings::create()->withMultibyteSupport(false)); + $expected = '.overlay {z-index: 10000000000000000000000;}'; + self::assertSame($expected, $document->render()); } /** @@ -1237,14 +1237,14 @@ public function largeSizeValuesInFile(): void */ public function scientificNotationSizeValuesInFile(): void { - $oDoc = $this->parsedStructureForFile( + $document = $this->parsedStructureForFile( 'scientific-notation-numbers', Settings::create()->withMultibyteSupport(false) ); - $sExpected = '' + $expected = '' . 'body {background-color: rgba(62,174,151,3041820656523200167936);' . 'z-index: .030418206565232;font-size: 1em;top: 192.3478px;}'; - self::assertSame($sExpected, $oDoc->render()); + self::assertSame($expected, $document->render()); } /** @@ -1252,15 +1252,15 @@ public function scientificNotationSizeValuesInFile(): void */ public function lonelyImport(): void { - $oDoc = self::parsedStructureForFile('lonely-import'); - $sExpected = '@import url("example.css") only screen and (max-width: 600px);'; - self::assertSame($sExpected, $oDoc->render()); + $document = self::parsedStructureForFile('lonely-import'); + $expected = '@import url("example.css") only screen and (max-width: 600px);'; + self::assertSame($expected, $document->render()); } public function escapedSpecialCaseTokens(): void { - $oDoc = $this->parsedStructureForFile('escaped-tokens'); - $contents = $oDoc->getContents(); + $document = $this->parsedStructureForFile('escaped-tokens'); + $contents = $document->getContents(); $rules = $contents[0]->getRules(); $urlRule = $rules[0]; $calcRule = $rules[1]; From e6632ae5c4810fb6b27490a2e1fba83713cf0361 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 22:17:38 +0200 Subject: [PATCH 037/555] Update phpstan/phpstan requirement from ^1.12.0 to ^1.12.1 (#705) Updates the requirements on [phpstan/phpstan](https://github.com/phpstan/phpstan) to permit the latest version. - [Release notes](https://github.com/phpstan/phpstan/releases) - [Changelog](https://github.com/phpstan/phpstan/blob/1.12.x/CHANGELOG.md) - [Commits](https://github.com/phpstan/phpstan/compare/1.12.0...1.12.1) --- updated-dependencies: - dependency-name: phpstan/phpstan dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 28414b53..d2324f24 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.4.0", "phpstan/extension-installer": "^1.4.2", - "phpstan/phpstan": "^1.12.0", + "phpstan/phpstan": "^1.12.1", "phpstan/phpstan-phpunit": "^1.4.0", "phpunit/phpunit": "^8.5.38", "rector/rector": "^1.2.4" From be0e7ee0e4f0641eb0c1d740b1374765fe8acb54 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Sep 2024 14:13:19 +0200 Subject: [PATCH 038/555] Update phpstan/extension-installer requirement from ^1.4.2 to ^1.4.3 (#708) Updates the requirements on [phpstan/extension-installer](https://github.com/phpstan/extension-installer) to permit the latest version. - [Release notes](https://github.com/phpstan/extension-installer/releases) - [Commits](https://github.com/phpstan/extension-installer/compare/1.4.2...1.4.3) --- updated-dependencies: - dependency-name: phpstan/extension-installer dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index d2324f24..88f1072c 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.4.0", - "phpstan/extension-installer": "^1.4.2", + "phpstan/extension-installer": "^1.4.3", "phpstan/phpstan": "^1.12.1", "phpstan/phpstan-phpunit": "^1.4.0", "phpunit/phpunit": "^8.5.38", From 36ae13e48e5ae75d47c3d85cf262d98264ca8384 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 5 Sep 2024 17:43:22 +0200 Subject: [PATCH 039/555] [TASK] Use fixed versions of the development dependencies (#707) We don't want to have sudden build failures when a new version of a development dependency gets released. Instead, we'll keep having automatic Dependabot updates for our dependencies that will allow us to see the effects of each update before switchting to a new version of a dependency. --- composer.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 88f1072c..0a7453ca 100644 --- a/composer.json +++ b/composer.json @@ -27,12 +27,12 @@ "ext-iconv": "*" }, "require-dev": { - "php-parallel-lint/php-parallel-lint": "^1.4.0", - "phpstan/extension-installer": "^1.4.3", - "phpstan/phpstan": "^1.12.1", - "phpstan/phpstan-phpunit": "^1.4.0", - "phpunit/phpunit": "^8.5.38", - "rector/rector": "^1.2.4" + "php-parallel-lint/php-parallel-lint": "1.4.0", + "phpstan/extension-installer": "1.4.3", + "phpstan/phpstan": "1.12.1", + "phpstan/phpstan-phpunit": "1.4.0", + "phpunit/phpunit": "8.5.38", + "rector/rector": "1.2.4" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" From 6cb99d1a243dba8768327ffffb1a900a433f0540 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 5 Sep 2024 17:50:23 +0200 Subject: [PATCH 040/555] [TASK] Consolidate some deprecration changelog entries (#710) --- CHANGELOG.md | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9460e317..5e292134 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,21 +23,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Deprecated - Deprecate `Parser::setCharset()` and `Parser::getCharset()` (#688) -- Deprecate `DeclarationBlock::createBorderShorthand()` (#578) -- Deprecate `DeclarationBlock::createFontShorthand()` (#580) -- Deprecate `DeclarationBlock::createDimensionsShorthand()` (#579) -- Deprecate `DeclarationBlock::createListStyleShorthand()` (#577) -- Deprecate `DeclarationBlock::createBackgroundShorthand()` (#576) -- Deprecate `DeclarationBlock::createShorthandProperties()` (#575) -- Deprecate `DeclarationBlock::expandListStyleShorthand()` (#574) -- Deprecate `DeclarationBlock::expandBackgroundShorthand()` (#573) -- Deprecate `DeclarationBlock::expandFontShorthand()` (#572) -- Deprecate `DeclarationBlock::expandDimensionsShorthand()` (#571) -- Deprecate `DeclarationBlock::expandBorderShorthand()` (#570) -- Deprecate `DeclarationBlock::createShorthands()` (#569) -- Deprecate `Document::expandShorthands()` (#566) -- Deprecate `Document::createShorthands()` (#567) -- Deprecate `DeclarationBlock::expandShorthands()` (#558) +- Deprecate the expansion of shorthand properties (#578, #580, #579, #577, #576, + #575, #574, #573, #572, #571, #570, #569, #566, #567, #558) ### Removed From 707b5cb359f06751588fb5484b4cb049bd6922fb Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 5 Sep 2024 18:01:13 +0200 Subject: [PATCH 041/555] [TASK] Mark parsing-internal classes and methods as `@internal` (#674) 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. 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 5e292134..83e988da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Changed +- Mark parsing-internal classes and methods as `@internal` (#674) - Block installations on unsupported higher PHP versions (#691) - Improve performance of `Value::parseValue` with many delimiters by refactoring to remove `array_search()` (#413) diff --git a/src/Parsing/Anchor.php b/src/Parsing/Anchor.php index df36f214..f3cea959 100644 --- a/src/Parsing/Anchor.php +++ b/src/Parsing/Anchor.php @@ -4,6 +4,9 @@ namespace Sabberworm\CSS\Parsing; +/** + * @internal + */ class Anchor { /** diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index dc29262b..ab63157d 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -7,6 +7,9 @@ use Sabberworm\CSS\Comment\Comment; use Sabberworm\CSS\Settings; +/** + * @internal + */ class ParserState { /** From 4cbe68d6b4405dff6ca5f9ca716eb9f282311300 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Sep 2024 19:01:28 +0200 Subject: [PATCH 042/555] Update phpstan/phpstan requirement from 1.12.1 to 1.12.2 (#712) Updates the requirements on [phpstan/phpstan](https://github.com/phpstan/phpstan) to permit the latest version. - [Release notes](https://github.com/phpstan/phpstan/releases) - [Changelog](https://github.com/phpstan/phpstan/blob/2.0.x/CHANGELOG.md) - [Commits](https://github.com/phpstan/phpstan/compare/1.12.1...1.12.2) --- updated-dependencies: - dependency-name: phpstan/phpstan dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 0a7453ca..696c0d5f 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "require-dev": { "php-parallel-lint/php-parallel-lint": "1.4.0", "phpstan/extension-installer": "1.4.3", - "phpstan/phpstan": "1.12.1", + "phpstan/phpstan": "1.12.2", "phpstan/phpstan-phpunit": "1.4.0", "phpunit/phpunit": "8.5.38", "rector/rector": "1.2.4" From 8bf7763b90d29890fd1dcbd17fc0f411e7ae41f6 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 5 Sep 2024 19:11:41 +0200 Subject: [PATCH 043/555] [TASK] Set the target milestone for the Dependabot updates (#713) --- .github/dependabot.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 919acb30..da08602a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,6 +6,7 @@ updates: directory: "/" schedule: interval: "daily" + milestone: 1 - package-ecosystem: "composer" directory: "/" @@ -14,3 +15,4 @@ updates: allow: - dependency-type: "development" versioning-strategy: "increase" + milestone: 1 From b6f57b9b14358ff47403be71464472d2b93d4aff Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 8 Sep 2024 17:25:40 +0200 Subject: [PATCH 044/555] [TASK] Mark the shorthand expansion as to be removed earlier (#714) This will allow us to get rid of the code earlier, which will reduce code complexity and make maintenance easier. I will backport those deprecations to 8.x. --- src/CSSList/Document.php | 4 ++-- src/RuleSet/DeclarationBlock.php | 26 +++++++++++++------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/CSSList/Document.php b/src/CSSList/Document.php index aa21d1ec..787d1d56 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -114,7 +114,7 @@ public function getSelectorsBySpecificity($sSpecificitySearch = null) /** * Expands all shorthand properties to their long value. * - * @deprecated This will be removed without substitution in version 10.0. + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function expandShorthands(): void { @@ -126,7 +126,7 @@ public function expandShorthands(): void /** * Create shorthands properties whenever possible. * - * @deprecated This will be removed without substitution in version 10.0. + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function createShorthands(): void { diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 704f49b7..3f4d7bb8 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -157,7 +157,7 @@ public function getSelectors() /** * Splits shorthand declarations (e.g. `margin` or `font`) into their constituent parts. * - * @deprecated This will be removed without substitution in version 10.0. + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function expandShorthands(): void { @@ -172,7 +172,7 @@ public function expandShorthands(): void /** * Creates shorthand declarations (e.g. `margin` or `font`) whenever possible. * - * @deprecated This will be removed without substitution in version 10.0. + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function createShorthands(): void { @@ -191,7 +191,7 @@ public function createShorthands(): void * * Multiple borders are not yet supported as of 3. * - * @deprecated This will be removed without substitution in version 10.0. + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function expandBorderShorthand(): void { @@ -252,7 +252,7 @@ public function expandBorderShorthand(): void * * Handles `margin`, `padding`, `border-color`, `border-style` and `border-width`. * - * @deprecated This will be removed without substitution in version 10.0. + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function expandDimensionsShorthand(): void { @@ -312,7 +312,7 @@ public function expandDimensionsShorthand(): void * (e.g. `font: 300 italic 11px/14px verdana, helvetica, sans-serif;`) * into their constituent parts. * - * @deprecated This will be removed without substitution in version 10.0. + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function expandFontShorthand(): void { @@ -381,7 +381,7 @@ public function expandFontShorthand(): void * * @see http://www.w3.org/TR/21/colors.html#propdef-background * - * @deprecated This will be removed without substitution in version 10.0. + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function expandBackgroundShorthand(): void { @@ -453,7 +453,7 @@ public function expandBackgroundShorthand(): void } /** - * @deprecated This will be removed without substitution in version 10.0. + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function expandListStyleShorthand(): void { @@ -536,7 +536,7 @@ public function expandListStyleShorthand(): void * @param array $aProperties * @param string $sShorthand * - * @deprecated This will be removed without substitution in version 10.0. + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function createShorthandProperties(array $aProperties, $sShorthand): void { @@ -572,7 +572,7 @@ public function createShorthandProperties(array $aProperties, $sShorthand): void } /** - * @deprecated This will be removed without substitution in version 10.0. + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function createBackgroundShorthand(): void { @@ -587,7 +587,7 @@ public function createBackgroundShorthand(): void } /** - * @deprecated This will be removed without substitution in version 10.0. + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function createListStyleShorthand(): void { @@ -604,7 +604,7 @@ public function createListStyleShorthand(): void * * Should be run after `create_dimensions_shorthand`! * - * @deprecated This will be removed without substitution in version 10.0. + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function createBorderShorthand(): void { @@ -621,7 +621,7 @@ public function createBorderShorthand(): void * (margin, padding, border-color, border-style and border-width) * and converts them into shorthand CSS properties. * - * @deprecated This will be removed without substitution in version 10.0. + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function createDimensionsShorthand(): void { @@ -695,7 +695,7 @@ public function createDimensionsShorthand(): void * * At least `font-size` AND `font-family` must be present in order to create a shorthand declaration. * - * @deprecated This will be removed without substitution in version 10.0. + * @deprecated This will be removed without substitution in version 9.0 in #511. */ public function createFontShorthand(): void { From f02253ad157637b12e8e3acde56cd8fdd9e3a7d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 12:10:49 +0200 Subject: [PATCH 045/555] Update rector/rector requirement from 1.2.4 to 1.2.5 (#716) Updates the requirements on [rector/rector](https://github.com/rectorphp/rector) to permit the latest version. - [Release notes](https://github.com/rectorphp/rector/releases) - [Commits](https://github.com/rectorphp/rector/compare/1.2.4...1.2.5) --- updated-dependencies: - dependency-name: rector/rector dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 696c0d5f..91362299 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "phpstan/phpstan": "1.12.2", "phpstan/phpstan-phpunit": "1.4.0", "phpunit/phpunit": "8.5.38", - "rector/rector": "1.2.4" + "rector/rector": "1.2.5" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" From f820122a357d3667566a4e21fdd46fa858b351d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 12:15:11 +0200 Subject: [PATCH 046/555] Update phpstan/phpstan requirement from 1.12.2 to 1.12.3 (#717) Updates the requirements on [phpstan/phpstan](https://github.com/phpstan/phpstan) to permit the latest version. - [Release notes](https://github.com/phpstan/phpstan/releases) - [Changelog](https://github.com/phpstan/phpstan/blob/2.0.x/CHANGELOG.md) - [Commits](https://github.com/phpstan/phpstan/compare/1.12.2...1.12.3) --- updated-dependencies: - dependency-name: phpstan/phpstan dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 91362299..27645e5b 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "require-dev": { "php-parallel-lint/php-parallel-lint": "1.4.0", "phpstan/extension-installer": "1.4.3", - "phpstan/phpstan": "1.12.2", + "phpstan/phpstan": "1.12.3", "phpstan/phpstan-phpunit": "1.4.0", "phpunit/phpunit": "8.5.38", "rector/rector": "1.2.5" From 97cc5e4973cb2ffebe1694963ad530f47147fbe2 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 10 Sep 2024 18:13:44 +0200 Subject: [PATCH 047/555] [TASK] Autoformat the changelog (#718) --- CHANGELOG.md | 110 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 77 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83e988da..f9546e54 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 official support for PHP 8.4 (#657) - Support arithmetic operators in CSS function arguments (#607) - Add support for inserting an item in a CSS list (#545) @@ -23,9 +24,9 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Deprecated -- Deprecate `Parser::setCharset()` and `Parser::getCharset()` (#688) +- Deprecate `Parser::setCharset()` and `Parser::getCharset()` (#688) - Deprecate the expansion of shorthand properties (#578, #580, #579, #577, #576, - #575, #574, #573, #572, #571, #570, #569, #566, #567, #558) + #575, #574, #573, #572, #571, #570, #569, #566, #567, #558) ### Removed @@ -41,7 +42,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). - Fix type errors in PHP strict mode (#664) - Fix comment parsing to support multiple comments (#672) - Fix undefined local variable in `CalcFunction::parse()` (#593) -- 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) @ziegenberg is a new contributor to this release and did a lot of the heavy @@ -69,7 +71,8 @@ lifting. Thanks! :heart: * 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* @@ -84,7 +87,10 @@ lifting. Thanks! :heart: * 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 @@ -96,11 +102,16 @@ lifting. Thanks! :heart: ## 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* @@ -108,16 +119,20 @@ lifting. Thanks! :heart: * 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* @@ -129,7 +144,8 @@ lifting. Thanks! :heart: ### 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) @@ -139,7 +155,8 @@ lifting. Thanks! :heart: ## 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* @@ -156,7 +173,8 @@ lifting. Thanks! :heart: ### 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) @@ -170,22 +188,27 @@ lifting. Thanks! :heart: ### 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* @@ -200,13 +223,15 @@ lifting. Thanks! :heart: ## 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* @@ -224,7 +249,9 @@ lifting. Thanks! :heart: ## 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* @@ -261,18 +288,22 @@ lifting. Thanks! :heart: ### 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) @@ -281,10 +312,18 @@ lifting. Thanks! :heart: ### 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) @@ -292,8 +331,13 @@ lifting. Thanks! :heart: ### 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 18120ca0073db2055fe6498bdf437a2661510bf0 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 18 Sep 2024 19:08:23 +0200 Subject: [PATCH 048/555] [DOCS] Add an API and deprecation policy (#720) As discussed here: https://github.com/MyIntervals/PHP-CSS-Parser/discussions/715 --- .gitattributes | 1 + CHANGELOG.md | 7 ++++ README.md | 5 +++ docs/API-and-deprecation-policy.md | 52 ++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+) create mode 100644 docs/API-and-deprecation-policy.md diff --git a/.gitattributes b/.gitattributes index 70a6d5e1..82a4f0b9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -7,5 +7,6 @@ /CONTRIBUTING.md export-ignore /bin/ export-ignore /config/ export-ignore +/docs/ export-ignore /phpunit.xml export-ignore /tests/ export-ignore diff --git a/CHANGELOG.md b/CHANGELOG.md index f9546e54..7eef20b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). +Please also have a look at our +[API and deprecation policy](docs/API-and-deprecation-policy.md). + ## x.y.z ### Added @@ -46,6 +49,10 @@ This project adheres to [Semantic Versioning](https://semver.org/). characters (#485) - Fix (regression) failure to parse at-rules with strict parsing (#456) +### Documentation + +- Add an API and deprecation policy (#720) + @ziegenberg is a new contributor to this release and did a lot of the heavy lifting. Thanks! :heart: diff --git a/README.md b/README.md index 6bbefade..ed15010e 100644 --- a/README.md +++ b/README.md @@ -780,6 +780,11 @@ classDiagram ValueList --> "*" Value : aComponents ``` +## API and deprecation policy + +Please have a look at our +[API and deprecation policy](docs/API-and-deprecation-policy.md). + ## Contributing Contributions in the form of bug reports, feature requests, or pull requests are diff --git a/docs/API-and-deprecation-policy.md b/docs/API-and-deprecation-policy.md new file mode 100644 index 00000000..57e2acec --- /dev/null +++ b/docs/API-and-deprecation-policy.md @@ -0,0 +1,52 @@ +# API and Deprecation Policy + +## API Policy + +The code in this library is intended to be called by other projects. It is not +intended to be extended. If you want to extend any classes, you're on your own, +and your code might break with any new release of this library. + +Any classes, methods and properties that are `public` and not marked as +`@internal` are considered to be part of the API. Those methods will continue +working in a compatible way over minor and bug-fix releases according +to [Semantic Versioning](https://semver.org/), though we might change the native +type declarations in a way that could break subclasses. + +Any classes, methods and properties that are `protected` or `private` are _not_ +considered part of the API. Please do not rely on them. If you do, you're on +your own. + +Any code that is marked as `@internal` is subject to change or removal without +notice. Please do not call it. There be dragons. + +If a class is marked as `@internal`, all properties and methods of this class +are by definition considered to be internal as well. + +When we change some code from public to `@internal` in a release, the first +release that might change that code in a breaking way will be the next major +release after that. This will allow you to change your code accordingly. We'll +also add since which version the code is internal. + +For example, we might mark some code as `@internal` in version 8.7.0. The first +version that possibly changes this code in a breaking way will then be version +9.0.0. + +Before you upgrade your code to the next major version of this library, please +update to the latest release of the previous major version and make sure that +your code does not reference any code that is marked as `@internal`. + +## Deprecation Policy + +Code that we plan to remove is marked as `@deprecated`. In the corresponding +annotation, we also note in which release the code will be removed. + +When we mark some code as `@deprecated` in a release, we'll usually remove it in +the next major release. We'll also add since which version the code is +deprecated. + +For example, when we mark some code as `@deprecated` in version 8.7.0, we'll +remove it in version 9.0.0 (or sometimes a later major release). + +Before you upgrade your code to the next major version of this library, please +update to the latest release of the previous major version and make sure that +your code does not reference any code that is marked as `@deprecated`. From 74df2cd893eda1e3be46f97ff86f7dedb1a31e99 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 18 Sep 2024 19:31:56 +0200 Subject: [PATCH 049/555] [TASK] State since which version code is `@internal`/`@deprecated` (#722) This will help us avoid breaking things in backports. --- 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 787d1d56..7414fe3d 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -114,7 +114,7 @@ public function getSelectorsBySpecificity($sSpecificitySearch = null) /** * Expands all shorthand properties to their long value. * - * @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(): void { @@ -126,7 +126,7 @@ public function expandShorthands(): void /** * Create shorthands properties whenever possible. * - * @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(): void { diff --git a/src/Parser.php b/src/Parser.php index 72f343b6..53126a2a 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -36,7 +36,7 @@ public function __construct($sText, ?Settings $oParserSettings = null, $iLineNo * * @param string $sCharset * - * @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): void { @@ -46,7 +46,7 @@ public function setCharset($sCharset): void /** * Returns the charset that is used if the CSS does not contain an `@charset` declaration. * - * @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(): void { diff --git a/src/Parsing/Anchor.php b/src/Parsing/Anchor.php index f3cea959..1a1d42ae 100644 --- a/src/Parsing/Anchor.php +++ b/src/Parsing/Anchor.php @@ -5,7 +5,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 ab63157d..b69e1309 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -8,14 +8,14 @@ use Sabberworm\CSS\Settings; /** - * @internal + * @internal since 8.7.0 */ class ParserState { /** * @var null * - * @internal + * @internal since 8.5.2 */ public const EOF = null; diff --git a/src/Property/AtRule.php b/src/Property/AtRule.php index cf0328eb..4d8d74ff 100644 --- a/src/Property/AtRule.php +++ b/src/Property/AtRule.php @@ -15,7 +15,7 @@ interface AtRule extends Renderable, Commentable * * @var string * - * @internal + * @internal since 8.5.2 */ public const BLOCK_RULES = 'media/document/supports/region-style/font-feature-values'; @@ -24,7 +24,7 @@ interface AtRule extends Renderable, Commentable * * @var string * - * @internal + * @internal since 8.5.2 */ public const SET_RULES = 'font-face/counter-style/page/swash/styleset/annotation'; diff --git a/src/Property/KeyframeSelector.php b/src/Property/KeyframeSelector.php index 94e131df..2ab8ca97 100644 --- a/src/Property/KeyframeSelector.php +++ b/src/Property/KeyframeSelector.php @@ -11,7 +11,7 @@ class KeyframeSelector extends Selector * * @var string * - * @internal + * @internal since 8.5.2 */ public const SELECTOR_VALIDATION_RX = '/ ^( diff --git a/src/Property/Selector.php b/src/Property/Selector.php index 53750e0d..9a70d6c3 100644 --- a/src/Property/Selector.php +++ b/src/Property/Selector.php @@ -52,7 +52,7 @@ class Selector * * @var string * - * @internal + * @internal since 8.5.2 */ public const SELECTOR_VALIDATION_RX = '/ ^( diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 3f4d7bb8..f701c1ec 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -157,7 +157,7 @@ public function getSelectors() /** * Splits shorthand declarations (e.g. `margin` or `font`) into their constituent parts. * - * @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(): void { @@ -172,7 +172,7 @@ public function expandShorthands(): void /** * Creates shorthand declarations (e.g. `margin` or `font`) whenever possible. * - * @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(): void { @@ -191,7 +191,7 @@ public function createShorthands(): void * * Multiple borders are not yet supported as of 3. * - * @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(): void { @@ -252,7 +252,7 @@ public function expandBorderShorthand(): void * * Handles `margin`, `padding`, `border-color`, `border-style` and `border-width`. * - * @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(): void { @@ -312,7 +312,7 @@ public function expandDimensionsShorthand(): void * (e.g. `font: 300 italic 11px/14px verdana, helvetica, sans-serif;`) * into their constituent parts. * - * @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(): void { @@ -381,7 +381,7 @@ public function expandFontShorthand(): void * * @see http://www.w3.org/TR/21/colors.html#propdef-background * - * @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(): void { @@ -453,7 +453,7 @@ public function expandBackgroundShorthand(): 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(): void { @@ -536,7 +536,7 @@ public function expandListStyleShorthand(): void * @param array $aProperties * @param string $sShorthand * - * @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): void { @@ -572,7 +572,7 @@ public function createShorthandProperties(array $aProperties, $sShorthand): 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(): void { @@ -587,7 +587,7 @@ public function createBackgroundShorthand(): 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(): void { @@ -604,7 +604,7 @@ public function createListStyleShorthand(): void * * Should be run after `create_dimensions_shorthand`! * - * @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(): void { @@ -621,7 +621,7 @@ public function createBorderShorthand(): void * (margin, padding, border-color, border-style and border-width) * and converts them into shorthand CSS properties. * - * @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(): void { @@ -695,7 +695,7 @@ public function createDimensionsShorthand(): void * * At least `font-size` AND `font-family` must be present in order to create a shorthand declaration. * - * @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(): void { From 76076de8f813d5176b05b8cbee5a6bf601065001 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Sep 2024 10:34:03 +0200 Subject: [PATCH 050/555] Update phpstan/phpstan requirement from 1.12.3 to 1.12.4 (#724) Updates the requirements on [phpstan/phpstan](https://github.com/phpstan/phpstan) to permit the latest version. - [Release notes](https://github.com/phpstan/phpstan/releases) - [Changelog](https://github.com/phpstan/phpstan/blob/2.0.x/CHANGELOG.md) - [Commits](https://github.com/phpstan/phpstan/compare/1.12.3...1.12.4) --- updated-dependencies: - dependency-name: phpstan/phpstan dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 27645e5b..0f152330 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "require-dev": { "php-parallel-lint/php-parallel-lint": "1.4.0", "phpstan/extension-installer": "1.4.3", - "phpstan/phpstan": "1.12.3", + "phpstan/phpstan": "1.12.4", "phpstan/phpstan-phpunit": "1.4.0", "phpunit/phpunit": "8.5.38", "rector/rector": "1.2.5" From 3ba62f18dde7d0608896c6ad19cc4177ba9649fb Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 20 Sep 2024 18:52:17 +0200 Subject: [PATCH 051/555] [TASK] Avoid Hungarian notation in a class (#704) This change does not include any breaking changes, according to the API policy. --- src/Comment/Comment.php | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Comment/Comment.php b/src/Comment/Comment.php index 9bd24ba6..bd573eeb 100644 --- a/src/Comment/Comment.php +++ b/src/Comment/Comment.php @@ -12,21 +12,21 @@ class Comment implements Renderable /** * @var int */ - protected $iLineNo; + protected $lineNumber; /** * @var string */ - protected $sComment; + protected $commentText; /** - * @param string $sComment - * @param int $iLineNo + * @param string $commentText + * @param int $lineNumber */ - public function __construct($sComment = '', $iLineNo = 0) + public function __construct($commentText = '', $lineNumber = 0) { - $this->sComment = $sComment; - $this->iLineNo = $iLineNo; + $this->commentText = $commentText; + $this->lineNumber = $lineNumber; } /** @@ -34,7 +34,7 @@ public function __construct($sComment = '', $iLineNo = 0) */ public function getComment() { - return $this->sComment; + return $this->commentText; } /** @@ -42,15 +42,15 @@ public function getComment() */ public function getLineNo() { - return $this->iLineNo; + return $this->lineNumber; } /** - * @param string $sComment + * @param string $commentText */ - public function setComment($sComment): void + public function setComment($commentText): void { - $this->sComment = $sComment; + $this->commentText = $commentText; } public function __toString(): string @@ -58,8 +58,8 @@ public function __toString(): string return $this->render(new OutputFormat()); } - public function render(OutputFormat $oOutputFormat): string + public function render(OutputFormat $outputFormat): string { - return '/*' . $this->sComment . '*/'; + return '/*' . $this->commentText . '*/'; } } From 1f67618a0d6a912df6a4ef49c961c36f0b9e5fed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Sep 2024 15:09:40 +0200 Subject: [PATCH 052/555] Update phpstan/phpstan requirement from 1.12.4 to 1.12.5 (#727) Updates the requirements on [phpstan/phpstan](https://github.com/phpstan/phpstan) to permit the latest version. - [Release notes](https://github.com/phpstan/phpstan/releases) - [Changelog](https://github.com/phpstan/phpstan/blob/2.0.x/CHANGELOG.md) - [Commits](https://github.com/phpstan/phpstan/compare/1.12.4...1.12.5) --- updated-dependencies: - dependency-name: phpstan/phpstan dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 0f152330..d1e19b9b 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "require-dev": { "php-parallel-lint/php-parallel-lint": "1.4.0", "phpstan/extension-installer": "1.4.3", - "phpstan/phpstan": "1.12.4", + "phpstan/phpstan": "1.12.5", "phpstan/phpstan-phpunit": "1.4.0", "phpunit/phpunit": "8.5.38", "rector/rector": "1.2.5" From 95263f2dcd9a9f5c8fb8d73ba175eac72e4d5485 Mon Sep 17 00:00:00 2001 From: Nathanael Esayeas Date: Tue, 1 Oct 2024 10:53:49 -0500 Subject: [PATCH 053/555] [TASK] Mark Exception classes as final (#731) Fixes #709 --- src/Parsing/OutputException.php | 2 +- src/Parsing/UnexpectedEOFException.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Parsing/OutputException.php b/src/Parsing/OutputException.php index 766b8e50..8b27388a 100644 --- a/src/Parsing/OutputException.php +++ b/src/Parsing/OutputException.php @@ -7,7 +7,7 @@ /** * Thrown if the CSS parser attempts to print something invalid. */ -class OutputException extends SourceException +final class OutputException extends SourceException { /** * @param string $sMessage diff --git a/src/Parsing/UnexpectedEOFException.php b/src/Parsing/UnexpectedEOFException.php index f439e329..17e2a215 100644 --- a/src/Parsing/UnexpectedEOFException.php +++ b/src/Parsing/UnexpectedEOFException.php @@ -9,4 +9,4 @@ * * Extends `UnexpectedTokenException` in order to preserve backwards compatibility. */ -class UnexpectedEOFException extends UnexpectedTokenException {} +final class UnexpectedEOFException extends UnexpectedTokenException {} From 342b49320ace4fc36c495165c47a4a06af906b22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Oct 2024 15:53:15 +0200 Subject: [PATCH 054/555] Update rector/rector requirement from 1.2.5 to 1.2.6 (#732) Updates the requirements on [rector/rector](https://github.com/rectorphp/rector) to permit the latest version. - [Release notes](https://github.com/rectorphp/rector/releases) - [Commits](https://github.com/rectorphp/rector/compare/1.2.5...1.2.6) --- updated-dependencies: - dependency-name: rector/rector dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index d1e19b9b..60588dee 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "phpstan/phpstan": "1.12.5", "phpstan/phpstan-phpunit": "1.4.0", "phpunit/phpunit": "8.5.38", - "rector/rector": "1.2.5" + "rector/rector": "1.2.6" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" From f0a587646a25a92ed6a23e1e1610807eeb8b0eb8 Mon Sep 17 00:00:00 2001 From: Nathanael Esayeas Date: Thu, 3 Oct 2024 09:35:07 -0500 Subject: [PATCH 055/555] Add GitHub Action to Require Tests in Pull Requests (#730) --- .github/workflows/require-tests.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/require-tests.yml diff --git a/.github/workflows/require-tests.yml b/.github/workflows/require-tests.yml new file mode 100644 index 00000000..a4a7a6c6 --- /dev/null +++ b/.github/workflows/require-tests.yml @@ -0,0 +1,21 @@ +name: 'Require Tests on Code Change' + +on: + pull_request_target: + types: [opened] + +jobs: + check: + name: 'Require Tests on Code Change' + + runs-on: ubuntu-22.04 + + steps: + - name: "Execute tests-checker-action" + uses: infection/tests-checker-action@v1.0.2 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + comment: Could you please add tests to make sure this change works as expected? + fileExtensions: '.php' + testDir: 'tests' + testPattern: '*Test.php' From 38589f48950f85fa010cb9f1fc7752aad681d3c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 09:30:51 +0200 Subject: [PATCH 056/555] Update phpstan/phpstan requirement from 1.12.5 to 1.12.6 (#733) Updates the requirements on [phpstan/phpstan](https://github.com/phpstan/phpstan) to permit the latest version. - [Release notes](https://github.com/phpstan/phpstan/releases) - [Changelog](https://github.com/phpstan/phpstan/blob/2.0.x/CHANGELOG.md) - [Commits](https://github.com/phpstan/phpstan/compare/1.12.5...1.12.6) --- updated-dependencies: - dependency-name: phpstan/phpstan dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 60588dee..c1b24314 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "require-dev": { "php-parallel-lint/php-parallel-lint": "1.4.0", "phpstan/extension-installer": "1.4.3", - "phpstan/phpstan": "1.12.5", + "phpstan/phpstan": "1.12.6", "phpstan/phpstan-phpunit": "1.4.0", "phpunit/phpunit": "8.5.38", "rector/rector": "1.2.6" From 5d04fdb9c9a25ccff48c690b66336ba029b709b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 12 Oct 2024 14:37:55 +0200 Subject: [PATCH 057/555] Update rector/rector requirement from 1.2.6 to 1.2.7 (#735) Updates the requirements on [rector/rector](https://github.com/rectorphp/rector) to permit the latest version. - [Release notes](https://github.com/rectorphp/rector/releases) - [Commits](https://github.com/rectorphp/rector/compare/1.2.6...1.2.7) --- updated-dependencies: - dependency-name: rector/rector dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index c1b24314..64244488 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "phpstan/phpstan": "1.12.6", "phpstan/phpstan-phpunit": "1.4.0", "phpunit/phpunit": "8.5.38", - "rector/rector": "1.2.6" + "rector/rector": "1.2.7" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" From 00fdf8a344379aa98c0e9ddd2c714bf0321c1520 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 20 Oct 2024 16:49:27 +0200 Subject: [PATCH 058/555] Update rector/rector requirement from 1.2.7 to 1.2.8 (#737) Updates the requirements on [rector/rector](https://github.com/rectorphp/rector) to permit the latest version. - [Release notes](https://github.com/rectorphp/rector/releases) - [Commits](https://github.com/rectorphp/rector/compare/1.2.7...1.2.8) --- updated-dependencies: - dependency-name: rector/rector dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 64244488..ead3422b 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "phpstan/phpstan": "1.12.6", "phpstan/phpstan-phpunit": "1.4.0", "phpunit/phpunit": "8.5.38", - "rector/rector": "1.2.7" + "rector/rector": "1.2.8" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" From 9974bbf4ea2b0c9b67fe6aba92d4251e664b349e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 20 Oct 2024 16:51:56 +0200 Subject: [PATCH 059/555] Update phpstan/phpstan requirement from 1.12.6 to 1.12.7 (#736) Updates the requirements on [phpstan/phpstan](https://github.com/phpstan/phpstan) to permit the latest version. - [Release notes](https://github.com/phpstan/phpstan/releases) - [Changelog](https://github.com/phpstan/phpstan/blob/2.0.x/CHANGELOG.md) - [Commits](https://github.com/phpstan/phpstan/compare/1.12.6...1.12.7) --- updated-dependencies: - dependency-name: phpstan/phpstan dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index ead3422b..9ce29896 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "require-dev": { "php-parallel-lint/php-parallel-lint": "1.4.0", "phpstan/extension-installer": "1.4.3", - "phpstan/phpstan": "1.12.6", + "phpstan/phpstan": "1.12.7", "phpstan/phpstan-phpunit": "1.4.0", "phpunit/phpunit": "8.5.38", "rector/rector": "1.2.8" From e2bb6b73945285e7abc7dad79739795b57cd64ea Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 20 Oct 2024 19:43:48 +0200 Subject: [PATCH 060/555] [TASK] Add some more tests for parsing comments (#738) Also add a skipped test for the broken behavior that currently is blocking Emogrifier. --- tests/RuleSet/DeclarationBlockTest.php | 49 ++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/RuleSet/DeclarationBlockTest.php b/tests/RuleSet/DeclarationBlockTest.php index 95625d37..871ed194 100644 --- a/tests/RuleSet/DeclarationBlockTest.php +++ b/tests/RuleSet/DeclarationBlockTest.php @@ -5,8 +5,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; /** @@ -428,4 +430,51 @@ public function orderOfElementsMatchingOriginalOrderAfterExpandingShorthands(): \array_map('strval', $lastDeclarationBlock->getRulesAssoc()) ); } + + /** + * @return array + */ + public static function declarationBlocksWithCommentsProvider(): array + { + 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 + * @dataProvider declarationBlocksWithCommentsProvider + */ + public function canRemoveCommentsFromRulesUsingLenientParsing( + string $cssWithComments, + string $cssWithoutComments + ): void { + $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 + * @dataProvider declarationBlocksWithCommentsProvider + */ + public function canRemoveCommentsFromRulesUsingStrictParsing( + string $cssWithComments, + string $cssWithoutComments + ): void { + 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 a5a1f1a088d7068446b6396fb8735eb8e1e26f15 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 24 Oct 2024 17:45:25 +0200 Subject: [PATCH 061/555] [BUGFIX] Revert broken support for multiple comments (#740) This reverts e4c66f62c5df3f53f48d82cb30b20b3007af7574 (#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. --- CHANGELOG.md | 1 - src/Rule/Rule.php | 4 +--- tests/ParserTest.php | 3 +++ tests/RuleSet/DeclarationBlockTest.php | 2 -- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7eef20b7..a0fabf7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,7 +43,6 @@ Please also have a look at our ### Fixed - Fix type errors in PHP strict mode (#664) -- Fix comment parsing to support multiple comments (#672) - Fix undefined local variable in `CalcFunction::parse()` (#593) - Fix PHP notice caused by parsing invalid color values having less than 6 characters (#485) diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index e2a483ce..9770a0ad 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -108,9 +108,7 @@ public static function parse(ParserState $oParserState): Rule $oParserState->consume(';'); } - 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 8bbbd69d..dcbcc1c0 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -1171,11 +1171,14 @@ public function flatCommentExtractingOneComment(): void self::assertCount(1, $comments); self::assertSame('Find Me!', $comments[0]->getComment()); } + /** * @test */ public function flatCommentExtractingTwoComments(): void { + self::markTestSkipped('This is currently broken.'); + $parser = new Parser('div {/*Find Me!*/left:10px; /*Find Me Too!*/text-align:left;}'); $document = $parser->parse(); $contents = $document->getContents(); diff --git a/tests/RuleSet/DeclarationBlockTest.php b/tests/RuleSet/DeclarationBlockTest.php index 871ed194..15430ae9 100644 --- a/tests/RuleSet/DeclarationBlockTest.php +++ b/tests/RuleSet/DeclarationBlockTest.php @@ -467,8 +467,6 @@ public function canRemoveCommentsFromRulesUsingStrictParsing( string $cssWithComments, string $cssWithoutComments ): void { - self::markTestSkipped('This currently crashes, and we need to fix it.'); - $parserSettings = ParserSettings::create()->withLenientParsing(false); $document = (new Parser($cssWithComments, $parserSettings))->parse(); From 35c038954cbd6026c80d6360234e2fe01d6221bb Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 26 Oct 2024 01:09:14 +0200 Subject: [PATCH 062/555] [TASK] Configure PHPUnit to be more strict (#743) --- phpunit.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/phpunit.xml b/phpunit.xml index 96c9fdfa..aab1f10c 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -2,9 +2,13 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.5/phpunit.xsd" beStrictAboutChangesToGlobalState="true" + beStrictAboutOutputDuringTests="true" + beStrictAboutTodoAnnotatedTests="true" cacheResult="false" colors="true" + convertDeprecationsToExceptions="true" forceCoversAnnotation="true" + verbose="true" > From f5ea7855728f32e542fe329003476b76454582eb Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 27 Oct 2024 18:25:13 +0100 Subject: [PATCH 063/555] [DOCS] Explain code reviews in `CONTRIBUTING.md` (#752) This change copies the corresponding section from our sister project. --- CONTRIBUTING.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fd092560..d0deec1e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,3 +33,20 @@ This is the workflow for contributing changes to this project:: maintainer to allow your CI build to run. 1. Wait for a review by the maintainers. 1. Polish your changes as needed until they are ready to be merged. + +## About code reviews + +After you have submitted a pull request, the maintainers will review your +changes. This will probably result in quite a few comments on ways to improve +your pull request. This project receives contributions from developers around +the world, so we need the code to be the most consistent, readable, and +maintainable that it can be. + +Please do not feel frustrated by this - instead please view this both as our +contribution to your pull request as well as a way to learn more about +improving code quality. + +If you would like to know whether an idea would fit in the general strategy of +this project or would like to get feedback on the best architecture for your +ideas, we propose you open a ticket first and discuss your ideas there +first before investing a lot of time in writing code. From 53c52101cdb98cdf19061a51555399343ddc4d5d Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 27 Oct 2024 20:14:48 +0100 Subject: [PATCH 064/555] [DOCS] Add a section on development deps to `CONTRIBUTING.md` (#753) Copied without changes from our sister project. --- CONTRIBUTING.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d0deec1e..899a8e2f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,3 +50,24 @@ If you would like to know whether an idea would fit in the general strategy of this project or would like to get feedback on the best architecture for your ideas, we propose you open a ticket first and discuss your ideas there first before investing a lot of time in writing code. + +## Install the development dependencies + +To install the most important development dependencies, please run the following +command: + +```bash +composer install +``` + +We also have some optional development dependencies that require higher PHP +versions than the lowest PHP version this project supports. Hence they are not +installed by default. + +To install these, you will need to have [PHIVE](https://phar.io/) installed. +You can then run the following command: + +```bash +phive install +``` + From 7a2945541ae1fe7897194dcfe40623e4b2c5eea5 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 30 Oct 2024 23:43:10 +0100 Subject: [PATCH 065/555] [TASK] Create directory structure for making fine-grained tests (#754) We'll rework and sort the tests into these folders: - `Unit` and `Functional`: for tests that do not call deprecated methods, and where PHPUnit should warn if deprecated methods are called - `UnitDeprecated` and `FunctionalDeprecated`: for tests that call deprecated methods, and where PHPUnit should not warn about those For the time being, we still call all tests with the same configuration. --- tests/Functional/.gitkeep | 0 tests/FunctionalDeprecated/.gitkeep | 0 tests/Unit/.gitkeep | 0 tests/UnitDeprecated/.gitkeep | 0 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/Functional/.gitkeep create mode 100644 tests/FunctionalDeprecated/.gitkeep create mode 100644 tests/Unit/.gitkeep create mode 100644 tests/UnitDeprecated/.gitkeep diff --git a/tests/Functional/.gitkeep b/tests/Functional/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tests/FunctionalDeprecated/.gitkeep b/tests/FunctionalDeprecated/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tests/Unit/.gitkeep b/tests/Unit/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tests/UnitDeprecated/.gitkeep b/tests/UnitDeprecated/.gitkeep new file mode 100644 index 00000000..e69de29b From f6feb879e1e6ff7bb87c147310c8bc4a38b49d65 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 31 Oct 2024 15:09:10 +0100 Subject: [PATCH 066/555] [CLEANUP] Autoformat the code (#748) --- src/Parsing/ParserState.php | 1 - src/Value/Size.php | 19 +++++++++++---- tests/CSSList/AtRuleBlockListTest.php | 22 +++++++++-------- tests/ParserTest.php | 5 +++- tests/Value/SizeTest.php | 34 ++++++++++++++++++++++----- 5 files changed, 59 insertions(+), 22 deletions(-) diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index b69e1309..fa146d7e 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -117,7 +117,6 @@ public function getSettings() return $this->oParserSettings; } - public function anchor(): Anchor { return new Anchor($this->iCurrentPosition, $this); diff --git a/src/Value/Size.php b/src/Value/Size.php index 31574720..0ba4bd66 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -20,10 +20,21 @@ class Size extends PrimitiveValue * @var array */ private 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 f996ff82..73e6c38b 100644 --- a/tests/CSSList/AtRuleBlockListTest.php +++ b/tests/CSSList/AtRuleBlockListTest.php @@ -35,17 +35,19 @@ public static function provideSyntacticallyCorrectAtRule(): array 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/ParserTest.php b/tests/ParserTest.php index dcbcc1c0..e57511ff 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -755,7 +755,10 @@ public function emptyGridLineNameLenientInFile(): void */ public function invalidGridLineNameInFile(): void { - $document = self::parsedStructureForFile('invalid-grid-linename', Settings::create()->withMultibyteSupport(true)); + $document = self::parsedStructureForFile( + 'invalid-grid-linename', + Settings::create()->withMultibyteSupport(true) + ); $expected = 'div {}'; self::assertSame($expected, $document->render()); } diff --git a/tests/Value/SizeTest.php b/tests/Value/SizeTest.php index 6a05c3e9..d4c5438a 100644 --- a/tests/Value/SizeTest.php +++ b/tests/Value/SizeTest.php @@ -20,12 +20,34 @@ final class SizeTest extends TestCase public static function provideUnit(): array { $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 d8154d322bbdd6a71c45b97f0b060d08ffce07f8 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 4 Nov 2024 14:37:36 +0100 Subject: [PATCH 067/555] [TASK] Add a `fix` Composer script to run all code fixers (#759) This makes our life as developers a bit more convenient. Also run Rector before the style fixer as Rector is responsible for structural changes that need to be style-fixed after that. --- composer.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 9ce29896..0acbb910 100644 --- a/composer.json +++ b/composer.json @@ -85,9 +85,12 @@ "ci:tests:coverage": "phpunit --do-not-cache-result --coverage-clover=coverage.xml", "ci:tests:sof": "phpunit --stop-on-failure --do-not-cache-result", "ci:tests:unit": "phpunit --do-not-cache-result", + "fix": [ + "@fix:php" + ], "fix:php": [ - "@fix:php:fixer", - "@fix:php:rector" + "@fix:php:rector", + "@fix:php:fixer" ], "fix:php:fixer": "\"./.phive/php-cs-fixer\" --config=config/php-cs-fixer.php fix bin src tests", "fix:php:rector": "rector --config=config/rector.php", @@ -105,6 +108,7 @@ "ci:tests:coverage": "Runs the unit tests with code coverage.", "ci:tests:sof": "Runs the unit tests and stops at the first failure.", "ci:tests:unit": "Runs all unit tests.", + "fix": "Runs all fixers", "fix:php": "Autofixes all autofixable issues in the PHP code.", "fix:php:fixer": "Fixes autofixable issues found by PHP CS Fixer.", "fix:php:rector": "Fixes autofixable issues found by Rector.", From 13b077930e4635019d973684bda2c642bb976559 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Nov 2024 00:26:27 +0000 Subject: [PATCH 068/555] Update rector/rector requirement from 1.2.8 to 1.2.9 (#761) Updates the requirements on [rector/rector](https://github.com/rectorphp/rector) to permit the latest version. - [Release notes](https://github.com/rectorphp/rector/releases) - [Commits](https://github.com/rectorphp/rector/compare/1.2.8...1.2.9) --- updated-dependencies: - dependency-name: rector/rector dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 0acbb910..63e9d1fb 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "phpstan/phpstan": "1.12.7", "phpstan/phpstan-phpunit": "1.4.0", "phpunit/phpunit": "8.5.38", - "rector/rector": "1.2.8" + "rector/rector": "1.2.9" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" From cd5604fbba2c8e6acce63c406734c45f3b98aa72 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 5 Nov 2024 13:37:24 +0100 Subject: [PATCH 069/555] [TASK] Update to PHPUnit 8.5.40 (#762) https://github.com/sebastianbergmann/phpunit/releases/tag/8.5.39 https://github.com/sebastianbergmann/phpunit/releases/tag/8.5.40 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 63e9d1fb..6d94b171 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "phpstan/extension-installer": "1.4.3", "phpstan/phpstan": "1.12.7", "phpstan/phpstan-phpunit": "1.4.0", - "phpunit/phpunit": "8.5.38", + "phpunit/phpunit": "8.5.40", "rector/rector": "1.2.9" }, "suggest": { From 89cfeaff14ac147f5ed0bbe089e31fe2338fd15e Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 5 Nov 2024 18:57:45 +0100 Subject: [PATCH 070/555] [TASK] Prepare for non-PHP-checks on CI (#763) Following this change in Emogrifier and preparing for the addition of composer-normalize: https://github.com/MyIntervals/emogrifier/pull/1363 --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 960f491a..622bb17b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -103,9 +103,9 @@ jobs: fail-fast: false matrix: command: - - fixer - - stan - - rector + - php:fixer + - php:stan + - php:rector php-version: - '8.3' @@ -142,4 +142,4 @@ jobs: phive --no-progress install --trust-gpg-keys BBAB5DF0A0D6672989CF1869E82B2FB314E9906E - name: Run Command - run: composer ci:php:${{ matrix.command }} + run: composer ci:${{ matrix.command }} From 23f406137633e5b3a9fbfb640bd51c872966cf2d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Nov 2024 23:53:38 +0100 Subject: [PATCH 071/555] Update phpstan/phpstan requirement from 1.12.7 to 1.12.8 (#764) Updates the requirements on [phpstan/phpstan](https://github.com/phpstan/phpstan) to permit the latest version. - [Release notes](https://github.com/phpstan/phpstan/releases) - [Changelog](https://github.com/phpstan/phpstan/blob/2.0.x/CHANGELOG.md) - [Commits](https://github.com/phpstan/phpstan/compare/1.12.7...1.12.8) --- updated-dependencies: - dependency-name: phpstan/phpstan dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 6d94b171..1bf0a7bc 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "require-dev": { "php-parallel-lint/php-parallel-lint": "1.4.0", "phpstan/extension-installer": "1.4.3", - "phpstan/phpstan": "1.12.7", + "phpstan/phpstan": "1.12.8", "phpstan/phpstan-phpunit": "1.4.0", "phpunit/phpunit": "8.5.40", "rector/rector": "1.2.9" From 73a41808750ddf929d9226b63c99a057c6b2cb2c Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 7 Nov 2024 20:44:36 +0100 Subject: [PATCH 072/555] [CLEANUP] Normalize the `composer.json` with `composer-normalize` (#766) --- composer.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 1bf0a7bc..72e85ebb 100644 --- a/composer.json +++ b/composer.json @@ -1,14 +1,13 @@ { "name": "sabberworm/php-css-parser", - "type": "library", "description": "Parser for CSS Files written in PHP", + "license": "MIT", + "type": "library", "keywords": [ "parser", "css", "stylesheet" ], - "homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser", - "license": "MIT", "authors": [ { "name": "Raphael Schweikert" @@ -22,6 +21,7 @@ "email": "jake.github@qzdesign.co.uk" } ], + "homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser", "require": { "php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", "ext-iconv": "*" @@ -101,8 +101,8 @@ "ci:dynamic": "Runs all dynamic code checks (i.e., currently, the unit tests).", "ci:php:fixer": "Checks the code style with PHP CS Fixer.", "ci:php:lint": "Checks the syntax of the PHP code.", - "ci:php:stan": "Checks the types with PHPStan.", "ci:php:rector": "Checks the code for possible code updates and refactoring.", + "ci:php:stan": "Checks the types with PHPStan.", "ci:static": "Runs all static code analysis checks for the code.", "ci:tests": "Runs all dynamic tests (i.e., currently, the unit tests).", "ci:tests:coverage": "Runs the unit tests with code coverage.", From 3ef8030a27d18ac78c714b00990a5400857f388d Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 7 Nov 2024 20:55:51 +0100 Subject: [PATCH 073/555] [DOCS] Add a section on testing to `CONTRIBUTING.md` (#767) This change mirrors exactly what we already have at our sister project. --- CONTRIBUTING.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 899a8e2f..3b010446 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -71,3 +71,14 @@ You can then run the following command: phive install ``` +## Unit-test your changes + +Please cover all changes with unit tests and make sure that your code does not +break any existing tests. We will only merge pull requests that include full +code coverage of the fixed bugs and the new features. + +To run the existing PHPUnit tests, run this command: + +```bash +composer ci:tests:unit +``` From 08f56a1a166cd09f80395246ca94e50c8eae2f68 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 7 Nov 2024 22:12:12 +0100 Subject: [PATCH 074/555] [FEATURE] Add composer-normalize to our development toolchain (#765) --- .github/workflows/ci.yml | 3 ++- .phive/phars.xml | 1 + composer.json | 6 ++++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 622bb17b..14624a48 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -103,6 +103,7 @@ jobs: fail-fast: false matrix: command: + - composer:normalize - php:fixer - php:stan - php:rector @@ -139,7 +140,7 @@ jobs: - name: Install development tools run: | - phive --no-progress install --trust-gpg-keys BBAB5DF0A0D6672989CF1869E82B2FB314E9906E + phive --no-progress install --trust-gpg-keys 0FDE18AE1D09E19F60F6B1CBC00543248C87FB13,BBAB5DF0A0D6672989CF1869E82B2FB314E9906E - name: Run Command run: composer ci:${{ matrix.command }} diff --git a/.phive/phars.xml b/.phive/phars.xml index b7baec8f..6af30ed5 100644 --- a/.phive/phars.xml +++ b/.phive/phars.xml @@ -1,4 +1,5 @@ + diff --git a/composer.json b/composer.json index 72e85ebb..0aae9b6d 100644 --- a/composer.json +++ b/composer.json @@ -66,6 +66,7 @@ "@ci:static", "@ci:dynamic" ], + "ci:composer:normalize": "\"./.phive/composer-normalize\" --dry-run", "ci:dynamic": [ "@ci:tests" ], @@ -74,6 +75,7 @@ "ci:php:rector": "rector --no-progress-bar --dry-run --config=config/rector.php", "ci:php:stan": "phpstan --no-progress --configuration=config/phpstan.neon", "ci:static": [ + "@ci:composer:normalize", "@ci:php:fixer", "@ci:php:lint", "@ci:php:rector", @@ -88,7 +90,9 @@ "fix": [ "@fix:php" ], + "fix:composer:normalize": "\"./.phive/composer-normalize\" --no-check-lock", "fix:php": [ + "@fix:composer:normalize", "@fix:php:rector", "@fix:php:fixer" ], @@ -98,6 +102,7 @@ }, "scripts-descriptions": { "ci": "Runs all dynamic and static code checks.", + "ci:composer:normalize": "Checks the formatting and structure of the composer.json.", "ci:dynamic": "Runs all dynamic code checks (i.e., currently, the unit tests).", "ci:php:fixer": "Checks the code style with PHP CS Fixer.", "ci:php:lint": "Checks the syntax of the PHP code.", @@ -109,6 +114,7 @@ "ci:tests:sof": "Runs the unit tests and stops at the first failure.", "ci:tests:unit": "Runs all unit tests.", "fix": "Runs all fixers", + "fix:composer:normalize": "Reformats and sorts the composer.json file.", "fix:php": "Autofixes all autofixable issues in the PHP code.", "fix:php:fixer": "Fixes autofixable issues found by PHP CS Fixer.", "fix:php:rector": "Fixes autofixable issues found by Rector.", From 46cd2e1f4297c06bab254fa329114134864c52e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 10 Nov 2024 19:13:50 +0100 Subject: [PATCH 075/555] Update phpstan/phpstan requirement from 1.12.8 to 1.12.9 (#768) Updates the requirements on [phpstan/phpstan](https://github.com/phpstan/phpstan) to permit the latest version. - [Release notes](https://github.com/phpstan/phpstan/releases) - [Changelog](https://github.com/phpstan/phpstan/blob/2.0.x/CHANGELOG.md) - [Commits](https://github.com/phpstan/phpstan/compare/1.12.8...1.12.9) --- updated-dependencies: - dependency-name: phpstan/phpstan dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 0aae9b6d..46dc7d99 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "require-dev": { "php-parallel-lint/php-parallel-lint": "1.4.0", "phpstan/extension-installer": "1.4.3", - "phpstan/phpstan": "1.12.8", + "phpstan/phpstan": "1.12.9", "phpstan/phpstan-phpunit": "1.4.0", "phpunit/phpunit": "8.5.40", "rector/rector": "1.2.9" From b56f7c6976379575a8ee179e6d1aeee49037f2e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 10 Nov 2024 19:17:26 +0100 Subject: [PATCH 076/555] Update rector/rector requirement from 1.2.9 to 1.2.10 (#769) Updates the requirements on [rector/rector](https://github.com/rectorphp/rector) to permit the latest version. - [Release notes](https://github.com/rectorphp/rector/releases) - [Commits](https://github.com/rectorphp/rector/compare/1.2.9...1.2.10) --- updated-dependencies: - dependency-name: rector/rector dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 46dc7d99..5195e086 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "phpstan/phpstan": "1.12.9", "phpstan/phpstan-phpunit": "1.4.0", "phpunit/phpunit": "8.5.40", - "rector/rector": "1.2.9" + "rector/rector": "1.2.10" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" From 6026011b7fb25287df959071135906246c3cdecc Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 10 Nov 2024 23:35:29 +0100 Subject: [PATCH 077/555] [TASK] Configure a git commit message prefix for Dependabot (#770) --- .github/dependabot.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index da08602a..ca574e58 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,6 +6,8 @@ updates: directory: "/" schedule: interval: "daily" + commit-message: + prefix: "[Dependabot] " milestone: 1 - package-ecosystem: "composer" @@ -15,4 +17,6 @@ updates: allow: - dependency-type: "development" versioning-strategy: "increase" + commit-message: + prefix: "[Dependabot] " milestone: 1 From fa7a20ee855bac26189d850537d7312754c0fd78 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 11 Nov 2024 20:36:55 +0100 Subject: [PATCH 078/555] [TASK] Add a section on coding style to `CONTRIBUTING.md` (#773) This is an exact copy of what we have at our sister project. --- CONTRIBUTING.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3b010446..77013eea 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -82,3 +82,29 @@ To run the existing PHPUnit tests, run this command: ```bash composer ci:tests:unit ``` + +## Coding Style + +Please use the same coding style +([PER 2.0](https://www.php-fig.org/per/coding-style/)) as the rest of the code. +Indentation is four spaces. + +We will only merge pull requests that follow the project's coding style. + +Please check your code with the provided static code analysis tools: + +```bash +composer ci:static +``` + +Please make your code clean, well-readable and easy to understand. + +If you add new methods or fields, please add proper PHPDoc for the new +methods/fields. Please use grammatically correct, complete sentences in the +code documentation. + +You can autoformat your code using the following command: + +```bash +composer fix +``` From 2c49003f757e1ec6a9147201ff8598065a0f78ca Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 11 Nov 2024 20:46:27 +0100 Subject: [PATCH 079/555] [TASK] Clean up the code with Rector (#772) Just the Rector changes, and some redundancy removal related to those, but nothing more. --- src/CSSList/CSSList.php | 8 +++++--- src/OutputFormat.php | 13 ++++--------- src/OutputFormatter.php | 3 +-- src/Parsing/Anchor.php | 3 +-- src/Parsing/ParserState.php | 4 +--- src/RuleSet/RuleSet.php | 2 +- src/Settings.php | 16 ++++++++-------- src/Value/CSSFunction.php | 5 +---- src/Value/CSSString.php | 2 +- src/Value/Color.php | 13 ++----------- 10 files changed, 25 insertions(+), 44 deletions(-) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 1267af29..6f31def8 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -167,9 +167,12 @@ private static function parseAtRule(ParserState $oParserState) $sMediaQuery = null; if (!$oParserState->comes(';')) { $sMediaQuery = \trim($oParserState->consumeUntil([';', ParserState::EOF])); + if ($sMediaQuery === '') { + $sMediaQuery = null; + } } $oParserState->consumeUntil([';', ParserState::EOF], true, true); - return new Import($oLocation, $sMediaQuery ?: null, $iIdentifierLineNum); + return new Import($oLocation, $sMediaQuery, $iIdentifierLineNum); } elseif ($sIdentifier === 'charset') { $oCharsetString = CSSString::parse($oParserState); $oParserState->consumeWhiteSpace(); @@ -240,9 +243,8 @@ private static function parseAtRule(ParserState $oParserState) * We need to check for these versions too. * * @param string $sIdentifier - * @param string $sMatch */ - private static function identifierIs($sIdentifier, $sMatch): bool + private static function identifierIs($sIdentifier, string $sMatch): bool { return (\strcasecmp($sIdentifier, $sMatch) === 0) ?: \preg_match("/^(-\\w+-)?$sMatch$/i", $sIdentifier) === 1; diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 2b5607db..3f126346 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -223,14 +223,13 @@ public function set($aNames, $mValue) } /** - * @param string $sMethodName * @param array $aArguments * * @return mixed * * @throws \Exception */ - public function __call($sMethodName, array $aArguments) + public function __call(string $sMethodName, array $aArguments) { if (\strpos($sMethodName, 'set') === 0) { return $this->set(\substr($sMethodName, 3), $aArguments[0]); @@ -303,17 +302,15 @@ public function level() /** * Creates an instance of this class without any particular formatting settings. */ - public static function create(): OutputFormat + public static function create(): self { return new OutputFormat(); } /** * Creates an instance of this class with a preset for compact formatting. - * - * @return self */ - public static function createCompact() + public static function createCompact(): self { $format = self::create(); $format->set('Space*Rules', '') @@ -327,10 +324,8 @@ public static function createCompact() /** * Creates an instance of this class with a preset for pretty formatting. - * - * @return self */ - public static function createPretty() + public static function createPretty(): self { $format = self::create(); $format->set('Space*Rules', "\n") diff --git a/src/OutputFormatter.php b/src/OutputFormatter.php index 4349f664..6e68037f 100644 --- a/src/OutputFormatter.php +++ b/src/OutputFormatter.php @@ -132,11 +132,10 @@ public function safely($cCode) /** * Clone of the `implode` function, but calls `render` with the current output format instead of `__toString()`. * - * @param string $sSeparator * @param array $aValues * @param bool $bIncreaseLevel */ - public function implode($sSeparator, array $aValues, $bIncreaseLevel = false): string + public function implode(string $sSeparator, array $aValues, $bIncreaseLevel = false): string { $sResult = ''; $oFormat = $this->oFormat; diff --git a/src/Parsing/Anchor.php b/src/Parsing/Anchor.php index 1a1d42ae..545cb035 100644 --- a/src/Parsing/Anchor.php +++ b/src/Parsing/Anchor.php @@ -15,13 +15,12 @@ class Anchor private $iPosition; /** - * @var \Sabberworm\CSS\Parsing\ParserState + * @var ParserState */ private $oParserState; /** * @param int $iPosition - * @param \Sabberworm\CSS\Parsing\ParserState $oParserState */ public function __construct($iPosition, ParserState $oParserState) { diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index fa146d7e..22152fbd 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -362,12 +362,10 @@ public function isEnd(): bool * @param string $consumeEnd * @param array $comments * - * @return string - * * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ - public function consumeUntil($aEnd, $bIncludeEnd = false, $consumeEnd = false, array &$comments = []) + public function consumeUntil($aEnd, $bIncludeEnd = false, $consumeEnd = false, array &$comments = []): string { $aEnd = \is_array($aEnd) ? $aEnd : [$aEnd]; $out = ''; diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index c2fd6e30..e53abd52 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -82,7 +82,7 @@ public static function parseRuleSet(ParserState $oParserState, RuleSet $oRuleSet } else { $oRule = Rule::parse($oParserState); } - if ($oRule) { + if ($oRule instanceof Rule) { $oRuleSet->addRule($oRule); } } diff --git a/src/Settings.php b/src/Settings.php index b5934a05..17e5fa00 100644 --- a/src/Settings.php +++ b/src/Settings.php @@ -56,9 +56,9 @@ public static function create(): Settings * * @param bool $bMultibyteSupport * - * @return self fluent interface + * @return $this fluent interface */ - public function withMultibyteSupport($bMultibyteSupport = true) + public function withMultibyteSupport($bMultibyteSupport = true): self { $this->bMultibyteSupport = $bMultibyteSupport; return $this; @@ -69,9 +69,9 @@ public function withMultibyteSupport($bMultibyteSupport = true) * * @param string $sDefaultCharset * - * @return self fluent interface + * @return $this fluent interface */ - public function withDefaultCharset($sDefaultCharset) + public function withDefaultCharset($sDefaultCharset): self { $this->sDefaultCharset = $sDefaultCharset; return $this; @@ -82,9 +82,9 @@ public function withDefaultCharset($sDefaultCharset) * * @param bool $bLenientParsing * - * @return self fluent interface + * @return $this fluent interface */ - public function withLenientParsing($bLenientParsing = true) + public function withLenientParsing($bLenientParsing = true): self { $this->bLenientParsing = $bLenientParsing; return $this; @@ -93,9 +93,9 @@ public function withLenientParsing($bLenientParsing = true) /** * Configures the parser to choke on invalid rules. * - * @return self fluent interface + * @return $this fluent interface */ - public function beStrict() + public function beStrict(): self { return $this->withLenientParsing(false); } diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index 5902cf77..82b21e8a 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -103,10 +103,7 @@ public function __toString(): string return $this->render(new OutputFormat()); } - /** - * @return string - */ - public function render(OutputFormat $oOutputFormat) + public function render(OutputFormat $oOutputFormat): string { $aArguments = parent::render($oOutputFormat); return "{$this->sName}({$aArguments})"; diff --git a/src/Value/CSSString.php b/src/Value/CSSString.php index 3385f48c..f212bdd8 100644 --- a/src/Value/CSSString.php +++ b/src/Value/CSSString.php @@ -53,7 +53,7 @@ public static function parse(ParserState $oParserState): CSSString $sContent = null; if ($sQuote === null) { // Unquoted strings end in whitespace or with braces, brackets, parentheses - while (!\preg_match('/[\\s{}()<>\\[\\]]/isu', $oParserState->peek())) { + while (\preg_match('/[\\s{}()<>\\[\\]]/isu', $oParserState->peek()) !== 1) { $sResult .= $oParserState->parseCharacter(false); } } else { diff --git a/src/Value/Color.php b/src/Value/Color.php index 706d1d1f..55a6ce3d 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -103,15 +103,9 @@ public static function parse(ParserState $oParserState, bool $bIgnoreCase = fals } /** - * @param float $fVal - * @param float $fFromMin - * @param float $fFromMax - * @param float $fToMin - * @param float $fToMax - * * @return float */ - private static function mapRange($fVal, $fFromMin, $fFromMax, $fToMin, $fToMax) + private static function mapRange(float $fVal, float $fFromMin, float $fFromMax, float $fToMin, float $fToMax) { $fFromRange = $fFromMax - $fFromMin; $fToRange = $fToMax - $fToMin; @@ -151,10 +145,7 @@ public function __toString(): string return $this->render(new OutputFormat()); } - /** - * @return string - */ - public function render(OutputFormat $oOutputFormat) + public function render(OutputFormat $oOutputFormat): string { // Shorthand RGB color values if ($oOutputFormat->getRGBHashNotation() && \implode('', \array_keys($this->aComponents)) === 'rgb') { From c7e6118ad4c4dafcb499064e877b8e0feaca7cfe Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 17 Nov 2024 19:08:00 +0100 Subject: [PATCH 080/555] [TASK] Add another native return type declaration (#774) As suggested by Rector. --- src/Value/Color.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Value/Color.php b/src/Value/Color.php index 55a6ce3d..07ec5b13 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -102,10 +102,7 @@ public static function parse(ParserState $oParserState, bool $bIgnoreCase = fals return new Color($aColor, $oParserState->currentLine()); } - /** - * @return float - */ - private static function mapRange(float $fVal, float $fFromMin, float $fFromMax, float $fToMin, float $fToMax) + private static function mapRange(float $fVal, float $fFromMin, float $fFromMax, float $fToMin, float $fToMax): float { $fFromRange = $fFromMax - $fFromMin; $fToRange = $fToMax - $fToMin; From 3eb42ce6d809c884e08aeeb1a34ca605f9c0becc Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 17 Nov 2024 20:05:45 +0100 Subject: [PATCH 081/555] [TASK] Add more Rector rules (#771) --- config/rector.php | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/config/rector.php b/config/rector.php index 978e0064..4141a011 100644 --- a/config/rector.php +++ b/config/rector.php @@ -3,6 +3,10 @@ declare(strict_types=1); use Rector\Config\RectorConfig; +use Rector\PHPUnit\Set\PHPUnitSetList; +use Rector\Set\ValueObject\LevelSetList; +use Rector\Set\ValueObject\SetList; +use Rector\TypeDeclaration\Rector\ClassMethod\AddVoidReturnTypeWhereNoReturnRector; return RectorConfig::configure() ->withPaths( @@ -11,9 +15,27 @@ __DIR__ . '/../tests', ] ) - ->withPhpSets() - ->withRules( - [ - // AddVoidReturnTypeWhereNoReturnRector::class, - ] - ); + ->withSets([ + // Rector sets + + LevelSetList::UP_TO_PHP_73, + + // SetList::CODE_QUALITY, + // SetList::CODING_STYLE, + // SetList::DEAD_CODE, + // SetList::EARLY_RETURN, + // SetList::INSTANCEOF, + // SetList::NAMING, + // SetList::PRIVATIZATION, + SetList::STRICT_BOOLEANS, + SetList::TYPE_DECLARATION, + + // PHPUnit sets + + PHPUnitSetList::PHPUNIT_80, + // PHPUnitSetList::PHPUNIT_CODE_QUALITY, + ]) + ->withRules([ + AddVoidReturnTypeWhereNoReturnRector::class, + ]) + ->withImportNames(true, true, false); From 428a2ebc841a58e298782148bbc4cc34faa9eca6 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 17 Nov 2024 20:28:53 +0100 Subject: [PATCH 082/555] [FOLLOWUP] Use the PHP 7.2 language level for Rector (#776) Followup to #771 --- config/rector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/rector.php b/config/rector.php index 4141a011..57c98235 100644 --- a/config/rector.php +++ b/config/rector.php @@ -18,7 +18,7 @@ ->withSets([ // Rector sets - LevelSetList::UP_TO_PHP_73, + LevelSetList::UP_TO_PHP_72, // SetList::CODE_QUALITY, // SetList::CODING_STYLE, From 36de90d462db5824351c29abf19537f0d877da8e Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 12 Dec 2024 12:49:08 +0100 Subject: [PATCH 083/555] [TASK] Add some more native return types (#778) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … as suggested by Rector 2.x. --- src/CSSList/Document.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/CSSList/Document.php b/src/CSSList/Document.php index 7414fe3d..8f9426cf 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -42,7 +42,7 @@ public static function parse(ParserState $oParserState): Document * * @return array */ - public function getAllDeclarationBlocks() + public function getAllDeclarationBlocks(): array { /** @var array $aResult */ $aResult = []; @@ -55,7 +55,7 @@ public function getAllDeclarationBlocks() * * @return array */ - public function getAllRuleSets() + public function getAllRuleSets(): array { /** @var array $aResult */ $aResult = []; @@ -75,7 +75,7 @@ public function getAllRuleSets() * * @see RuleSet->getRules() */ - public function getAllValues($mElement = null, $bSearchInFunctionArguments = false) + public function getAllValues($mElement = null, $bSearchInFunctionArguments = false): array { $sSearchString = null; if ($mElement === null) { @@ -103,7 +103,7 @@ public function getAllValues($mElement = null, $bSearchInFunctionArguments = fal * @return array * @example `getSelectorsBySpecificity('>= 100')` */ - public function getSelectorsBySpecificity($sSpecificitySearch = null) + public function getSelectorsBySpecificity($sSpecificitySearch = null): array { /** @var array $aResult */ $aResult = []; From 72899b583af4b481ebc3511897c0612ed10205d0 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 12 Dec 2024 13:33:24 +0100 Subject: [PATCH 084/555] [TASK] Upgrade to PHPStan 2.x and Rector 2.x (#777) --- .github/dependabot.yml | 5 +++++ composer.json | 6 +++--- config/phpstan-baseline.neon | 12 ++++++++++-- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ca574e58..4f6aacc9 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -16,6 +16,11 @@ updates: interval: "daily" allow: - dependency-type: "development" + ignore: + - dependency-name: "phpstan/*" + - dependency-name: "phpunit/phpunit" + versions: [ ">= 9.0.0" ] + - dependency-name: "rector/rector" versioning-strategy: "increase" commit-message: prefix: "[Dependabot] " diff --git a/composer.json b/composer.json index 5195e086..0cec10c8 100644 --- a/composer.json +++ b/composer.json @@ -29,10 +29,10 @@ "require-dev": { "php-parallel-lint/php-parallel-lint": "1.4.0", "phpstan/extension-installer": "1.4.3", - "phpstan/phpstan": "1.12.9", - "phpstan/phpstan-phpunit": "1.4.0", + "phpstan/phpstan": "1.12.12 || 2.0.3", + "phpstan/phpstan-phpunit": "1.4.1 || 2.0.1", "phpunit/phpunit": "8.5.40", - "rector/rector": "1.2.10" + "rector/rector": "1.2.10 || 2.0.0" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 82fcb3f4..a01c9a6c 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -1,12 +1,20 @@ parameters: ignoreErrors: - - message: "#^Call to an undefined method Sabberworm\\\\CSS\\\\OutputFormat\\:\\:setIndentation\\(\\)\\.$#" + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:setIndentation\(\)\.$#' + identifier: method.notFound count: 2 path: ../src/OutputFormat.php - - message: "#^Class Sabberworm\\\\CSS\\\\Value\\\\Size constructor invoked with 5 parameters, 1\\-4 required\\.$#" + message: '#^Class Sabberworm\\CSS\\Value\\Size constructor invoked with 5 parameters, 1\-4 required\.$#' + identifier: arguments.count count: 2 path: ../src/RuleSet/DeclarationBlock.php + - + message: '#^@covers value \\Sabberworm\\CSS\\Value\\Value\:\:parseValue\(\) references an invalid method\.$#' + identifier: phpunit.coversMethod + count: 2 + path: ../tests/ParserTest.php + From 1c1cdda74622722c72a26eb8f8903c2dca169f05 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 13:56:27 +0100 Subject: [PATCH 085/555] [Dependabot] Update phpunit/phpunit requirement from 8.5.40 to 8.5.41 (#779) Updates the requirements on [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) to permit the latest version. - [Release notes](https://github.com/sebastianbergmann/phpunit/releases) - [Changelog](https://github.com/sebastianbergmann/phpunit/blob/8.5.41/ChangeLog-8.5.md) - [Commits](https://github.com/sebastianbergmann/phpunit/compare/8.5.40...8.5.41) --- updated-dependencies: - dependency-name: phpunit/phpunit dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 0cec10c8..4e56d427 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "phpstan/extension-installer": "1.4.3", "phpstan/phpstan": "1.12.12 || 2.0.3", "phpstan/phpstan-phpunit": "1.4.1 || 2.0.1", - "phpunit/phpunit": "8.5.40", + "phpunit/phpunit": "8.5.41", "rector/rector": "1.2.10 || 2.0.0" }, "suggest": { From d601354466f6f7501c82f5b3407e6357547302bf Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 17 Dec 2024 12:46:29 +0100 Subject: [PATCH 086/555] [TASK] Allow generating an empty PHPStan baseline (#782) We're not there yet, but we want to be prepared for when we are. This follows what we have in our sister project. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 4e56d427..5b7f9141 100644 --- a/composer.json +++ b/composer.json @@ -98,7 +98,7 @@ ], "fix:php:fixer": "\"./.phive/php-cs-fixer\" --config=config/php-cs-fixer.php fix bin src tests", "fix:php:rector": "rector --config=config/rector.php", - "phpstan:baseline": "phpstan --configuration=config/phpstan.neon --generate-baseline=config/phpstan-baseline.neon" + "phpstan:baseline": "phpstan --configuration=config/phpstan.neon --generate-baseline=config/phpstan-baseline.neon --allow-empty-baseline" }, "scripts-descriptions": { "ci": "Runs all dynamic and static code checks.", From 3d8e6b68c73a4de6991800ddf9565700eeb1b2f8 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 18 Dec 2024 00:22:55 +0100 Subject: [PATCH 087/555] [TASK] Update the development dependencies (#783) --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 5b7f9141..20159e60 100644 --- a/composer.json +++ b/composer.json @@ -29,10 +29,10 @@ "require-dev": { "php-parallel-lint/php-parallel-lint": "1.4.0", "phpstan/extension-installer": "1.4.3", - "phpstan/phpstan": "1.12.12 || 2.0.3", - "phpstan/phpstan-phpunit": "1.4.1 || 2.0.1", + "phpstan/phpstan": "1.12.13 || 2.0.4", + "phpstan/phpstan-phpunit": "1.4.2 || 2.0.2", "phpunit/phpunit": "8.5.41", - "rector/rector": "1.2.10 || 2.0.0" + "rector/rector": "1.2.10 || 2.0.3" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" From fe2eaa0c1b1325191bc9ccffdeda6ee9cf1f7fae Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 19 Dec 2024 20:46:07 +0100 Subject: [PATCH 088/555] [TASK] Drop redundant PHPStan configuration options (#781) `scanDirectories` is not needed when `paths` is set. --- config/phpstan.neon | 4 ---- 1 file changed, 4 deletions(-) diff --git a/config/phpstan.neon b/config/phpstan.neon index 3d7611a6..f8df3337 100644 --- a/config/phpstan.neon +++ b/config/phpstan.neon @@ -8,10 +8,6 @@ parameters: level: 1 - scanDirectories: - - %currentWorkingDirectory%/bin/ - - %currentWorkingDirectory%/src/ - - %currentWorkingDirectory%/tests/ paths: - %currentWorkingDirectory%/bin/ - %currentWorkingDirectory%/src/ From 56d4c4e8e0b61df2a8c0821f46f4bc5674347177 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 31 Dec 2024 18:41:25 +0100 Subject: [PATCH 089/555] [TASK] Add a section on git commits to `CONTRIBUTING.md` (#784) Copied from our sister project without any changes. Part of #489 --- CONTRIBUTING.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 77013eea..6290793b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -108,3 +108,14 @@ You can autoformat your code using the following command: ```bash composer fix ``` + +## Git commits + +Commit message should have a <= 50-character summary, optionally followed by a +blank line and a more in depth description of 79 characters per line. + +Please use grammatically correct, complete sentences in the commit messages. + +Also, please prefix the subject line of the commit message with either +`[FEATURE]`, `[TASK]`, `[BUGFIX]` OR `[CLEANUP]`. This makes it faster to see +what a commit is about. From 9aba8865fb309fd4f64974e8d88523ffbdb0ccad Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 31 Dec 2024 18:47:53 +0100 Subject: [PATCH 090/555] [TASK] Drop the CI action that requires test coverage (#785) This tool has turned out to be less helpful for our project that we had hoped. --- .github/workflows/require-tests.yml | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 .github/workflows/require-tests.yml diff --git a/.github/workflows/require-tests.yml b/.github/workflows/require-tests.yml deleted file mode 100644 index a4a7a6c6..00000000 --- a/.github/workflows/require-tests.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: 'Require Tests on Code Change' - -on: - pull_request_target: - types: [opened] - -jobs: - check: - name: 'Require Tests on Code Change' - - runs-on: ubuntu-22.04 - - steps: - - name: "Execute tests-checker-action" - uses: infection/tests-checker-action@v1.0.2 - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - comment: Could you please add tests to make sure this change works as expected? - fileExtensions: '.php' - testDir: 'tests' - testPattern: '*Test.php' From d783f0e94ccb58136e9da84f661a310b01b66166 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 3 Jan 2025 16:22:43 +0100 Subject: [PATCH 091/555] [TASK] Add a section on creating PRs to `CONTRIBUTING.md` (#788) Copied without changes from our sister project. Part of #489 --- CONTRIBUTING.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6290793b..292a98c1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -119,3 +119,8 @@ Please use grammatically correct, complete sentences in the commit messages. Also, please prefix the subject line of the commit message with either `[FEATURE]`, `[TASK]`, `[BUGFIX]` OR `[CLEANUP]`. This makes it faster to see what a commit is about. + +## Creating pull requests (PRs) + +When you create a pull request, please +[make your PR editable](https://github.com/blog/2247-improving-collaboration-with-forks). From 13127009efcc638054595710fe7b21ddc264a2ac Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Fri, 17 Jan 2025 20:45:15 +0000 Subject: [PATCH 092/555] [BUGFIX] Parse @font-face src property as comma-delimited list (#790) Fixes #789. Also adds an initial `TestCase` for `Rule/Rule`. --- CHANGELOG.md | 1 + src/Rule/Rule.php | 14 +++++++-- tests/Unit/.gitkeep | 0 tests/Unit/Rule/RuleTest.php | 61 ++++++++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 2 deletions(-) delete mode 100644 tests/Unit/.gitkeep create mode 100644 tests/Unit/Rule/RuleTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index a0fabf7d..4fa23627 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ Please also have a look at our ### Fixed +- Parse `@font-face` `src` property as comma-delimited list (#790) - Fix type errors in PHP strict mode (#664) - Fix undefined local variable in `CalcFunction::parse()` (#593) - Fix PHP notice caused by parsing invalid color values having less than 6 diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index 9770a0ad..c8f39b1c 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -114,16 +114,26 @@ public static function parse(ParserState $oParserState): Rule } /** + * 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): array { if (\preg_match('/^font($|-)/', $sRule)) { return [',', '/', ' ']; } - return [',', ' ', '/']; + + switch ($sRule) { + case 'src': + return [' ', ',']; + default: + return [',', ' ', '/']; + } } /** diff --git a/tests/Unit/.gitkeep b/tests/Unit/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/Unit/Rule/RuleTest.php b/tests/Unit/Rule/RuleTest.php new file mode 100644 index 00000000..8265aa64 --- /dev/null +++ b/tests/Unit/Rule/RuleTest.php @@ -0,0 +1,61 @@ +}> + */ + public static function provideRulesAndExpectedParsedValueListTypes(): array + { + 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 list $expectedTypeClassnames + * + * @dataProvider provideRulesAndExpectedParsedValueListTypes + */ + public function parsesValuesIntoExpectedTypeList(string $rule, array $expectedTypeClassnames): void + { + $subject = Rule::parse(new ParserState($rule, Settings::create())); + + $value = $subject->getValue(); + self::assertInstanceOf(ValueList::class, $value); + + $actualClassnames = \array_map( + /** + * @param Value|string $component + */ + static function ($component): string { + return \is_string($component) ? 'string' : \get_class($component); + }, + $value->getListComponents() + ); + + self::assertSame($expectedTypeClassnames, $actualClassnames); + } +} From 49ddae646f7ad44646741eaca6c35961bbc02f26 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sun, 19 Jan 2025 09:35:32 +0000 Subject: [PATCH 093/555] [CLEANUP] Remove trailing space from changelog (#792) Co-authored-by: Jake Hotson --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fa23627..a1f9a7e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). Please also have a look at our -[API and deprecation policy](docs/API-and-deprecation-policy.md). +[API and deprecation policy](docs/API-and-deprecation-policy.md). ## x.y.z From 4d33fafe32173448e6622ddbaad27f50ca25b817 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sun, 19 Jan 2025 09:44:39 +0000 Subject: [PATCH 094/555] [TASK] Add TestCase for `Value\Color` (#793) This covers expected behaviour for CSS level 3 (some of which is also covered by more general tests). Also included are some commented-out tests for CSS Color Module Level 4, support for which is yet to be implemented, as well as for some syntaxes that should be rejected (but currently are not). Precursor to resolving #755 and supporting CSS Color Module Level 4. Co-authored-by: Jake Hotson --- tests/Unit/Value/ColorTest.php | 219 +++++++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 tests/Unit/Value/ColorTest.php diff --git a/tests/Unit/Value/ColorTest.php b/tests/Unit/Value/ColorTest.php new file mode 100644 index 00000000..08df8573 --- /dev/null +++ b/tests/Unit/Value/ColorTest.php @@ -0,0 +1,219 @@ + + */ + public static function provideValidColorAndExpectedRendering(): array + { + return [ + '3-digit hex color' => [ + '#070', + '#070', + ], + '6-digit hex color that can be represented as 3-digit' => [ + '#007700', + '#070', + ], + '6-digit hex color that cannot be represented as 3-digit' => [ + '#007600', + '#007600', + ], + '4-digit hex color (with alpha)' => [ + '#0707', + 'rgba(0,119,0,.47)', + ], + '8-digit hex color (with alpha)' => [ + '#0077007F', + 'rgba(0,119,0,.5)', + ], + 'legacy rgb that can be represented as 3-digit hex' => [ + 'rgb(0, 119, 0)', + '#070', + ], + 'legacy rgb that cannot be represented as 3-digit hex' => [ + 'rgb(0, 118, 0)', + '#007600', + ], + 'legacy rgba with fractional alpha' => [ + 'rgba(0, 119, 0, 0.5)', + 'rgba(0,119,0,.5)', + ], + 'legacy rgba with percentage alpha' => [ + 'rgba(0, 119, 0, 50%)', + 'rgba(0,119,0,50%)', + ], + /* + 'legacy rgb as rgba' => [ + 'rgba(0, 119, 0)', + 'rgb(0,119,0)', + ], + 'legacy rgba as rgb' => [ + 'rgb(0, 119, 0, 0.5)', + 'rgba(0,119,0,.5)', + ], + 'modern rgb' => [ + 'rgb(0 119 0)', + 'rgb(0,119,0)', + ], + 'modern rgb with none' => [ + 'rgb(none 119 0)', + 'rgb(none 119 0)', + ], + 'modern rgba' => [ + 'rgb(0 119 0 / 0.5)', + 'rgba(0,119,0,.5)', + ], + //*/ + 'legacy hsl' => [ + 'hsl(120, 100%, 25%)', + 'hsl(120,100%,25%)', + ], + 'legacy hsl with deg' => [ + 'hsl(120deg, 100%, 25%)', + 'hsl(120deg,100%,25%)', + ], + 'legacy hsl with grad' => [ + 'hsl(133grad, 100%, 25%)', + 'hsl(133grad,100%,25%)', + ], + 'legacy hsl with rad' => [ + 'hsl(2.094rad, 100%, 25%)', + 'hsl(2.094rad,100%,25%)', + ], + 'legacy hsl with turn' => [ + 'hsl(0.333turn, 100%, 25%)', + 'hsl(.333turn,100%,25%)', + ], + 'legacy hsla with fractional alpha' => [ + 'hsla(120, 100%, 25%, 0.5)', + 'hsla(120,100%,25%,.5)', + ], + 'legacy hsla with percentage alpha' => [ + 'hsla(120, 100%, 25%, 50%)', + 'hsla(120,100%,25%,50%)', + ], + /* + 'legacy hsl as hsla' => [ + 'hsla(120, 100%, 25%)', + 'hsl(120,100%,25%)', + ], + 'legacy hsla as hsl' => [ + 'hsl(120, 100%, 25%, 0.5)', + 'hsla(120,100%,25%,0.5)', + ], + //*/ + ]; + } + + /** + * @test + * + * @dataProvider provideValidColorAndExpectedRendering + */ + public function parsesAndRendersValidColor(string $color, string $expectedRendering): void + { + $subject = Color::parse(new ParserState($color, Settings::create())); + + $renderedResult = (string) $subject; + + self::assertSame($expectedRendering, $renderedResult); + } + + /** + * Browsers reject all these, thus so should the parser. + * + * @return array + */ + public static function provideInvalidColor(): array + { + return [ + 'hex color with 0 digits' => [ + '#', + ], + 'hex color with 1 digit' => [ + '#f', + ], + 'hex color with 2 digits' => [ + '#f0', + ], + 'hex color with 5 digits' => [ + '#ff000', + ], + 'hex color with 7 digits' => [ + '#ff00000', + ], + 'hex color with 9 digits' => [ + '#ff0000000', + ], + 'rgb color with 0 arguments' => [ + 'rgb()', + ], + 'rgb color with 1 argument' => [ + 'rgb(255)', + ], + 'legacy rgb color with 2 arguments' => [ + 'rgb(255, 0)', + ], + 'legacy rgb color with 5 arguments' => [ + 'rgb(255, 0, 0, 0.5, 0)', + ], + /* + 'legacy rgb color with invalid unit' => [ + 'rgb(255, 0px, 0)', + ], + //*/ + 'hsl color with 0 arguments' => [ + 'hsl()', + ], + 'hsl color with 1 argument' => [ + 'hsl(0)', + ], + 'legacy hsl color with 2 arguments' => [ + 'hsl(0, 100%)', + ], + 'legacy hsl color with 5 arguments' => [ + 'hsl(0, 100%, 50%, 0.5, 0)', + ], + /* + 'legacy hsl color without % for S/L units' => [ + 'hsl(0, 1, 0.5)' + ], + 'legacy hsl color with invalid unit for H' => [ + 'hsl(0px, 100%, 50%)' + ], + //*/ + ]; + } + + /** + * @test + * + * @dataProvider provideInvalidColor + */ + public function throwsExceptionWithInvalidColor(string $color): void + { + $this->expectException(SourceException::class); + + Color::parse(new ParserState($color, Settings::create())); + } +} From f67aa1200ce4b55f53d97910e74944bda865c924 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 19 Jan 2025 18:33:32 +0100 Subject: [PATCH 095/555] [BUGFIX] Add missing import for a type annotation in a test (#795) --- tests/Unit/Rule/RuleTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Unit/Rule/RuleTest.php b/tests/Unit/Rule/RuleTest.php index 8265aa64..604ace1c 100644 --- a/tests/Unit/Rule/RuleTest.php +++ b/tests/Unit/Rule/RuleTest.php @@ -9,6 +9,7 @@ use Sabberworm\CSS\Settings; use Sabberworm\CSS\Rule\Rule; use Sabberworm\CSS\Value\RuleValueList; +use Sabberworm\CSS\Value\Value; use Sabberworm\CSS\Value\ValueList; /** From bc511bb93ecee57ebf00b067e41be6518f781ce4 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Tue, 21 Jan 2025 08:53:22 +0000 Subject: [PATCH 096/555] [TASK] Disable PHP CS Fixer `statement_indentation` rule (#798) Unfortunately, this has been broken for comments preceding a `case` within a `switch` since PHP CS Fixer version 3.9.1. See - https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues/6572 - https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/pull/6490 - https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/pull/6490#issuecomment-1874110032 - https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/pull/7624 A configuration option `stick_comment_to_next_continuous_control_statement` was added, but this has not yet been extended to cover `case` within a `switch`. So, for the time being, we cannot use this rule if we want to comment `case`s within a `swtich`. Co-authored-by: Jake Hotson --- config/php-cs-fixer.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/php-cs-fixer.php b/config/php-cs-fixer.php index 0a1224ee..96b39bbb 100644 --- a/config/php-cs-fixer.php +++ b/config/php-cs-fixer.php @@ -96,5 +96,8 @@ // string notation 'single_quote' => true, 'string_implicit_backslashes' => ['single_quoted' => 'escape'], + + // whitespace + 'statement_indentation' => false, ] ); From f3ea6a759146d2d3fd92f8672779525ac351c7eb Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Wed, 22 Jan 2025 17:52:27 +0000 Subject: [PATCH 097/555] [FEATURE] Support `rgba` and `rgb` (etc.) being aliases (#797) As of CSS Color Module Level 4, `rgba` is an alias of `rgb`; likewise, `hsla` is an alias of `hsl`. This change allows any of the above color functions to contain either three or four arguments, with alpha assumed as the fourth, and allowed to be absent. --- CHANGELOG.md | 2 ++ src/Value/Color.php | 40 +++++++++++++++++++++++++++++----- tests/Unit/Value/ColorTest.php | 8 +++---- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1f9a7e9..d3704586 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ Please also have a look at our ### Added +- Partial support for CSS Color Module Level 4 syntax: + - `rgb` and `rgba`, and `hsl` and `hsla` are now aliases (#797} - Add official support for PHP 8.4 (#657) - Support arithmetic operators in CSS function arguments (#607) - Add support for inserting an item in a CSS list (#545) diff --git a/src/Value/Color.php b/src/Value/Color.php index 07ec5b13..f62bda3c 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -72,23 +72,51 @@ public static function parse(ParserState $oParserState, bool $bIgnoreCase = fals $oParserState->consumeWhiteSpace(); $oParserState->consume('('); + // CSS Color Module Level 4 says that `rgb` and `rgba` are now aliases; likewise `hsl` and `hsla`. + // So, attempt to parse with the `a`, and allow for it not being there. + switch ($sColorMode) { + case 'rgb': + $colorModeForParsing = 'rgba'; + $mayHaveOptionalAlpha = true; + break; + case 'hsl': + $colorModeForParsing = 'hsla'; + $mayHaveOptionalAlpha = true; + break; + case 'rgba': + // This is handled identically to the following case. + case 'hsla': + $colorModeForParsing = $sColorMode; + $mayHaveOptionalAlpha = true; + break; + default: + $colorModeForParsing = $sColorMode; + $mayHaveOptionalAlpha = false; + } + $bContainsVar = false; - $iLength = $oParserState->strlen($sColorMode); + $iLength = $oParserState->strlen($colorModeForParsing); for ($i = 0; $i < $iLength; ++$i) { $oParserState->consumeWhiteSpace(); if ($oParserState->comes('var')) { - $aColor[$sColorMode[$i]] = CSSFunction::parseIdentifierOrFunction($oParserState); + $aColor[$colorModeForParsing[$i]] = CSSFunction::parseIdentifierOrFunction($oParserState); $bContainsVar = true; } else { - $aColor[$sColorMode[$i]] = Size::parse($oParserState, true); + $aColor[$colorModeForParsing[$i]] = Size::parse($oParserState, true); } - if ($bContainsVar && $oParserState->comes(')')) { - // With a var argument the function can have fewer arguments + // This must be done first, to consume comments as well, so that the `comes` test will work. + $oParserState->consumeWhiteSpace(); + + // With a `var` argument, the function can have fewer arguments. + // And as of CSS Color Module Level 4, the alpha argument is optional. + $canCloseNow = + $bContainsVar || + ($mayHaveOptionalAlpha && $i >= $iLength - 2); + if ($canCloseNow && $oParserState->comes(')')) { break; } - $oParserState->consumeWhiteSpace(); if ($i < ($iLength - 1)) { $oParserState->consume(','); } diff --git a/tests/Unit/Value/ColorTest.php b/tests/Unit/Value/ColorTest.php index 08df8573..b4799aa8 100644 --- a/tests/Unit/Value/ColorTest.php +++ b/tests/Unit/Value/ColorTest.php @@ -62,15 +62,15 @@ public static function provideValidColorAndExpectedRendering(): array 'rgba(0, 119, 0, 50%)', 'rgba(0,119,0,50%)', ], - /* 'legacy rgb as rgba' => [ 'rgba(0, 119, 0)', - 'rgb(0,119,0)', + '#070', ], 'legacy rgba as rgb' => [ 'rgb(0, 119, 0, 0.5)', 'rgba(0,119,0,.5)', ], + /* 'modern rgb' => [ 'rgb(0 119 0)', 'rgb(0,119,0)', @@ -112,16 +112,14 @@ public static function provideValidColorAndExpectedRendering(): array 'hsla(120, 100%, 25%, 50%)', 'hsla(120,100%,25%,50%)', ], - /* 'legacy hsl as hsla' => [ 'hsla(120, 100%, 25%)', 'hsl(120,100%,25%)', ], 'legacy hsla as hsl' => [ 'hsl(120, 100%, 25%, 0.5)', - 'hsla(120,100%,25%,0.5)', + 'hsla(120,100%,25%,.5)', ], - //*/ ]; } From 9e79e16ff03980ba8e96cb98d4243c934e6391e3 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Thu, 23 Jan 2025 16:09:29 +0000 Subject: [PATCH 098/555] [CLEANUP] Split `Color::parse` into separate methods (#799) One for hex colors, and one for color functions. This reduces cyclomatic complexity on a per-method basis. --- src/Value/Color.php | 196 ++++++++++++++++++++++++-------------------- 1 file changed, 108 insertions(+), 88 deletions(-) diff --git a/src/Value/Color.php b/src/Value/Color.php index f62bda3c..d08822b7 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -30,104 +30,124 @@ public function __construct(array $aColor, $iLineNo = 0) */ public static function parse(ParserState $oParserState, bool $bIgnoreCase = false): CSSFunction { - $aColor = []; - if ($oParserState->comes('#')) { - $oParserState->consume('#'); - $sValue = $oParserState->parseIdentifier(false); - if ($oParserState->strlen($sValue) === 3) { - $sValue = $sValue[0] . $sValue[0] . $sValue[1] . $sValue[1] . $sValue[2] . $sValue[2]; - } elseif ($oParserState->strlen($sValue) === 4) { - $sValue = $sValue[0] . $sValue[0] . $sValue[1] . $sValue[1] . $sValue[2] . $sValue[2] . $sValue[3] - . $sValue[3]; - } + return + $oParserState->comes('#') + ? self::parseHexColor($oParserState) + : self::parseColorFunction($oParserState); + } - if ($oParserState->strlen($sValue) === 8) { - $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()), - 'a' => new Size( - \round(self::mapRange(\intval($sValue[6] . $sValue[7], 16), 0, 255, 0, 1), 2), - null, - true, - $oParserState->currentLine() - ), - ]; - } 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', + /** + * @throws UnexpectedEOFException + * @throws UnexpectedTokenException + */ + private static function parseHexColor(ParserState $oParserState): CSSFunction + { + $oParserState->consume('#'); + $sValue = $oParserState->parseIdentifier(false); + if ($oParserState->strlen($sValue) === 3) { + $sValue = $sValue[0] . $sValue[0] . $sValue[1] . $sValue[1] . $sValue[2] . $sValue[2]; + } elseif ($oParserState->strlen($sValue) === 4) { + $sValue = $sValue[0] . $sValue[0] . $sValue[1] . $sValue[1] . $sValue[2] . $sValue[2] . $sValue[3] + . $sValue[3]; + } + + if ($oParserState->strlen($sValue) === 8) { + $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()), + 'a' => new Size( + \round(self::mapRange(\intval($sValue[6] . $sValue[7], 16), 0, 255, 0, 1), 2), + null, + true, $oParserState->currentLine() - ); - } + ), + ]; + } 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 { - $sColorMode = $oParserState->parseIdentifier(true); + throw new UnexpectedTokenException( + 'Invalid hex color value', + $sValue, + 'custom', + $oParserState->currentLine() + ); + } + + return new Color($aColor, $oParserState->currentLine()); + } + + /** + * @throws UnexpectedEOFException + * @throws UnexpectedTokenException + */ + private static function parseColorFunction(ParserState $oParserState): CSSFunction + { + $aColor = []; + + $sColorMode = $oParserState->parseIdentifier(true); + $oParserState->consumeWhiteSpace(); + $oParserState->consume('('); + + // CSS Color Module Level 4 says that `rgb` and `rgba` are now aliases; likewise `hsl` and `hsla`. + // So, attempt to parse with the `a`, and allow for it not being there. + switch ($sColorMode) { + case 'rgb': + $colorModeForParsing = 'rgba'; + $mayHaveOptionalAlpha = true; + break; + case 'hsl': + $colorModeForParsing = 'hsla'; + $mayHaveOptionalAlpha = true; + break; + case 'rgba': + // This is handled identically to the following case. + case 'hsla': + $colorModeForParsing = $sColorMode; + $mayHaveOptionalAlpha = true; + break; + default: + $colorModeForParsing = $sColorMode; + $mayHaveOptionalAlpha = false; + } + + $bContainsVar = false; + $iLength = $oParserState->strlen($colorModeForParsing); + for ($i = 0; $i < $iLength; ++$i) { $oParserState->consumeWhiteSpace(); - $oParserState->consume('('); - - // CSS Color Module Level 4 says that `rgb` and `rgba` are now aliases; likewise `hsl` and `hsla`. - // So, attempt to parse with the `a`, and allow for it not being there. - switch ($sColorMode) { - case 'rgb': - $colorModeForParsing = 'rgba'; - $mayHaveOptionalAlpha = true; - break; - case 'hsl': - $colorModeForParsing = 'hsla'; - $mayHaveOptionalAlpha = true; - break; - case 'rgba': - // This is handled identically to the following case. - case 'hsla': - $colorModeForParsing = $sColorMode; - $mayHaveOptionalAlpha = true; - break; - default: - $colorModeForParsing = $sColorMode; - $mayHaveOptionalAlpha = false; + if ($oParserState->comes('var')) { + $aColor[$colorModeForParsing[$i]] = CSSFunction::parseIdentifierOrFunction($oParserState); + $bContainsVar = true; + } else { + $aColor[$colorModeForParsing[$i]] = Size::parse($oParserState, true); } - $bContainsVar = false; - $iLength = $oParserState->strlen($colorModeForParsing); - for ($i = 0; $i < $iLength; ++$i) { - $oParserState->consumeWhiteSpace(); - if ($oParserState->comes('var')) { - $aColor[$colorModeForParsing[$i]] = CSSFunction::parseIdentifierOrFunction($oParserState); - $bContainsVar = true; - } else { - $aColor[$colorModeForParsing[$i]] = Size::parse($oParserState, true); - } - - // This must be done first, to consume comments as well, so that the `comes` test will work. - $oParserState->consumeWhiteSpace(); - - // With a `var` argument, the function can have fewer arguments. - // And as of CSS Color Module Level 4, the alpha argument is optional. - $canCloseNow = - $bContainsVar || - ($mayHaveOptionalAlpha && $i >= $iLength - 2); - if ($canCloseNow && $oParserState->comes(')')) { - break; - } - - if ($i < ($iLength - 1)) { - $oParserState->consume(','); - } + // This must be done first, to consume comments as well, so that the `comes` test will work. + $oParserState->consumeWhiteSpace(); + + // With a `var` argument, the function can have fewer arguments. + // And as of CSS Color Module Level 4, the alpha argument is optional. + $canCloseNow = + $bContainsVar || + ($mayHaveOptionalAlpha && $i >= $iLength - 2); + if ($canCloseNow && $oParserState->comes(')')) { + break; } - $oParserState->consume(')'); - if ($bContainsVar) { - return new CSSFunction($sColorMode, \array_values($aColor), ',', $oParserState->currentLine()); + if ($i < ($iLength - 1)) { + $oParserState->consume(','); } } - return new Color($aColor, $oParserState->currentLine()); + $oParserState->consume(')'); + + return + $bContainsVar + ? new CSSFunction($sColorMode, \array_values($aColor), ',', $oParserState->currentLine()) + : new Color($aColor, $oParserState->currentLine()); } private static function mapRange(float $fVal, float $fFromMin, float $fFromMax, float $fToMin, float $fToMax): float From 9c70d857880023f7b65e1facd2047ef1e49dc10a Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Thu, 23 Jan 2025 23:03:36 +0000 Subject: [PATCH 099/555] [FEATURE] Parse color functions with modern syntax (#800) Also include tests for color functions with CSS variable substitutions where they use the "legacy" syntax. Resolves #755. --- CHANGELOG.md | 3 +- src/Value/Color.php | 34 ++++++- tests/Unit/Value/ColorTest.php | 164 ++++++++++++++++++++++++++++++++- 3 files changed, 197 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3704586..a92ad218 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,8 +10,9 @@ Please also have a look at our ### Added -- Partial support for CSS Color Module Level 4 syntax: +- Partial support for CSS Color Module Level 4: - `rgb` and `rgba`, and `hsl` and `hsla` are now aliases (#797} + - Parse color functions that use the "modern" syntax (#800) - Add official support for PHP 8.4 (#657) - Support arithmetic operators in CSS function arguments (#607) - Add support for inserting an item in a CSS list (#545) diff --git a/src/Value/Color.php b/src/Value/Color.php index d08822b7..5c716678 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -116,6 +116,7 @@ private static function parseColorFunction(ParserState $oParserState): CSSFuncti } $bContainsVar = false; + $isLegacySyntax = false; $iLength = $oParserState->strlen($colorModeForParsing); for ($i = 0; $i < $iLength; ++$i) { $oParserState->consumeWhiteSpace(); @@ -138,9 +139,40 @@ private static function parseColorFunction(ParserState $oParserState): CSSFuncti break; } - if ($i < ($iLength - 1)) { + // "Legacy" syntax is comma-delimited. + // "Modern" syntax is space-delimited, with `/` as alpha delimiter. + // They cannot be mixed. + if ($i === 0) { + // An immediate closing parenthesis is not valid. + if ($oParserState->comes(')')) { + throw new UnexpectedTokenException( + 'Color function with no arguments', + '', + 'custom', + $oParserState->currentLine() + ); + } + $isLegacySyntax = $oParserState->comes(','); + } + + if ($isLegacySyntax && $i < ($iLength - 1)) { $oParserState->consume(','); } + + // In the "modern" syntax, the alpha value must be delimited with `/`. + if (!$isLegacySyntax) { + if ($bContainsVar) { + // If the `var` substitution encompasses more than one argument, + // the alpha deliminator may come at any time. + if ($oParserState->comes('/')) { + $oParserState->consume('/'); + } + } elseif (($colorModeForParsing[$i + 1] ?? '') === 'a') { + // Alpha value is the next expected argument. + // Since a closing parenthesis was not found, a `/` separator is now required. + $oParserState->consume('/'); + } + } } $oParserState->consume(')'); diff --git a/tests/Unit/Value/ColorTest.php b/tests/Unit/Value/ColorTest.php index b4799aa8..822ba5be 100644 --- a/tests/Unit/Value/ColorTest.php +++ b/tests/Unit/Value/ColorTest.php @@ -70,20 +70,142 @@ public static function provideValidColorAndExpectedRendering(): array 'rgb(0, 119, 0, 0.5)', 'rgba(0,119,0,.5)', ], - /* 'modern rgb' => [ 'rgb(0 119 0)', - 'rgb(0,119,0)', + '#070', ], + /* 'modern rgb with none' => [ 'rgb(none 119 0)', 'rgb(none 119 0)', ], + //*/ 'modern rgba' => [ 'rgb(0 119 0 / 0.5)', 'rgba(0,119,0,.5)', ], + /* + 'modern rgba with none as alpha' => [ + 'rgb(0 119 0 / none)', + 'rgba(0 119 0 / none)', + ], //*/ + 'legacy rgb with var for R' => [ + 'rgb(var(--r), 119, 0)', + 'rgb(var(--r),119,0)', + ], + 'legacy rgb with var for G' => [ + 'rgb(0, var(--g), 0)', + 'rgb(0,var(--g),0)', + ], + 'legacy rgb with var for B' => [ + 'rgb(0, 119, var(--b))', + 'rgb(0,119,var(--b))', + ], + 'legacy rgb with var for RG' => [ + 'rgb(var(--rg), 0)', + 'rgb(var(--rg),0)', + ], + 'legacy rgb with var for GB' => [ + 'rgb(0, var(--gb))', + 'rgb(0,var(--gb))', + ], + 'legacy rgba with var for R' => [ + 'rgba(var(--r), 119, 0, 0.5)', + 'rgba(var(--r),119,0,.5)', + ], + 'legacy rgba with var for G' => [ + 'rgba(0, var(--g), 0, 0.5)', + 'rgba(0,var(--g),0,.5)', + ], + 'legacy rgba with var for B' => [ + 'rgb(0, 119, var(--b), 0.5)', + 'rgb(0,119,var(--b),.5)', + ], + 'legacy rgba with var for A' => [ + 'rgba(0, 119, 0, var(--a))', + 'rgba(0,119,0,var(--a))', + ], + 'legacy rgba with var for RG' => [ + 'rgba(var(--rg), 0, 0.5)', + 'rgba(var(--rg),0,.5)', + ], + 'legacy rgba with var for GB' => [ + 'rgba(0, var(--gb), 0.5)', + 'rgba(0,var(--gb),.5)', + ], + 'legacy rgba with var for BA' => [ + 'rgba(0, 119, var(--ba))', + 'rgba(0,119,var(--ba))', + ], + 'legacy rgba with var for RGB' => [ + 'rgba(var(--rgb), 0.5)', + 'rgba(var(--rgb),.5)', + ], + 'legacy rgba with var for GBA' => [ + 'rgba(0, var(--gba))', + 'rgba(0,var(--gba))', + ], + 'modern rgb with var for R' => [ + 'rgb(var(--r) 119 0)', + 'rgb(var(--r),119,0)', + ], + 'modern rgb with var for G' => [ + 'rgb(0 var(--g) 0)', + 'rgb(0,var(--g),0)', + ], + 'modern rgb with var for B' => [ + 'rgb(0 119 var(--b))', + 'rgb(0,119,var(--b))', + ], + 'modern rgb with var for RG' => [ + 'rgb(var(--rg) 0)', + 'rgb(var(--rg),0)', + ], + 'modern rgb with var for GB' => [ + 'rgb(0 var(--gb))', + 'rgb(0,var(--gb))', + ], + 'modern rgba with var for R' => [ + 'rgba(var(--r) 119 0 / 0.5)', + 'rgba(var(--r),119,0,.5)', + ], + 'modern rgba with var for G' => [ + 'rgba(0 var(--g) 0 / 0.5)', + 'rgba(0,var(--g),0,.5)', + ], + 'modern rgba with var for B' => [ + 'rgba(0 119 var(--b) / 0.5)', + 'rgba(0,119,var(--b),.5)', + ], + 'modern rgba with var for A' => [ + 'rgba(0 119 0 / var(--a))', + 'rgba(0,119,0,var(--a))', + ], + 'modern rgba with var for RG' => [ + 'rgba(var(--rg) 0 / 0.5)', + 'rgba(var(--rg),0,.5)', + ], + 'modern rgba with var for GB' => [ + 'rgba(0 var(--gb) / 0.5)', + 'rgba(0,var(--gb),.5)', + ], + 'modern rgba with var for BA' => [ + 'rgba(0 119 var(--ba))', + 'rgba(0,119,var(--ba))', + ], + 'modern rgba with var for RGB' => [ + 'rgba(var(--rgb) / 0.5)', + 'rgba(var(--rgb),.5)', + ], + 'modern rgba with var for GBA' => [ + 'rgba(0 var(--gba))', + 'rgba(0,var(--gba))', + ], + 'rgba with var for RGBA' => [ + 'rgba(var(--rgba))', + 'rgba(var(--rgba))', + ], 'legacy hsl' => [ 'hsl(120, 100%, 25%)', 'hsl(120,100%,25%)', @@ -120,6 +242,26 @@ public static function provideValidColorAndExpectedRendering(): array 'hsl(120, 100%, 25%, 0.5)', 'hsla(120,100%,25%,.5)', ], + 'modern hsl' => [ + 'hsl(120 100% 25%)', + 'hsl(120,100%,25%)', + ], + /* + 'modern hsl with none' => [ + 'hsl(none 100% 25%)', + 'hsl(none 100% 25%)', + ], + //*/ + 'modern hsla' => [ + 'hsl(120 100% 25% / 0.5)', + 'hsla(120,100%,25%,.5)', + ], + /* + 'modern hsla with none as alpha' => [ + 'hsl(120 100% 25% none)', + 'hsla(120 100% 25% none)', + ], + //*/ ]; } @@ -180,6 +322,15 @@ public static function provideInvalidColor(): array 'rgb(255, 0px, 0)', ], //*/ + 'modern rgb color without slash separator for alpha' => [ + 'rgb(255 0 0 0.5)', + ], + 'rgb color with mixed separators, comma first' => [ + 'rgb(255, 0 0)', + ], + 'rgb color with mixed separators, space first' => [ + 'rgb(255 0, 0)', + ], 'hsl color with 0 arguments' => [ 'hsl()', ], @@ -200,6 +351,15 @@ public static function provideInvalidColor(): array 'hsl(0px, 100%, 50%)' ], //*/ + 'modern hsl color without slash separator for alpha' => [ + 'rgb(0 100% 50% 0.5)', + ], + 'hsl color with mixed separators, comma first' => [ + 'hsl(0, 100% 50%)', + ], + 'hsl color with mixed separators, space first' => [ + 'hsl(0 100%, 50%)', + ], ]; } From 98c8bb896fce0342c4d3391bfab3e2d1e459d576 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Fri, 24 Jan 2025 09:50:33 +0000 Subject: [PATCH 100/555] [CLEANUP] Avoid Hungarian notation in `Color` class (#802) Also rename some variables to be more descriptive and/or avoid abbreviations. Co-authored-by: Jake Hotson --- src/Value/Color.php | 166 ++++++++++++++++++++++---------------------- 1 file changed, 83 insertions(+), 83 deletions(-) diff --git a/src/Value/Color.php b/src/Value/Color.php index 5c716678..85890393 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -16,86 +16,86 @@ class Color extends CSSFunction { /** - * @param array $aColor - * @param int $iLineNo + * @param array $colorValues + * @param int $lineNumber */ - public function __construct(array $aColor, $iLineNo = 0) + public function __construct(array $colorValues, $lineNumber = 0) { - parent::__construct(\implode('', \array_keys($aColor)), $aColor, ',', $iLineNo); + parent::__construct(\implode('', \array_keys($colorValues)), $colorValues, ',', $lineNumber); } /** * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ - public static function parse(ParserState $oParserState, bool $bIgnoreCase = false): CSSFunction + public static function parse(ParserState $parserState, bool $ignoreCase = false): CSSFunction { return - $oParserState->comes('#') - ? self::parseHexColor($oParserState) - : self::parseColorFunction($oParserState); + $parserState->comes('#') + ? self::parseHexColor($parserState) + : self::parseColorFunction($parserState); } /** * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ - private static function parseHexColor(ParserState $oParserState): CSSFunction + private static function parseHexColor(ParserState $parserState): CSSFunction { - $oParserState->consume('#'); - $sValue = $oParserState->parseIdentifier(false); - if ($oParserState->strlen($sValue) === 3) { - $sValue = $sValue[0] . $sValue[0] . $sValue[1] . $sValue[1] . $sValue[2] . $sValue[2]; - } elseif ($oParserState->strlen($sValue) === 4) { - $sValue = $sValue[0] . $sValue[0] . $sValue[1] . $sValue[1] . $sValue[2] . $sValue[2] . $sValue[3] - . $sValue[3]; + $parserState->consume('#'); + $hexValue = $parserState->parseIdentifier(false); + if ($parserState->strlen($hexValue) === 3) { + $hexValue = $hexValue[0] . $hexValue[0] . $hexValue[1] . $hexValue[1] . $hexValue[2] . $hexValue[2]; + } elseif ($parserState->strlen($hexValue) === 4) { + $hexValue = $hexValue[0] . $hexValue[0] . $hexValue[1] . $hexValue[1] . $hexValue[2] . $hexValue[2] + . $hexValue[3] . $hexValue[3]; } - if ($oParserState->strlen($sValue) === 8) { - $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()), + if ($parserState->strlen($hexValue) === 8) { + $colorValues = [ + 'r' => new Size(\intval($hexValue[0] . $hexValue[1], 16), null, true, $parserState->currentLine()), + 'g' => new Size(\intval($hexValue[2] . $hexValue[3], 16), null, true, $parserState->currentLine()), + 'b' => new Size(\intval($hexValue[4] . $hexValue[5], 16), null, true, $parserState->currentLine()), 'a' => new Size( - \round(self::mapRange(\intval($sValue[6] . $sValue[7], 16), 0, 255, 0, 1), 2), + \round(self::mapRange(\intval($hexValue[6] . $hexValue[7], 16), 0, 255, 0, 1), 2), null, true, - $oParserState->currentLine() + $parserState->currentLine() ), ]; - } 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()), + } elseif ($parserState->strlen($hexValue) === 6) { + $colorValues = [ + 'r' => new Size(\intval($hexValue[0] . $hexValue[1], 16), null, true, $parserState->currentLine()), + 'g' => new Size(\intval($hexValue[2] . $hexValue[3], 16), null, true, $parserState->currentLine()), + 'b' => new Size(\intval($hexValue[4] . $hexValue[5], 16), null, true, $parserState->currentLine()), ]; } else { throw new UnexpectedTokenException( 'Invalid hex color value', - $sValue, + $hexValue, 'custom', - $oParserState->currentLine() + $parserState->currentLine() ); } - return new Color($aColor, $oParserState->currentLine()); + return new Color($colorValues, $parserState->currentLine()); } /** * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ - private static function parseColorFunction(ParserState $oParserState): CSSFunction + private static function parseColorFunction(ParserState $parserState): CSSFunction { - $aColor = []; + $colorValues = []; - $sColorMode = $oParserState->parseIdentifier(true); - $oParserState->consumeWhiteSpace(); - $oParserState->consume('('); + $colorMode = $parserState->parseIdentifier(true); + $parserState->consumeWhiteSpace(); + $parserState->consume('('); // CSS Color Module Level 4 says that `rgb` and `rgba` are now aliases; likewise `hsl` and `hsla`. // So, attempt to parse with the `a`, and allow for it not being there. - switch ($sColorMode) { + switch ($colorMode) { case 'rgb': $colorModeForParsing = 'rgba'; $mayHaveOptionalAlpha = true; @@ -107,89 +107,89 @@ private static function parseColorFunction(ParserState $oParserState): CSSFuncti case 'rgba': // This is handled identically to the following case. case 'hsla': - $colorModeForParsing = $sColorMode; + $colorModeForParsing = $colorMode; $mayHaveOptionalAlpha = true; break; default: - $colorModeForParsing = $sColorMode; + $colorModeForParsing = $colorMode; $mayHaveOptionalAlpha = false; } - $bContainsVar = false; + $containsVar = false; $isLegacySyntax = false; - $iLength = $oParserState->strlen($colorModeForParsing); - for ($i = 0; $i < $iLength; ++$i) { - $oParserState->consumeWhiteSpace(); - if ($oParserState->comes('var')) { - $aColor[$colorModeForParsing[$i]] = CSSFunction::parseIdentifierOrFunction($oParserState); - $bContainsVar = true; + $expectedArgumentCount = $parserState->strlen($colorModeForParsing); + for ($argumentIndex = 0; $argumentIndex < $expectedArgumentCount; ++$argumentIndex) { + $parserState->consumeWhiteSpace(); + if ($parserState->comes('var')) { + $colorValues[$colorModeForParsing[$argumentIndex]] = CSSFunction::parseIdentifierOrFunction($parserState); + $containsVar = true; } else { - $aColor[$colorModeForParsing[$i]] = Size::parse($oParserState, true); + $colorValues[$colorModeForParsing[$argumentIndex]] = Size::parse($parserState, true); } // This must be done first, to consume comments as well, so that the `comes` test will work. - $oParserState->consumeWhiteSpace(); + $parserState->consumeWhiteSpace(); // With a `var` argument, the function can have fewer arguments. // And as of CSS Color Module Level 4, the alpha argument is optional. $canCloseNow = - $bContainsVar || - ($mayHaveOptionalAlpha && $i >= $iLength - 2); - if ($canCloseNow && $oParserState->comes(')')) { + $containsVar || + ($mayHaveOptionalAlpha && $argumentIndex >= $expectedArgumentCount - 2); + if ($canCloseNow && $parserState->comes(')')) { break; } // "Legacy" syntax is comma-delimited. // "Modern" syntax is space-delimited, with `/` as alpha delimiter. // They cannot be mixed. - if ($i === 0) { + if ($argumentIndex === 0) { // An immediate closing parenthesis is not valid. - if ($oParserState->comes(')')) { + if ($parserState->comes(')')) { throw new UnexpectedTokenException( 'Color function with no arguments', '', 'custom', - $oParserState->currentLine() + $parserState->currentLine() ); } - $isLegacySyntax = $oParserState->comes(','); + $isLegacySyntax = $parserState->comes(','); } - if ($isLegacySyntax && $i < ($iLength - 1)) { - $oParserState->consume(','); + if ($isLegacySyntax && $argumentIndex < ($expectedArgumentCount - 1)) { + $parserState->consume(','); } // In the "modern" syntax, the alpha value must be delimited with `/`. if (!$isLegacySyntax) { - if ($bContainsVar) { + if ($containsVar) { // If the `var` substitution encompasses more than one argument, // the alpha deliminator may come at any time. - if ($oParserState->comes('/')) { - $oParserState->consume('/'); + if ($parserState->comes('/')) { + $parserState->consume('/'); } - } elseif (($colorModeForParsing[$i + 1] ?? '') === 'a') { + } elseif (($colorModeForParsing[$argumentIndex + 1] ?? '') === 'a') { // Alpha value is the next expected argument. // Since a closing parenthesis was not found, a `/` separator is now required. - $oParserState->consume('/'); + $parserState->consume('/'); } } } - $oParserState->consume(')'); + $parserState->consume(')'); return - $bContainsVar - ? new CSSFunction($sColorMode, \array_values($aColor), ',', $oParserState->currentLine()) - : new Color($aColor, $oParserState->currentLine()); + $containsVar + ? new CSSFunction($colorMode, \array_values($colorValues), ',', $parserState->currentLine()) + : new Color($colorValues, $parserState->currentLine()); } - private static function mapRange(float $fVal, float $fFromMin, float $fFromMax, float $fToMin, float $fToMax): float + private static function mapRange(float $value, float $fromMin, float $fromMax, float $toMin, float $toMax): float { - $fFromRange = $fFromMax - $fFromMin; - $fToRange = $fToMax - $fToMin; - $fMultiplier = $fToRange / $fFromRange; - $fNewVal = $fVal - $fFromMin; - $fNewVal *= $fMultiplier; - return $fNewVal + $fToMin; + $fromRange = $fromMax - $fromMin; + $toRange = $toMax - $toMin; + $multiplier = $toRange / $fromRange; + $newValue = $value - $fromMin; + $newValue *= $multiplier; + return $newValue + $toMin; } /** @@ -201,12 +201,12 @@ public function getColor() } /** - * @param array $aColor + * @param array $colorValues */ - public function setColor(array $aColor): void + public function setColor(array $colorValues): void { - $this->setName(\implode('', \array_keys($aColor))); - $this->aComponents = $aColor; + $this->setName(\implode('', \array_keys($colorValues))); + $this->aComponents = $colorValues; } /** @@ -222,19 +222,19 @@ public function __toString(): string return $this->render(new OutputFormat()); } - public function render(OutputFormat $oOutputFormat): string + public function render(OutputFormat $outputFormat): string { // Shorthand RGB color values - if ($oOutputFormat->getRGBHashNotation() && \implode('', \array_keys($this->aComponents)) === 'rgb') { - $sResult = \sprintf( + if ($outputFormat->getRGBHashNotation() && \implode('', \array_keys($this->aComponents)) === 'rgb') { + $result = \sprintf( '%02x%02x%02x', $this->aComponents['r']->getSize(), $this->aComponents['g']->getSize(), $this->aComponents['b']->getSize() ); - return '#' . (($sResult[0] == $sResult[1]) && ($sResult[2] == $sResult[3]) && ($sResult[4] == $sResult[5]) - ? "$sResult[0]$sResult[2]$sResult[4]" : $sResult); + return '#' . (($result[0] == $result[1]) && ($result[2] == $result[3]) && ($result[4] == $result[5]) + ? "$result[0]$result[2]$result[4]" : $result); } - return parent::render($oOutputFormat); + return parent::render($outputFormat); } } From 540b315935c334f0f4d1e6698fcec2173d4073ba Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sat, 25 Jan 2025 08:50:44 +0000 Subject: [PATCH 101/555] [BUGFIX] Don't render `rgb` colors with `%` values as hex (#803) The only percentage values that could be reliably converted to hex notation are 0%, 20%, 40%, etc. It's beyond the scope of this library to do that. Also add additional tests to confirm parsing of percentage values. Some of these are commented out, because the input data would result in rendering in an invalid format. (The "legacy" syntax does not allow a mixture of `percentage`s and `number`s, so it would be necessary to implement rendering in the "modern" syntax to resolve those cases, which is beyond the scope of this PR.) Co-authored-by: Jake Hotson --- CHANGELOG.md | 1 + src/Value/Color.php | 21 +++++++++- tests/Unit/Value/ColorTest.php | 71 +++++++++++++++++++++++++++++++++- 3 files changed, 91 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a92ad218..3cae9f51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ Please also have a look at our ### Fixed +- Don't render `rgb` colors with percentage values using hex notation (#803) - Parse `@font-face` `src` property as comma-delimited list (#790) - Fix type errors in PHP strict mode (#664) - Fix undefined local variable in `CalcFunction::parse()` (#593) diff --git a/src/Value/Color.php b/src/Value/Color.php index 85890393..07f11c34 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -225,7 +225,11 @@ public function __toString(): string public function render(OutputFormat $outputFormat): string { // Shorthand RGB color values - if ($outputFormat->getRGBHashNotation() && \implode('', \array_keys($this->aComponents)) === 'rgb') { + if ( + $outputFormat->getRGBHashNotation() + && \implode('', \array_keys($this->aComponents)) === 'rgb' + && $this->allComponentsAreNumbers() + ) { $result = \sprintf( '%02x%02x%02x', $this->aComponents['r']->getSize(), @@ -237,4 +241,19 @@ public function render(OutputFormat $outputFormat): string } return parent::render($outputFormat); } + + /** + * Test whether all color components are absolute numbers (CSS type `number`), not percentages or anything else. + * If any component is not an instance of `Size`, the method will also return `false`. + */ + private function allComponentsAreNumbers(): bool + { + foreach ($this->aComponents as $component) { + if (!$component instanceof Size || $component->getUnit() !== null) { + return false; + } + } + + return true; + } } diff --git a/tests/Unit/Value/ColorTest.php b/tests/Unit/Value/ColorTest.php index 822ba5be..2b533a7f 100644 --- a/tests/Unit/Value/ColorTest.php +++ b/tests/Unit/Value/ColorTest.php @@ -54,6 +54,10 @@ public static function provideValidColorAndExpectedRendering(): array 'rgb(0, 118, 0)', '#007600', ], + 'legacy rgb with percentage components' => [ + 'rgb(0%, 60%, 0%)', + 'rgb(0%,60%,0%)', + ], 'legacy rgba with fractional alpha' => [ 'rgba(0, 119, 0, 0.5)', 'rgba(0,119,0,.5)', @@ -62,6 +66,14 @@ public static function provideValidColorAndExpectedRendering(): array 'rgba(0, 119, 0, 50%)', 'rgba(0,119,0,50%)', ], + 'legacy rgba with percentage components and fractional alpha' => [ + 'rgba(0%, 60%, 0%, 0.5)', + 'rgba(0%,60%,0%,.5)', + ], + 'legacy rgba with percentage components and percentage alpha' => [ + 'rgba(0%, 60%, 0%, 50%)', + 'rgba(0%,60%,0%,50%)', + ], 'legacy rgb as rgba' => [ 'rgba(0, 119, 0)', '#070', @@ -74,16 +86,73 @@ public static function provideValidColorAndExpectedRendering(): array 'rgb(0 119 0)', '#070', ], + // The "legacy" syntax currently used for rendering does not allow a mixture of percentages and numbers. + /* + 'modern rgb with percentage R' => [ + 'rgb(0% 119 0)', + 'rgb(0% 119 0)', + ], + 'modern rgb with percentage G' => [ + 'rgb(0 60% 0)', + 'rgb(0 60% 0)', + ], + 'modern rgb with percentage B' => [ + 'rgb(0 119 0%)', + 'rgb(0 119 0%)', + ], + 'modern rgb with percentage R&G' => [ + 'rgb(0% 60% 0)', + 'rgb(0% 60% 0)', + ], + 'modern rgb with percentage R&B' => [ + 'rgb(0% 119 0%)', + 'rgb(0% 119 0%)', + ], + 'modern rgb with percentage G&B' => [ + 'rgb(0 60% 0%)', + 'rgb(0 60% 0%)', + ], + //*/ + 'modern rgb with percentage components' => [ + 'rgb(0% 60% 0%)', + 'rgb(0%,60%,0%)', + ], /* 'modern rgb with none' => [ 'rgb(none 119 0)', 'rgb(none 119 0)', ], //*/ - 'modern rgba' => [ + 'modern rgba with fractional alpha' => [ 'rgb(0 119 0 / 0.5)', 'rgba(0,119,0,.5)', ], + 'modern rgba with percentage alpha' => [ + 'rgb(0 119 0 / 50%)', + 'rgba(0,119,0,50%)', + ], + /* + 'modern rgba with percentage R' => [ + 'rgb(0% 119 0 / 0.5)', + 'rgba(0% 119 0/.5)', + ], + 'modern rgba with percentage G' => [ + 'rgb(0 60% 0 / 0.5)', + 'rgba(0 60% 0/.5)', + ], + 'modern rgba with percentage B' => [ + 'rgb(0 119 0% / 0.5)', + 'rgba(0 119 0%/.5)', + ], + //*/ + 'modern rgba with percentage RGB' => [ + 'rgb(0% 60% 0% / 0.5)', + 'rgba(0%,60%,0%,.5)', + ], + 'modern rgba with percentage components' => [ + 'rgb(0% 60% 0% / 50%)', + 'rgba(0%,60%,0%,50%)', + ], /* 'modern rgba with none as alpha' => [ 'rgb(0 119 0 / none)', From 65cacc1890e8932f64e53e5e2dd7e8e8ba9bba7c Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 25 Jan 2025 20:13:54 +0100 Subject: [PATCH 102/555] [TASK] Update PHPStan and Rector (#787) --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 20159e60..b147d6a7 100644 --- a/composer.json +++ b/composer.json @@ -29,10 +29,10 @@ "require-dev": { "php-parallel-lint/php-parallel-lint": "1.4.0", "phpstan/extension-installer": "1.4.3", - "phpstan/phpstan": "1.12.13 || 2.0.4", - "phpstan/phpstan-phpunit": "1.4.2 || 2.0.2", + "phpstan/phpstan": "1.12.16 || 2.1.2", + "phpstan/phpstan-phpunit": "1.4.2 || 2.0.4", "phpunit/phpunit": "8.5.41", - "rector/rector": "1.2.10 || 2.0.3" + "rector/rector": "1.2.10 || 2.0.7" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" From 24f44c2ad2aa6731fc6b424711c211726383f292 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 25 Jan 2025 20:25:21 +0100 Subject: [PATCH 103/555] [CLEANUP] Avoid Hungarian notion for the `Commentable` interface (#804) Also adapt the implementing classes accordingly. Part of #756. --- src/CSSList/CSSList.php | 18 +++++++++--------- src/Comment/Commentable.php | 8 ++++---- src/Property/CSSNamespace.php | 18 +++++++++--------- src/Property/Charset.php | 18 +++++++++--------- src/Property/Import.php | 18 +++++++++--------- src/Rule/Rule.php | 18 +++++++++--------- src/RuleSet/RuleSet.php | 18 +++++++++--------- 7 files changed, 58 insertions(+), 58 deletions(-) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 6f31def8..282c7bd6 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -36,7 +36,7 @@ abstract class CSSList implements Renderable, Commentable /** * @var array */ - protected $aComments; + protected $comments; /** * @var array @@ -53,7 +53,7 @@ abstract class CSSList implements Renderable, Commentable */ public function __construct($iLineNo = 0) { - $this->aComments = []; + $this->comments = []; $this->aContents = []; $this->iLineNo = $iLineNo; } @@ -456,11 +456,11 @@ public function getContents() } /** - * @param array $aComments + * @param array $comments */ - public function addComments(array $aComments): void + public function addComments(array $comments): void { - $this->aComments = \array_merge($this->aComments, $aComments); + $this->comments = \array_merge($this->comments, $comments); } /** @@ -468,14 +468,14 @@ public function addComments(array $aComments): void */ public function getComments() { - return $this->aComments; + return $this->comments; } /** - * @param array $aComments + * @param array $comments */ - public function setComments(array $aComments): void + public function setComments(array $comments): void { - $this->aComments = $aComments; + $this->comments = $comments; } } diff --git a/src/Comment/Commentable.php b/src/Comment/Commentable.php index 7639b5a5..e07d9774 100644 --- a/src/Comment/Commentable.php +++ b/src/Comment/Commentable.php @@ -7,11 +7,11 @@ interface Commentable { /** - * @param array $aComments + * @param array $comments * * @return void */ - public function addComments(array $aComments); + public function addComments(array $comments); /** * @return array @@ -19,9 +19,9 @@ public function addComments(array $aComments); public function getComments(); /** - * @param array $aComments + * @param array $comments * * @return void */ - public function setComments(array $aComments); + public function setComments(array $comments); } diff --git a/src/Property/CSSNamespace.php b/src/Property/CSSNamespace.php index 1ccf36cf..1a827434 100644 --- a/src/Property/CSSNamespace.php +++ b/src/Property/CSSNamespace.php @@ -30,7 +30,7 @@ class CSSNamespace implements AtRule /** * @var array */ - protected $aComments; + protected $comments; /** * @param string $mUrl @@ -42,7 +42,7 @@ public function __construct($mUrl, $sPrefix = null, $iLineNo = 0) $this->mUrl = $mUrl; $this->sPrefix = $sPrefix; $this->iLineNo = $iLineNo; - $this->aComments = []; + $this->comments = []; } /** @@ -117,11 +117,11 @@ public function atRuleArgs(): array } /** - * @param array $aComments + * @param array $comments */ - public function addComments(array $aComments): void + public function addComments(array $comments): void { - $this->aComments = \array_merge($this->aComments, $aComments); + $this->comments = \array_merge($this->comments, $comments); } /** @@ -129,14 +129,14 @@ public function addComments(array $aComments): void */ public function getComments() { - return $this->aComments; + return $this->comments; } /** - * @param array $aComments + * @param array $comments */ - public function setComments(array $aComments): void + public function setComments(array $comments): void { - $this->aComments = $aComments; + $this->comments = $comments; } } diff --git a/src/Property/Charset.php b/src/Property/Charset.php index ff34487f..4352850f 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -31,7 +31,7 @@ class Charset implements AtRule /** * @var array */ - protected $aComments; + protected $comments; /** * @param CSSString $oCharset @@ -41,7 +41,7 @@ public function __construct(CSSString $oCharset, $iLineNo = 0) { $this->oCharset = $oCharset; $this->iLineNo = $iLineNo; - $this->aComments = []; + $this->comments = []; } /** @@ -95,13 +95,13 @@ public function atRuleArgs() } /** - * @param array $aComments + * @param array $comments * * @return void */ - public function addComments(array $aComments): void + public function addComments(array $comments): void { - $this->aComments = \array_merge($this->aComments, $aComments); + $this->comments = \array_merge($this->comments, $comments); } /** @@ -109,16 +109,16 @@ public function addComments(array $aComments): void */ public function getComments() { - return $this->aComments; + return $this->comments; } /** - * @param array $aComments + * @param array $comments * * @return void */ - public function setComments(array $aComments): void + public function setComments(array $comments): void { - $this->aComments = $aComments; + $this->comments = $comments; } } diff --git a/src/Property/Import.php b/src/Property/Import.php index ab883bde..ec8c97df 100644 --- a/src/Property/Import.php +++ b/src/Property/Import.php @@ -31,7 +31,7 @@ class Import implements AtRule /** * @var array */ - protected $aComments; + protected $comments; /** * @param URL $oLocation @@ -43,7 +43,7 @@ public function __construct(URL $oLocation, $sMediaQuery, $iLineNo = 0) $this->oLocation = $oLocation; $this->sMediaQuery = $sMediaQuery; $this->iLineNo = $iLineNo; - $this->aComments = []; + $this->comments = []; } /** @@ -99,11 +99,11 @@ public function atRuleArgs(): array } /** - * @param array $aComments + * @param array $comments */ - public function addComments(array $aComments): void + public function addComments(array $comments): void { - $this->aComments = \array_merge($this->aComments, $aComments); + $this->comments = \array_merge($this->comments, $comments); } /** @@ -111,15 +111,15 @@ public function addComments(array $aComments): void */ public function getComments() { - return $this->aComments; + return $this->comments; } /** - * @param array $aComments + * @param array $comments */ - public function setComments(array $aComments): void + public function setComments(array $comments): void { - $this->aComments = $aComments; + $this->comments = $comments; } /** diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index c8f39b1c..137a64e7 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -54,7 +54,7 @@ class Rule implements Renderable, Commentable /** * @var array */ - protected $aComments; + protected $comments; /** * @param string $sRule @@ -69,7 +69,7 @@ public function __construct($sRule, $iLineNo = 0, $iColNo = 0) $this->aIeHack = []; $this->iLineNo = $iLineNo; $this->iColNo = $iColNo; - $this->aComments = []; + $this->comments = []; } /** @@ -284,11 +284,11 @@ public function render(OutputFormat $oOutputFormat): string } /** - * @param array $aComments + * @param array $comments */ - public function addComments(array $aComments): void + public function addComments(array $comments): void { - $this->aComments = \array_merge($this->aComments, $aComments); + $this->comments = \array_merge($this->comments, $comments); } /** @@ -296,14 +296,14 @@ public function addComments(array $aComments): void */ public function getComments() { - return $this->aComments; + return $this->comments; } /** - * @param array $aComments + * @param array $comments */ - public function setComments(array $aComments): void + public function setComments(array $comments): void { - $this->aComments = $aComments; + $this->comments = $comments; } } diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index e53abd52..2920830c 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -37,7 +37,7 @@ abstract class RuleSet implements Renderable, Commentable /** * @var array */ - protected $aComments; + protected $comments; /** * @param int $iLineNo @@ -46,7 +46,7 @@ public function __construct($iLineNo = 0) { $this->aRules = []; $this->iLineNo = $iLineNo; - $this->aComments = []; + $this->comments = []; } /** @@ -294,11 +294,11 @@ protected function renderRules(OutputFormat $oOutputFormat) } /** - * @param array $aComments + * @param array $comments */ - public function addComments(array $aComments): void + public function addComments(array $comments): void { - $this->aComments = \array_merge($this->aComments, $aComments); + $this->comments = \array_merge($this->comments, $comments); } /** @@ -306,14 +306,14 @@ public function addComments(array $aComments): void */ public function getComments() { - return $this->aComments; + return $this->comments; } /** - * @param array $aComments + * @param array $comments */ - public function setComments(array $aComments): void + public function setComments(array $comments): void { - $this->aComments = $aComments; + $this->comments = $comments; } } From 129f7cec64cef87e24d6dd82e4937c2a073c0deb Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 25 Jan 2025 20:46:11 +0100 Subject: [PATCH 104/555] [TASK] Use native `void` return type declarations (#805) Also include previous changes for adding native type declarations or strict mode into the changelog. Co-authored-by: JakeQZ --- CHANGELOG.md | 2 ++ src/CSSList/CSSBlockList.php | 22 +++++++++------------- src/Comment/Commentable.php | 8 ++------ src/Property/Charset.php | 6 ------ src/Property/Selector.php | 2 -- src/Rule/Rule.php | 2 -- 6 files changed, 13 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cae9f51..8a67f6e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ Please also have a look at our ### Changed +- Use more native type declarations and strict mode + (#641, #772, #774, #778, #804) - Mark parsing-internal classes and methods as `@internal` (#674) - Block installations on unsupported higher PHP versions (#691) - Improve performance of `Value::parseValue` with many delimiters by refactoring diff --git a/src/CSSList/CSSBlockList.php b/src/CSSList/CSSBlockList.php index c1cc05c2..1be8fe3a 100644 --- a/src/CSSList/CSSBlockList.php +++ b/src/CSSList/CSSBlockList.php @@ -30,10 +30,8 @@ public function __construct($iLineNo = 0) /** * @param array $aResult - * - * @return void */ - protected function allDeclarationBlocks(array &$aResult) + protected function allDeclarationBlocks(array &$aResult): void { foreach ($this->aContents as $mContent) { if ($mContent instanceof DeclarationBlock) { @@ -46,10 +44,8 @@ protected function allDeclarationBlocks(array &$aResult) /** * @param array $aResult - * - * @return void */ - protected function allRuleSets(array &$aResult) + protected function allRuleSets(array &$aResult): void { foreach ($this->aContents as $mContent) { if ($mContent instanceof RuleSet) { @@ -65,11 +61,13 @@ protected function allRuleSets(array &$aResult) * @param array $aResult * @param string|null $sSearchString * @param bool $bSearchInFunctionArguments - * - * @return void */ - protected function allValues($oElement, array &$aResult, $sSearchString = null, $bSearchInFunctionArguments = false) - { + protected function allValues( + $oElement, + array &$aResult, + $sSearchString = null, + $bSearchInFunctionArguments = false + ): void { if ($oElement instanceof CSSBlockList) { foreach ($oElement->getContents() as $oContent) { $this->allValues($oContent, $aResult, $sSearchString, $bSearchInFunctionArguments); @@ -95,10 +93,8 @@ protected function allValues($oElement, array &$aResult, $sSearchString = null, /** * @param array $aResult * @param string|null $sSpecificitySearch - * - * @return void */ - protected function allSelectors(array &$aResult, $sSpecificitySearch = null) + protected function allSelectors(array &$aResult, $sSpecificitySearch = null): void { /** @var array $aDeclarationBlocks */ $aDeclarationBlocks = []; diff --git a/src/Comment/Commentable.php b/src/Comment/Commentable.php index e07d9774..0cb3df24 100644 --- a/src/Comment/Commentable.php +++ b/src/Comment/Commentable.php @@ -8,10 +8,8 @@ interface Commentable { /** * @param array $comments - * - * @return void */ - public function addComments(array $comments); + public function addComments(array $comments): void; /** * @return array @@ -20,8 +18,6 @@ public function getComments(); /** * @param array $comments - * - * @return void */ - public function setComments(array $comments); + public function setComments(array $comments): void; } diff --git a/src/Property/Charset.php b/src/Property/Charset.php index 4352850f..a36e55a2 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -54,8 +54,6 @@ public function getLineNo() /** * @param string|CSSString $oCharset - * - * @return void */ public function setCharset($sCharset): void { @@ -96,8 +94,6 @@ public function atRuleArgs() /** * @param array $comments - * - * @return void */ public function addComments(array $comments): void { @@ -114,8 +110,6 @@ public function getComments() /** * @param array $comments - * - * @return void */ public function setComments(array $comments): void { diff --git a/src/Property/Selector.php b/src/Property/Selector.php index 9a70d6c3..28e63ec2 100644 --- a/src/Property/Selector.php +++ b/src/Property/Selector.php @@ -106,8 +106,6 @@ public function getSelector() /** * @param string $sSelector - * - * @return void */ public function setSelector($sSelector): void { diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index 137a64e7..d598a366 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -228,8 +228,6 @@ public function addIeHack($iModifier): void /** * @param array $aModifiers - * - * @return void */ public function setIeHack(array $aModifiers): void { From c7c610c05e2918dc6df8b54f7232a7fe5547437f Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 26 Jan 2025 01:39:33 +0100 Subject: [PATCH 105/555] [DOCS] Remove section on testing from `README.md` (#807) We now have instructions on testing in `CONTRIBUTING.md`. --- README.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/README.md b/README.md index ed15010e..7d18303b 100644 --- a/README.md +++ b/README.md @@ -814,14 +814,3 @@ contribute to PHP-CSS-Parser. ### Legacy Support The latest pre-PSR-0 version of this project can be checked with the `0.9.0` tag. - -### Running Tests - -To run all continuous integration (CI) checks for this project (including unit tests), -* run `composer install` to install the development dependencies managed with Composer; -* run `phive install` to install the development dependencies managed with PHIVE; - * [Installation of PHIVE](https://github.com/phar-io/phive?tab=readme-ov-file#getting-phive) -* run `composer ci` to run all static and dynamic CI checks. - -Details of other Composer scripts available (e.g. to run one specific CI check) are provided with `composer list`. - From 90ca871c0b3f3bcd886aee5104b01dfbbce5bf77 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 26 Jan 2025 01:42:23 +0100 Subject: [PATCH 106/555] [TASK] Remove `Parser::setCharset/getCharset` (#808) These methods were deprecated in version 8.7.0. Fixes #687 --- CHANGELOG.md | 1 + src/Parser.php | 23 ----------------------- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a67f6e1..92d888c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ Please also have a look at our ### Removed +- Remove `Parser::setCharset/getCharset` (#808) - Remove `Rule::getValues()` (#582) - Remove `Rule::setValues()` (#562) - Remove `Document::getAllSelectors()` (#561) diff --git a/src/Parser.php b/src/Parser.php index 53126a2a..0bb836ac 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -31,29 +31,6 @@ public function __construct($sText, ?Settings $oParserSettings = null, $iLineNo $this->oParserState = new ParserState($sText, $oParserSettings, $iLineNo); } - /** - * Sets the charset to be used if the CSS does not contain an `@charset` declaration. - * - * @param string $sCharset - * - * @deprecated since 8.7.0, will be removed in version 9.0.0 with #687 - */ - public function setCharset($sCharset): void - { - $this->oParserState->setCharset($sCharset); - } - - /** - * Returns the charset that is used if the CSS does not contain an `@charset` declaration. - * - * @deprecated since 8.7.0, will be removed in version 9.0.0 with #687 - */ - public function getCharset(): void - { - // Note: The `return` statement is missing here. This is a bug that needs to be fixed. - $this->oParserState->getCharset(); - } - /** * Parses the CSS provided to the constructor and creates a `Document` from it. * From 0742acc92f019e5c79607d55f24e663e7bfc832b Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 26 Jan 2025 02:22:47 +0100 Subject: [PATCH 107/555] [TASK] Move `SettingsTest` to the unit tests (#810) Part of #758 --- tests/{ => Unit}/SettingsTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tests/{ => Unit}/SettingsTest.php (98%) diff --git a/tests/SettingsTest.php b/tests/Unit/SettingsTest.php similarity index 98% rename from tests/SettingsTest.php rename to tests/Unit/SettingsTest.php index 642dc8ba..3fcbcbb4 100644 --- a/tests/SettingsTest.php +++ b/tests/Unit/SettingsTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Sabberworm\CSS\Tests; +namespace Sabberworm\CSS\Tests\Unit; use PHPUnit\Framework\TestCase; use Sabberworm\CSS\Settings; From 7c6845f832980396610c59b79d835af2222f5826 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 26 Jan 2025 02:26:00 +0100 Subject: [PATCH 108/555] [CLEANUP] Avoid Hungarian notation in `AtRuleBlockList` (#812) Part of #756. --- src/CSSList/AtRuleBlockList.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/CSSList/AtRuleBlockList.php b/src/CSSList/AtRuleBlockList.php index 2ac5776b..af0adf31 100644 --- a/src/CSSList/AtRuleBlockList.php +++ b/src/CSSList/AtRuleBlockList.php @@ -15,7 +15,7 @@ class AtRuleBlockList extends CSSBlockList implements AtRule /** * @var string */ - private $sType; + private $type; /** * @var string @@ -23,15 +23,15 @@ class AtRuleBlockList extends CSSBlockList implements AtRule private $sArgs; /** - * @param string $sType - * @param string $sArgs - * @param int $iLineNo + * @param string $type + * @param string $arguments + * @param int $lineNumber */ - public function __construct($sType, $sArgs = '', $iLineNo = 0) + public function __construct($type, $arguments = '', $lineNumber = 0) { - parent::__construct($iLineNo); - $this->sType = $sType; - $this->sArgs = $sArgs; + parent::__construct($lineNumber); + $this->type = $type; + $this->sArgs = $arguments; } /** @@ -39,7 +39,7 @@ public function __construct($sType, $sArgs = '', $iLineNo = 0) */ public function atRuleName() { - return $this->sType; + return $this->type; } /** @@ -63,7 +63,7 @@ public function render(OutputFormat $oOutputFormat): string if ($sArgs) { $sArgs = ' ' . $sArgs; } - $sResult .= "@{$this->sType}$sArgs{$oOutputFormat->spaceBeforeOpeningBrace()}{"; + $sResult .= "@{$this->type}$sArgs{$oOutputFormat->spaceBeforeOpeningBrace()}{"; $sResult .= $this->renderListContents($oOutputFormat); $sResult .= '}'; $sResult .= $oOutputFormat->sAfterAtRuleBlock; From fd1373e9bde3cf5c13596f9fe4cb57f4f6020d7b Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 26 Jan 2025 18:00:46 +0100 Subject: [PATCH 109/555] [TASK] Add a release checklist (#816) Copied without changes (except for the link to the releases list) from our sister project. Part of #489. --- docs/release-checklist.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 docs/release-checklist.md diff --git a/docs/release-checklist.md b/docs/release-checklist.md new file mode 100644 index 00000000..48d50b7e --- /dev/null +++ b/docs/release-checklist.md @@ -0,0 +1,15 @@ +# Steps to release a new version + +1. In the [composer.json](../composer.json), update the `branch-alias` entry to + point to the release _after_ the upcoming release. +1. In the [CHANGELOG.md](../CHANGELOG.md), create a new section with subheadings + for changes _after_ the upcoming release, set the version number for the + upcoming release, and remove any empty sections. +1. Update the target milestone in the Dependabot configuration. +1. Create a pull request "Prepare release of version x.y.z" with those changes. +1. Have the pull request reviewed and merged. +1. Tag the new release. +1. In the + [Releases tab](https://github.com/MyIntervals/PHP-CSS-Parser/releases), + create a new release and copy the change log entries to the new release. +1. Post about the new release on social media. From 8cd39f4a755ac34d2b0227d4910e67bc5a15eae8 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 26 Jan 2025 18:06:44 +0100 Subject: [PATCH 110/555] [TASK] Avoid Hungarian notation in `CSSBlockList` (#817) Part of #756. --- src/CSSList/CSSBlockList.php | 78 ++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/src/CSSList/CSSBlockList.php b/src/CSSList/CSSBlockList.php index 1be8fe3a..137faff8 100644 --- a/src/CSSList/CSSBlockList.php +++ b/src/CSSList/CSSBlockList.php @@ -21,91 +21,91 @@ abstract class CSSBlockList extends CSSList { /** - * @param int $iLineNo + * @param int $lineNumber */ - public function __construct($iLineNo = 0) + public function __construct($lineNumber = 0) { - parent::__construct($iLineNo); + parent::__construct($lineNumber); } /** - * @param array $aResult + * @param array $result */ - protected function allDeclarationBlocks(array &$aResult): void + protected function allDeclarationBlocks(array &$result): void { foreach ($this->aContents as $mContent) { if ($mContent instanceof DeclarationBlock) { - $aResult[] = $mContent; + $result[] = $mContent; } elseif ($mContent instanceof CSSBlockList) { - $mContent->allDeclarationBlocks($aResult); + $mContent->allDeclarationBlocks($result); } } } /** - * @param array $aResult + * @param array $result */ - protected function allRuleSets(array &$aResult): void + protected function allRuleSets(array &$result): void { foreach ($this->aContents as $mContent) { if ($mContent instanceof RuleSet) { - $aResult[] = $mContent; + $result[] = $mContent; } elseif ($mContent instanceof CSSBlockList) { - $mContent->allRuleSets($aResult); + $mContent->allRuleSets($result); } } } /** - * @param CSSList|Rule|RuleSet|Value $oElement - * @param array $aResult - * @param string|null $sSearchString - * @param bool $bSearchInFunctionArguments + * @param CSSList|Rule|RuleSet|Value $element + * @param array $result + * @param string|null $searchString + * @param bool $searchInFunctionArguments */ protected function allValues( - $oElement, - array &$aResult, - $sSearchString = null, - $bSearchInFunctionArguments = false + $element, + array &$result, + $searchString = null, + $searchInFunctionArguments = false ): void { - if ($oElement instanceof CSSBlockList) { - foreach ($oElement->getContents() as $oContent) { - $this->allValues($oContent, $aResult, $sSearchString, $bSearchInFunctionArguments); + if ($element instanceof CSSBlockList) { + foreach ($element->getContents() as $oContent) { + $this->allValues($oContent, $result, $searchString, $searchInFunctionArguments); } - } elseif ($oElement instanceof RuleSet) { - foreach ($oElement->getRules($sSearchString) as $oRule) { - $this->allValues($oRule, $aResult, $sSearchString, $bSearchInFunctionArguments); + } elseif ($element instanceof RuleSet) { + foreach ($element->getRules($searchString) as $oRule) { + $this->allValues($oRule, $result, $searchString, $searchInFunctionArguments); } - } elseif ($oElement instanceof Rule) { - $this->allValues($oElement->getValue(), $aResult, $sSearchString, $bSearchInFunctionArguments); - } elseif ($oElement instanceof ValueList) { - if ($bSearchInFunctionArguments || !($oElement instanceof CSSFunction)) { - foreach ($oElement->getListComponents() as $mComponent) { - $this->allValues($mComponent, $aResult, $sSearchString, $bSearchInFunctionArguments); + } elseif ($element instanceof Rule) { + $this->allValues($element->getValue(), $result, $searchString, $searchInFunctionArguments); + } elseif ($element instanceof ValueList) { + if ($searchInFunctionArguments || !($element instanceof CSSFunction)) { + foreach ($element->getListComponents() as $mComponent) { + $this->allValues($mComponent, $result, $searchString, $searchInFunctionArguments); } } } else { // Non-List `Value` or `CSSString` (CSS identifier) - $aResult[] = $oElement; + $result[] = $element; } } /** - * @param array $aResult - * @param string|null $sSpecificitySearch + * @param array $result + * @param string|null $specificitySearch */ - protected function allSelectors(array &$aResult, $sSpecificitySearch = null): void + protected function allSelectors(array &$result, $specificitySearch = null): void { /** @var array $aDeclarationBlocks */ $aDeclarationBlocks = []; $this->allDeclarationBlocks($aDeclarationBlocks); foreach ($aDeclarationBlocks as $oBlock) { foreach ($oBlock->getSelectors() as $oSelector) { - if ($sSpecificitySearch === null) { - $aResult[] = $oSelector; + if ($specificitySearch === null) { + $result[] = $oSelector; } else { $sComparator = '==='; - $aSpecificitySearch = \explode(' ', $sSpecificitySearch); + $aSpecificitySearch = \explode(' ', $specificitySearch); $iTargetSpecificity = $aSpecificitySearch[0]; if (\count($aSpecificitySearch) > 1) { $sComparator = $aSpecificitySearch[0]; @@ -132,7 +132,7 @@ protected function allSelectors(array &$aResult, $sSpecificitySearch = null): vo break; } if ($bMatches) { - $aResult[] = $oSelector; + $result[] = $oSelector; } } } From 169fad26e731d224ce3f130f9ab82718ce72aec0 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 26 Jan 2025 23:59:32 +0100 Subject: [PATCH 111/555] [TASK] Migrate part of `CommentTests` to unit tests (#821) Part of #758. --- tests/Comment/CommentTest.php | 93 ------------------------- tests/Unit/Comment/CommentTest.php | 107 +++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 93 deletions(-) create mode 100644 tests/Unit/Comment/CommentTest.php diff --git a/tests/Comment/CommentTest.php b/tests/Comment/CommentTest.php index 1c32b6ca..d80c65f9 100644 --- a/tests/Comment/CommentTest.php +++ b/tests/Comment/CommentTest.php @@ -5,9 +5,7 @@ namespace Sabberworm\CSS\Tests\Comment; use PHPUnit\Framework\TestCase; -use Sabberworm\CSS\Comment\Comment; use Sabberworm\CSS\OutputFormat; -use Sabberworm\CSS\Renderable; use Sabberworm\CSS\Tests\ParserTest as TestsParserTest; /** @@ -17,97 +15,6 @@ */ final class CommentTest extends TestCase { - /** - * @test - */ - public function implementsRenderable(): void - { - $subject = new Comment(); - - self::assertInstanceOf(Renderable::class, $subject); - } - - /** - * @test - */ - public function getCommentOnEmptyInstanceReturnsReturnsEmptyString(): void - { - $subject = new Comment(); - - self::assertSame('', $subject->getComment()); - } - - /** - * @test - */ - public function getCommentInitiallyReturnsCommentPassedToConstructor(): void - { - $comment = 'There is no spoon.'; - $subject = new Comment($comment); - - self::assertSame($comment, $subject->getComment()); - } - - /** - * @test - */ - public function setCommentSetsComments(): void - { - $comment = 'There is no spoon.'; - $subject = new Comment(); - - $subject->setComment($comment); - - self::assertSame($comment, $subject->getComment()); - } - - /** - * @test - */ - public function getLineNoOnEmptyInstanceReturnsReturnsZero(): void - { - $subject = new Comment(); - - self::assertSame(0, $subject->getLineNo()); - } - - /** - * @test - */ - public function getLineNoInitiallyReturnsLineNumberPassedToConstructor(): void - { - $lineNumber = 42; - $subject = new Comment('', $lineNumber); - - self::assertSame($lineNumber, $subject->getLineNo()); - } - - /** - * @test - */ - public function toStringRendersCommentEnclosedInCommentDelimiters(): void - { - $comment = 'There is no spoon.'; - $subject = new Comment(); - - $subject->setComment($comment); - - self::assertSame('/*' . $comment . '*/', (string) $subject); - } - - /** - * @test - */ - public function renderRendersCommentEnclosedInCommentDelimiters(): void - { - $comment = 'There is no spoon.'; - $subject = new Comment(); - - $subject->setComment($comment); - - self::assertSame('/*' . $comment . '*/', $subject->render(new OutputFormat())); - } - /** * @test */ diff --git a/tests/Unit/Comment/CommentTest.php b/tests/Unit/Comment/CommentTest.php new file mode 100644 index 00000000..c9e07102 --- /dev/null +++ b/tests/Unit/Comment/CommentTest.php @@ -0,0 +1,107 @@ +getComment()); + } + + /** + * @test + */ + public function getCommentInitiallyReturnsCommentPassedToConstructor(): void + { + $comment = 'There is no spoon.'; + $subject = new Comment($comment); + + self::assertSame($comment, $subject->getComment()); + } + + /** + * @test + */ + public function setCommentSetsComments(): void + { + $comment = 'There is no spoon.'; + $subject = new Comment(); + + $subject->setComment($comment); + + self::assertSame($comment, $subject->getComment()); + } + + /** + * @test + */ + public function getLineNoOnEmptyInstanceReturnsZero(): void + { + $subject = new Comment(); + + self::assertSame(0, $subject->getLineNo()); + } + + /** + * @test + */ + public function getLineNoInitiallyReturnsLineNumberPassedToConstructor(): void + { + $lineNumber = 42; + $subject = new Comment('', $lineNumber); + + self::assertSame($lineNumber, $subject->getLineNo()); + } + + /** + * @test + */ + public function toStringRendersCommentEnclosedInCommentDelimiters(): void + { + $comment = 'There is no spoon.'; + $subject = new Comment(); + + $subject->setComment($comment); + + self::assertSame('/*' . $comment . '*/', (string) $subject); + } + + /** + * @test + */ + public function renderRendersCommentEnclosedInCommentDelimiters(): void + { + $comment = 'There is no spoon.'; + $subject = new Comment(); + + $subject->setComment($comment); + + self::assertSame('/*' . $comment . '*/', $subject->render(new OutputFormat())); + } +} From d057852f05182922ced5f3978cd94d1955790fb6 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 27 Jan 2025 00:00:46 +0100 Subject: [PATCH 112/555] [TASK] Move `KeyFrameTest` to unit tests (#822) Part of #758. --- tests/{ => Unit}/CSSList/KeyFrameTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename tests/{ => Unit}/CSSList/KeyFrameTest.php (93%) diff --git a/tests/CSSList/KeyFrameTest.php b/tests/Unit/CSSList/KeyFrameTest.php similarity index 93% rename from tests/CSSList/KeyFrameTest.php rename to tests/Unit/CSSList/KeyFrameTest.php index 501a19a5..7e9ebd8a 100644 --- a/tests/CSSList/KeyFrameTest.php +++ b/tests/Unit/CSSList/KeyFrameTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Sabberworm\CSS\Tests\CSSList; +namespace Sabberworm\CSS\Tests\Unit\CSSList; use PHPUnit\Framework\TestCase; use Sabberworm\CSS\Comment\Commentable; @@ -18,7 +18,7 @@ final class KeyFrameTest extends TestCase /** * @var KeyFrame */ - protected $subject; + private $subject; protected function setUp(): void { From 922da38c92fff8530a2f3ee8b66e788801428223 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 27 Jan 2025 00:02:23 +0100 Subject: [PATCH 113/555] [CLEANUP] Autoformat the changelog (#823) --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92d888c3..0ca30ce7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +11,8 @@ Please also have a look at our ### Added - Partial support for CSS Color Module Level 4: - - `rgb` and `rgba`, and `hsl` and `hsla` are now aliases (#797} - - Parse color functions that use the "modern" syntax (#800) + - `rgb` and `rgba`, and `hsl` and `hsla` are now aliases (#797} + - Parse color functions that use the "modern" syntax (#800) - Add official support for PHP 8.4 (#657) - Support arithmetic operators in CSS function arguments (#607) - Add support for inserting an item in a CSS list (#545) From d28f927f72003c04402170c2c7ffb96980b93e93 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 27 Jan 2025 00:30:46 +0100 Subject: [PATCH 114/555] [CLEANUP] Use spaces for indenting the fixture CSS files (#824) This makes editing the fixture files a bit easier as all other files in this project also use spaces for indentation, not tabs. Note: This has no effect on the CSS that is rendered (which still defaults to tabs at the moment). --- tests/fixtures/-fault-tolerance.css | 12 ++++---- tests/fixtures/1readme.css | 4 +-- tests/fixtures/2readme.css | 6 ++-- tests/fixtures/atrules.css | 22 +++++++-------- tests/fixtures/calc.css | 4 +-- tests/fixtures/case-insensitivity.css | 14 +++++----- tests/fixtures/colortest.css | 34 +++++++++++------------ tests/fixtures/escaped-tokens.css | 6 ++-- tests/fixtures/ie.css | 10 +++---- tests/fixtures/inner-color.css | 4 +-- tests/fixtures/missing-property-value.css | 4 +-- tests/fixtures/namespaces.css | 6 ++-- tests/fixtures/nested.css | 12 ++++---- tests/fixtures/slashed.css | 4 +-- tests/fixtures/specificity.css | 2 +- tests/fixtures/url.css | 4 +-- tests/fixtures/values.css | 18 ++++++------ tests/fixtures/whitespace.css | 2 +- 18 files changed, 84 insertions(+), 84 deletions(-) diff --git a/tests/fixtures/-fault-tolerance.css b/tests/fixtures/-fault-tolerance.css index 7a922157..7d4e6105 100644 --- a/tests/fixtures/-fault-tolerance.css +++ b/tests/fixtures/-fault-tolerance.css @@ -1,15 +1,15 @@ .test1 { - //gaga: hello; + //gaga: hello; } .test2 { - *hello: 1; - hello: 2.2; - hello: 2000000000000.2; + *hello: 1; + hello: 2.2; + hello: 2000000000000.2; } #test { - #hello: 1} + #hello: 1} #test2 { - help: none; \ No newline at end of file + help: none; diff --git a/tests/fixtures/1readme.css b/tests/fixtures/1readme.css index f782fad9..adfa9f99 100644 --- a/tests/fixtures/1readme.css +++ b/tests/fixtures/1readme.css @@ -4,7 +4,7 @@ font-family: "CrassRoots"; src: url("../media/cr.ttf") } - + html, body { - font-size: 1.6em + font-size: 1.6em } diff --git a/tests/fixtures/2readme.css b/tests/fixtures/2readme.css index 9b8dbcca..8deae980 100644 --- a/tests/fixtures/2readme.css +++ b/tests/fixtures/2readme.css @@ -1,5 +1,5 @@ #header { - margin: 10px 2em 1cm 2%; - font-family: Verdana, Helvetica, "Gill Sans", sans-serif; - color: red !important; + margin: 10px 2em 1cm 2%; + font-family: Verdana, Helvetica, "Gill Sans", sans-serif; + color: red !important; } diff --git a/tests/fixtures/atrules.css b/tests/fixtures/atrules.css index 58b27c1f..d4b4d21a 100644 --- a/tests/fixtures/atrules.css +++ b/tests/fixtures/atrules.css @@ -1,28 +1,28 @@ @charset "utf-8"; @font-face { - font-family: "CrassRoots"; - src: url("../media/cr.ttf") + font-family: "CrassRoots"; + src: url("../media/cr.ttf") } html, body { - font-size: -0.6em + font-size: -0.6em } @keyframes mymove { - from { top: 0px; } - to { top: 200px; } + from { top: 0px; } + to { top: 200px; } } @-moz-keyframes some-move { - from { top: 0px; } - to { top: 200px; } + from { top: 0px; } + to { top: 200px; } } @supports ( (perspective: 10px) or (-moz-perspective: 10px) or (-webkit-perspective: 10px) or (-ms-perspective: 10px) or (-o-perspective: 10px) ) { - body { - font-family: 'Helvetica'; - } + body { + font-family: 'Helvetica'; + } } @page :pseudo-class { @@ -54,4 +54,4 @@ html, body { @region-style #intro { p { color: blue; } -} \ No newline at end of file +} diff --git a/tests/fixtures/calc.css b/tests/fixtures/calc.css index 9fd0d973..aaaa65ce 100644 --- a/tests/fixtures/calc.css +++ b/tests/fixtures/calc.css @@ -1,7 +1,7 @@ div { width: calc(100% / 4); } div { margin-top: calc(-120% - 4px); } div { - height: -webkit-calc(9/16 * 100%)!important; - width: -moz-calc((50px - 50%)*2); + height: -webkit-calc(9/16 * 100%)!important; + width: -moz-calc((50px - 50%)*2); } div { width: calc(50% - ( ( 4% ) * 0.5 ) ); } diff --git a/tests/fixtures/case-insensitivity.css b/tests/fixtures/case-insensitivity.css index 43716029..bfc20ffe 100644 --- a/tests/fixtures/case-insensitivity.css +++ b/tests/fixtures/case-insensitivity.css @@ -6,10 +6,10 @@ } #myid { - CaSe: insensitive !imPORTANT; - frequency: 30hz; - font-size: 1EM; - color: RGB(255, 255, 0); - color: hSL(40, 40%, 30%); - font-Family: Arial; /* The value needs to remain capitalized */ -} \ No newline at end of file + CaSe: insensitive !imPORTANT; + frequency: 30hz; + font-size: 1EM; + color: RGB(255, 255, 0); + color: hSL(40, 40%, 30%); + font-Family: Arial; /* The value needs to remain capitalized */ +} diff --git a/tests/fixtures/colortest.css b/tests/fixtures/colortest.css index f834aa77..f344137b 100644 --- a/tests/fixtures/colortest.css +++ b/tests/fixtures/colortest.css @@ -1,29 +1,29 @@ #mine { - color: red; - border-color: rgb(10, 100, 230); - border-color: rgba(10, 100, 231, 0.3); - outline-color: #222; - background-color: #232323; + color: red; + border-color: rgb(10, 100, 230); + border-color: rgba(10, 100, 231, 0.3); + outline-color: #222; + background-color: #232323; } #yours { - background-color: hsl(220, 10%, 220%); - background-color: hsla(220, 10%, 220%, 0.3); - outline-color: #22; + background-color: hsl(220, 10%, 220%); + background-color: hsla(220, 10%, 220%, 0.3); + outline-color: #22; } #variables { - background-color: rgb(var(--some-rgb)); - background-color: rgb(var(--r), var(--g), var(--b)); - background-color: rgb(255, var(--g), var(--b)); - background-color: rgb(255, 255, var(--b)); - background-color: rgb(255, var(--rg)); + background-color: rgb(var(--some-rgb)); + background-color: rgb(var(--r), var(--g), var(--b)); + background-color: rgb(255, var(--g), var(--b)); + background-color: rgb(255, 255, var(--b)); + background-color: rgb(255, var(--rg)); - background-color: hsl(var(--some-hsl)); + background-color: hsl(var(--some-hsl)); } #variables-alpha { - background-color: rgba(var(--some-rgb), 0.1); - background-color: rgba(var(--some-rg), 255, 0.1); - background-color: hsla(var(--some-hsl), 0.1); + background-color: rgba(var(--some-rgb), 0.1); + background-color: rgba(var(--some-rg), 255, 0.1); + background-color: hsla(var(--some-hsl), 0.1); } diff --git a/tests/fixtures/escaped-tokens.css b/tests/fixtures/escaped-tokens.css index 333c6569..97e2dfeb 100644 --- a/tests/fixtures/escaped-tokens.css +++ b/tests/fixtures/escaped-tokens.css @@ -2,6 +2,6 @@ * Special case function-like tokens, with an escape backslash followed by a non-newline and non-hex digit character, should be parsed as the appropriate \Sabberworm\CSS\Value\ type */ body { - background: u\rl("//example.org/picture.jpg"); - height: ca\lc(100% - 1px); -} + background: u\rl("//example.org/picture.jpg"); + height: ca\lc(100% - 1px); +} diff --git a/tests/fixtures/ie.css b/tests/fixtures/ie.css index 6c0fb381..9f070e24 100644 --- a/tests/fixtures/ie.css +++ b/tests/fixtures/ie.css @@ -1,6 +1,6 @@ .nav-thumb-wrapper:hover img, a.activeSlide img { - filter: alpha(opacity=100); - -moz-opacity: 1; - -khtml-opacity: 1; - opacity: 1; -} + filter: alpha(opacity=100); + -moz-opacity: 1; + -khtml-opacity: 1; + opacity: 1; +} diff --git a/tests/fixtures/inner-color.css b/tests/fixtures/inner-color.css index 7fb28b6d..541a65f3 100644 --- a/tests/fixtures/inner-color.css +++ b/tests/fixtures/inner-color.css @@ -1,3 +1,3 @@ test { - background: -webkit-gradient(linear, 0 0, 0 bottom, from(#006cad), to(hsl(202, 100%, 49%))); -} \ No newline at end of file + background: -webkit-gradient(linear, 0 0, 0 bottom, from(#006cad), to(hsl(202, 100%, 49%))); +} diff --git a/tests/fixtures/missing-property-value.css b/tests/fixtures/missing-property-value.css index 33eb473d..22d87c8f 100644 --- a/tests/fixtures/missing-property-value.css +++ b/tests/fixtures/missing-property-value.css @@ -1,4 +1,4 @@ div { - display: inline-block; - display: + display: inline-block; + display: } diff --git a/tests/fixtures/namespaces.css b/tests/fixtures/namespaces.css index c396c974..3577c026 100644 --- a/tests/fixtures/namespaces.css +++ b/tests/fixtures/namespaces.css @@ -10,9 +10,9 @@ foo|test { - gaga: 1; + gaga: 1; } |test { - gaga: 2; -} \ No newline at end of file + gaga: 2; +} diff --git a/tests/fixtures/nested.css b/tests/fixtures/nested.css index b59dc80e..e1f41fe6 100644 --- a/tests/fixtures/nested.css +++ b/tests/fixtures/nested.css @@ -1,17 +1,17 @@ html { - some: -test(val1); + some: -test(val1); } html { - some-other: -test(val1); + some-other: -test(val1); } @media screen { - html { - some: -test(val2); - } + html { + some: -test(val2); + } } #unrelated { - other: yes; + other: yes; } diff --git a/tests/fixtures/slashed.css b/tests/fixtures/slashed.css index 5b629be5..86f4ee82 100644 --- a/tests/fixtures/slashed.css +++ b/tests/fixtures/slashed.css @@ -1,4 +1,4 @@ .test { - font: 12px/1.5 Verdana, Arial, sans-serif; - border-radius: 5px 10px 5px 10px / 10px 5px 10px 5px; + font: 12px/1.5 Verdana, Arial, sans-serif; + border-radius: 5px 10px 5px 10px / 10px 5px 10px 5px; } diff --git a/tests/fixtures/specificity.css b/tests/fixtures/specificity.css index 82a2939a..1a7a0121 100644 --- a/tests/fixtures/specificity.css +++ b/tests/fixtures/specificity.css @@ -3,5 +3,5 @@ .help:hover, li.green, ol li::before { - font-family: Helvetica; + font-family: Helvetica; } diff --git a/tests/fixtures/url.css b/tests/fixtures/url.css index 93aae97f..feb91bc3 100644 --- a/tests/fixtures/url.css +++ b/tests/fixtures/url.css @@ -1,4 +1,4 @@ body { background: #FFFFFF url("https://somesite.com/images/someimage.gif") repeat top center; } body { - background-url: url("https://somesite.com/images/someimage.gif"); -} \ No newline at end of file + background-url: url("https://somesite.com/images/someimage.gif"); +} diff --git a/tests/fixtures/values.css b/tests/fixtures/values.css index 35dbd729..f00c0768 100644 --- a/tests/fixtures/values.css +++ b/tests/fixtures/values.css @@ -1,15 +1,15 @@ #header { - margin: 10px 2em 1cm 2%; - font-family: Verdana, Helvetica, "Gill Sans", sans-serif; - font-size: 10px; - color: red !important; - background-color: green; - background-color: rgba(0,128,0,0.7); - frequency: 30Hz; + margin: 10px 2em 1cm 2%; + font-family: Verdana, Helvetica, "Gill Sans", sans-serif; + font-size: 10px; + color: red !important; + background-color: green; + background-color: rgba(0,128,0,0.7); + frequency: 30Hz; transform: rotate(1turn); } body { - color: green; - font: 75% "Lucida Grande", "Trebuchet MS", Verdana, sans-serif; + color: green; + font: 75% "Lucida Grande", "Trebuchet MS", Verdana, sans-serif; } diff --git a/tests/fixtures/whitespace.css b/tests/fixtures/whitespace.css index 6b21c24f..de127ece 100644 --- a/tests/fixtures/whitespace.css +++ b/tests/fixtures/whitespace.css @@ -1,3 +1,3 @@ .test { - background-image : url ( 4px ) ; + background-image : url ( 4px ) ; } From 0c96d67cd9ab89e2c6bae713891c0603a89c4225 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 27 Jan 2025 00:34:03 +0100 Subject: [PATCH 115/555] [TASK] Avoid using `@covers` annotations referencing methods (#820) According to the PHPUnit documentation [1], `@covers` annotations are only recommended to reference classes (and global functions), but not methods from classes. Also sort the `@covers` annotations. Note: This will probably artificially increase our calculated code coverage numbers until we migrate our tests to more focused unit tests. Closes #809 [1] https://docs.phpunit.de/en/8.5/annotations.html#covers --- config/phpstan-baseline.neon | 6 ------ tests/ParserTest.php | 23 ++++++++++------------- tests/RuleSet/LenientParsingTest.php | 18 +++++++++--------- 3 files changed, 19 insertions(+), 28 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index a01c9a6c..43b9c063 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -12,9 +12,3 @@ parameters: count: 2 path: ../src/RuleSet/DeclarationBlock.php - - - message: '#^@covers value \\Sabberworm\\CSS\\Value\\Value\:\:parseValue\(\) references an invalid method\.$#' - identifier: phpunit.coversMethod - count: 2 - path: ../tests/ParserTest.php - diff --git a/tests/ParserTest.php b/tests/ParserTest.php index e57511ff..ae91bf1d 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -26,16 +26,17 @@ use Sabberworm\CSS\Value\URL; /** + * @covers \Sabberworm\CSS\CSSList\Document * @covers \Sabberworm\CSS\Parser - * @covers \Sabberworm\CSS\CSSList\Document::parse - * @covers \Sabberworm\CSS\Rule\Rule::parse - * @covers \Sabberworm\CSS\RuleSet\DeclarationBlock::parse - * @covers \Sabberworm\CSS\Value\CalcFunction::parse - * @covers \Sabberworm\CSS\Value\Color::parse - * @covers \Sabberworm\CSS\Value\CSSString::parse - * @covers \Sabberworm\CSS\Value\LineName::parse - * @covers \Sabberworm\CSS\Value\Size::parse - * @covers \Sabberworm\CSS\Value\URL::parse + * @covers \Sabberworm\CSS\RuleSet\DeclarationBlock + * @covers \Sabberworm\CSS\Rule\Rule + * @covers \Sabberworm\CSS\Value\CSSString + * @covers \Sabberworm\CSS\Value\CalcFunction + * @covers \Sabberworm\CSS\Value\Color + * @covers \Sabberworm\CSS\Value\LineName + * @covers \Sabberworm\CSS\Value\Size + * @covers \Sabberworm\CSS\Value\URL + * @covers \Sabberworm\CSS\Value\Value */ final class ParserTest extends TestCase { @@ -971,8 +972,6 @@ public function unopenedClosingBracketFailure(): void /** * Ensure that a missing property value raises an exception. * - * @covers \Sabberworm\CSS\Value\Value::parseValue() - * * @test */ public function missingPropertyValueStrict(): void @@ -985,8 +984,6 @@ public function missingPropertyValueStrict(): void /** * Ensure that a missing property value is ignored when in lenient parsing mode. * - * @covers \Sabberworm\CSS\Value\Value::parseValue() - * * @test */ public function missingPropertyValueLenient(): void diff --git a/tests/RuleSet/LenientParsingTest.php b/tests/RuleSet/LenientParsingTest.php index e16dfec6..312bbd8b 100644 --- a/tests/RuleSet/LenientParsingTest.php +++ b/tests/RuleSet/LenientParsingTest.php @@ -11,16 +11,16 @@ use Sabberworm\CSS\Settings; /** + * @covers \Sabberworm\CSS\CSSList\Document * @covers \Sabberworm\CSS\Parser - * @covers \Sabberworm\CSS\CSSList\Document::parse - * @covers \Sabberworm\CSS\Rule\Rule::parse - * @covers \Sabberworm\CSS\RuleSet\DeclarationBlock::parse - * @covers \Sabberworm\CSS\Value\CalcFunction::parse - * @covers \Sabberworm\CSS\Value\Color::parse - * @covers \Sabberworm\CSS\Value\CSSString::parse - * @covers \Sabberworm\CSS\Value\LineName::parse - * @covers \Sabberworm\CSS\Value\Size::parse - * @covers \Sabberworm\CSS\Value\URL::parse + * @covers \Sabberworm\CSS\RuleSet\DeclarationBlock + * @covers \Sabberworm\CSS\Rule\Rule + * @covers \Sabberworm\CSS\Value\CSSString + * @covers \Sabberworm\CSS\Value\CalcFunction + * @covers \Sabberworm\CSS\Value\Color + * @covers \Sabberworm\CSS\Value\LineName + * @covers \Sabberworm\CSS\Value\Size + * @covers \Sabberworm\CSS\Value\URL */ final class LenientParsingTest extends TestCase { From 59bcb6c67f30fffe9bce77f5bb25712e6bccebf3 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 27 Jan 2025 00:46:50 +0100 Subject: [PATCH 116/555] [BUGFIX] Create `Size` with correct types in `expandBackgroundShorthand` (#814) Co-authored-by: JakeQZ --- config/phpstan-baseline.neon | 6 ------ src/RuleSet/DeclarationBlock.php | 4 ++-- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 43b9c063..a7a2f211 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -6,9 +6,3 @@ parameters: count: 2 path: ../src/OutputFormat.php - - - message: '#^Class Sabberworm\\CSS\\Value\\Size constructor invoked with 5 parameters, 1\-4 required\.$#' - identifier: arguments.count - count: 2 - path: ../src/RuleSet/DeclarationBlock.php - diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index f701c1ec..4a26e6d5 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -396,8 +396,8 @@ public function expandBackgroundShorthand(): void '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 c26f4a3ad7adf6ca5ce79b629256cc85cca2a135 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Mon, 27 Jan 2025 08:34:49 +0000 Subject: [PATCH 117/555] [BUGFIX] Correct type annotation for `ValueList` components (#826) The `Color` class uses this property (`aComponents`) as an associative array. Therefore all related type annotations need `array-key` rather than `int` for the array key type. Co-authored-by: Jake Hotson --- src/Value/CSSFunction.php | 4 ++-- src/Value/Color.php | 6 +++--- src/Value/ValueList.php | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index 82b21e8a..6f493d63 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -20,7 +20,7 @@ class CSSFunction extends ValueList /** * @param string $sName - * @param RuleValueList|array $aArguments + * @param RuleValueList|array $aArguments * @param string $sSeparator * @param int $iLineNo */ @@ -91,7 +91,7 @@ public function setName($sName): void } /** - * @return array + * @return array */ public function getArguments() { diff --git a/src/Value/Color.php b/src/Value/Color.php index 07f11c34..71104344 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -16,7 +16,7 @@ class Color extends CSSFunction { /** - * @param array $colorValues + * @param array $colorValues * @param int $lineNumber */ public function __construct(array $colorValues, $lineNumber = 0) @@ -193,7 +193,7 @@ private static function mapRange(float $value, float $fromMin, float $fromMax, f } /** - * @return array + * @return array */ public function getColor() { @@ -201,7 +201,7 @@ public function getColor() } /** - * @param array $colorValues + * @param array $colorValues */ public function setColor(array $colorValues): void { diff --git a/src/Value/ValueList.php b/src/Value/ValueList.php index d332c16f..4a245ed1 100644 --- a/src/Value/ValueList.php +++ b/src/Value/ValueList.php @@ -15,7 +15,7 @@ abstract class ValueList extends Value { /** - * @var array + * @var array */ protected $aComponents; @@ -25,7 +25,7 @@ abstract class ValueList extends Value protected $sSeparator; /** - * @param array|Value|string $aComponents + * @param array|Value|string $aComponents * @param string $sSeparator * @param int $iLineNo */ @@ -48,7 +48,7 @@ public function addListComponent($mComponent): void } /** - * @return array + * @return array */ public function getListComponents() { @@ -56,7 +56,7 @@ public function getListComponents() } /** - * @param array $aComponents + * @param array $aComponents */ public function setListComponents(array $aComponents): void { From 61971b88f6804276351529f8b84e0c98361563ba Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Mon, 27 Jan 2025 08:39:05 +0000 Subject: [PATCH 118/555] [CLEANUP] Add `Color::getRealName` method (#827) The DocBlock should explain. Co-authored-by: Jake Hotson --- src/Value/Color.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Value/Color.php b/src/Value/Color.php index 71104344..60d76fa9 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -227,7 +227,7 @@ public function render(OutputFormat $outputFormat): string // Shorthand RGB color values if ( $outputFormat->getRGBHashNotation() - && \implode('', \array_keys($this->aComponents)) === 'rgb' + && $this->getRealName() === 'rgb' && $this->allComponentsAreNumbers() ) { $result = \sprintf( @@ -242,6 +242,16 @@ public function render(OutputFormat $outputFormat): string return parent::render($outputFormat); } + /** + * The function name is a concatenation of the array keys of the components, which is passed to the constructor. + * However, this can be changed by calling {@see CSSFunction::setName}, + * so is not reliable in situations where it's necessary to determine the function name based on the components. + */ + private function getRealName(): string + { + return \implode('', \array_keys($this->aComponents)); + } + /** * Test whether all color components are absolute numbers (CSS type `number`), not percentages or anything else. * If any component is not an instance of `Size`, the method will also return `false`. From 9c0057605db6e4ef693b86fad90e9fea03af0ceb Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Mon, 27 Jan 2025 16:37:04 +0000 Subject: [PATCH 119/555] [CLEANUP] Extract method `Color::renderAsHex` (#825) An additional method to render in the "modern" syntax will soon be needed. Splitting up the `render` method into sub-methods will help as a precursor to that. --- src/Value/Color.php | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/Value/Color.php b/src/Value/Color.php index 60d76fa9..5dd832a6 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -230,15 +230,9 @@ public function render(OutputFormat $outputFormat): string && $this->getRealName() === 'rgb' && $this->allComponentsAreNumbers() ) { - $result = \sprintf( - '%02x%02x%02x', - $this->aComponents['r']->getSize(), - $this->aComponents['g']->getSize(), - $this->aComponents['b']->getSize() - ); - return '#' . (($result[0] == $result[1]) && ($result[2] == $result[3]) && ($result[4] == $result[5]) - ? "$result[0]$result[2]$result[4]" : $result); + return $this->renderAsHex(); } + return parent::render($outputFormat); } @@ -266,4 +260,26 @@ private function allComponentsAreNumbers(): bool return true; } + + /** + * Note that this method assumes the following: + * - The `aComponents` array has keys for `r`, `g` and `b`; + * - The values in the array are all instances of `Size`. + * + * Errors will be triggered or thrown if this is not the case. + * + * @return non-empty-string + */ + private function renderAsHex(): string + { + $result = \sprintf( + '%02x%02x%02x', + $this->aComponents['r']->getSize(), + $this->aComponents['g']->getSize(), + $this->aComponents['b']->getSize() + ); + $canUseShortVariant = ($result[0] == $result[1]) && ($result[2] == $result[3]) && ($result[4] == $result[5]); + + return '#' . ($canUseShortVariant ? $result[0] . $result[2] . $result[4] : $result); + } } From 98e420450cd0b329d8c1b4f596bb3250c49eb6c8 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 27 Jan 2025 18:07:01 +0100 Subject: [PATCH 120/555] [CLEANUP] Avoid using `empty` on an integer (#829) `empty` behaves in mysterious ways and should be avoided. --- src/Parsing/SourceException.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Parsing/SourceException.php b/src/Parsing/SourceException.php index 8612f249..67092550 100644 --- a/src/Parsing/SourceException.php +++ b/src/Parsing/SourceException.php @@ -18,7 +18,7 @@ class SourceException extends \Exception public function __construct($sMessage, $iLineNo = 0) { $this->iLineNo = $iLineNo; - if (!empty($iLineNo)) { + if ($iLineNo !== 0) { $sMessage .= " [line no: $iLineNo]"; } parent::__construct($sMessage); From 61aea7396adcbb80dd0b92681caf3c01a12184a2 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 27 Jan 2025 19:56:44 +0100 Subject: [PATCH 121/555] [CLEANUP] Avoid Hungarian notation for `iLineNo` (#833) Part of #756 --- src/CSSList/CSSList.php | 10 ++++---- src/CSSList/Document.php | 6 ++--- src/CSSList/KeyFrame.php | 6 ++--- src/Parser.php | 6 ++--- src/Parsing/OutputException.php | 6 ++--- src/Parsing/ParserState.php | 30 ++++++++++++------------ src/Parsing/SourceException.php | 14 +++++------ src/Parsing/UnexpectedTokenException.php | 6 ++--- src/Property/CSSNamespace.php | 10 ++++---- src/Property/Charset.php | 10 ++++---- src/Property/Import.php | 10 ++++---- src/Rule/Rule.php | 14 +++++------ src/RuleSet/AtRuleSet.php | 6 ++--- src/RuleSet/DeclarationBlock.php | 16 ++++++------- src/RuleSet/RuleSet.php | 10 ++++---- src/Value/CSSFunction.php | 8 +++---- src/Value/CSSString.php | 6 ++--- src/Value/CalcRuleValueList.php | 6 ++--- src/Value/LineName.php | 6 ++--- src/Value/PrimitiveValue.php | 6 ++--- src/Value/RuleValueList.php | 6 ++--- src/Value/Size.php | 6 ++--- src/Value/URL.php | 6 ++--- src/Value/Value.php | 10 ++++---- src/Value/ValueList.php | 6 ++--- 25 files changed, 113 insertions(+), 113 deletions(-) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 282c7bd6..d1c328c4 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -46,16 +46,16 @@ abstract class CSSList implements Renderable, Commentable /** * @var int */ - protected $iLineNo; + protected $lineNumber; /** - * @param int $iLineNo + * @param int $lineNumber */ - public function __construct($iLineNo = 0) + public function __construct($lineNumber = 0) { $this->comments = []; $this->aContents = []; - $this->iLineNo = $iLineNo; + $this->lineNumber = $lineNumber; } /** @@ -255,7 +255,7 @@ private static function identifierIs($sIdentifier, string $sMatch): bool */ public function getLineNo() { - return $this->iLineNo; + return $this->lineNumber; } /** diff --git a/src/CSSList/Document.php b/src/CSSList/Document.php index 8f9426cf..7667587f 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -19,11 +19,11 @@ class Document extends CSSBlockList { /** - * @param int $iLineNo + * @param int $lineNumber */ - public function __construct($iLineNo = 0) + public function __construct($lineNumber = 0) { - parent::__construct($iLineNo); + parent::__construct($lineNumber); } /** diff --git a/src/CSSList/KeyFrame.php b/src/CSSList/KeyFrame.php index 4aefa389..61e70380 100644 --- a/src/CSSList/KeyFrame.php +++ b/src/CSSList/KeyFrame.php @@ -20,11 +20,11 @@ class KeyFrame extends CSSList implements AtRule private $animationName; /** - * @param int $iLineNo + * @param int $lineNumber */ - public function __construct($iLineNo = 0) + public function __construct($lineNumber = 0) { - parent::__construct($iLineNo); + parent::__construct($lineNumber); $this->vendorKeyFrame = null; $this->animationName = null; } diff --git a/src/Parser.php b/src/Parser.php index 0bb836ac..2b09e507 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -21,14 +21,14 @@ class Parser /** * @param string $sText the complete CSS as text (i.e., usually the contents of a CSS file) * @param Settings|null $oParserSettings - * @param int $iLineNo the line number (starting from 1, not from 0) + * @param int $lineNumber the line number (starting from 1, not from 0) */ - public function __construct($sText, ?Settings $oParserSettings = null, $iLineNo = 1) + public function __construct($sText, ?Settings $oParserSettings = null, $lineNumber = 1) { if ($oParserSettings === null) { $oParserSettings = Settings::create(); } - $this->oParserState = new ParserState($sText, $oParserSettings, $iLineNo); + $this->oParserState = new ParserState($sText, $oParserSettings, $lineNumber); } /** diff --git a/src/Parsing/OutputException.php b/src/Parsing/OutputException.php index 8b27388a..d52d7380 100644 --- a/src/Parsing/OutputException.php +++ b/src/Parsing/OutputException.php @@ -11,10 +11,10 @@ final class OutputException extends SourceException { /** * @param string $sMessage - * @param int $iLineNo + * @param int $lineNumber */ - public function __construct($sMessage, $iLineNo = 0) + public function __construct($sMessage, $lineNumber = 0) { - parent::__construct($sMessage, $iLineNo); + parent::__construct($sMessage, $lineNumber); } } diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 22152fbd..e1e98386 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -54,18 +54,18 @@ class ParserState /** * @var int */ - private $iLineNo; + private $lineNumber; /** * @param string $sText the complete CSS as text (i.e., usually the contents of a CSS file) - * @param int $iLineNo + * @param int $lineNumber */ - public function __construct($sText, Settings $oParserSettings, $iLineNo = 1) + public function __construct($sText, Settings $oParserSettings, $lineNumber = 1) { $this->oParserSettings = $oParserSettings; $this->sText = $sText; $this->iCurrentPosition = 0; - $this->iLineNo = $iLineNo; + $this->lineNumber = $lineNumber; $this->setCharset($this->oParserSettings->sDefaultCharset); } @@ -98,7 +98,7 @@ public function getCharset() */ public function currentLine() { - return $this->iLineNo; + return $this->lineNumber; } /** @@ -140,11 +140,11 @@ public function setPosition($iPosition): void public function parseIdentifier($bIgnoreCase = true) { if ($this->isEnd()) { - throw new UnexpectedEOFException('', '', 'identifier', $this->iLineNo); + throw new UnexpectedEOFException('', '', 'identifier', $this->lineNumber); } $sResult = $this->parseCharacter(true); if ($sResult === null) { - throw new UnexpectedTokenException($sResult, $this->peek(5), 'identifier', $this->iLineNo); + throw new UnexpectedTokenException($sResult, $this->peek(5), 'identifier', $this->lineNumber); } $sCharacter = null; while (!$this->isEnd() && ($sCharacter = $this->parseCharacter(true)) !== null) { @@ -290,18 +290,18 @@ public function consume($mValue = 1): string $iLineCount = \substr_count($mValue, "\n"); $iLength = $this->strlen($mValue); if (!$this->streql($this->substr($this->iCurrentPosition, $iLength), $mValue)) { - throw new UnexpectedTokenException($mValue, $this->peek(\max($iLength, 5)), $this->iLineNo); + throw new UnexpectedTokenException($mValue, $this->peek(\max($iLength, 5)), $this->lineNumber); } - $this->iLineNo += $iLineCount; + $this->lineNumber += $iLineCount; $this->iCurrentPosition += $this->strlen($mValue); return $mValue; } else { if ($this->iCurrentPosition + $mValue > $this->iLength) { - throw new UnexpectedEOFException($mValue, $this->peek(5), 'count', $this->iLineNo); + throw new UnexpectedEOFException($mValue, $this->peek(5), 'count', $this->lineNumber); } $sResult = $this->substr($this->iCurrentPosition, $mValue); $iLineCount = \substr_count($sResult, "\n"); - $this->iLineNo += $iLineCount; + $this->lineNumber += $iLineCount; $this->iCurrentPosition += $mValue; return $sResult; } @@ -321,7 +321,7 @@ public function consumeExpression($mExpression, $iMaxLength = null): string if (\preg_match($mExpression, $sInput, $aMatches, PREG_OFFSET_CAPTURE) === 1) { return $this->consume($aMatches[0][0]); } - throw new UnexpectedTokenException($mExpression, $this->peek(5), 'expression', $this->iLineNo); + throw new UnexpectedTokenException($mExpression, $this->peek(5), 'expression', $this->lineNumber); } /** @@ -331,7 +331,7 @@ public function consumeComment() { $mComment = false; if ($this->comes('/*')) { - $iLineNo = $this->iLineNo; + $lineNumber = $this->lineNumber; $this->consume(1); $mComment = ''; while (($char = $this->consume(1)) !== '') { @@ -345,7 +345,7 @@ public function consumeComment() if ($mComment !== false) { // We skip the * which was included in the comment. - return new Comment(\substr($mComment, 1), $iLineNo); + return new Comment(\substr($mComment, 1), $lineNumber); } return $mComment; @@ -396,7 +396,7 @@ public function consumeUntil($aEnd, $bIncludeEnd = false, $consumeEnd = false, a 'One of ("' . \implode('","', $aEnd) . '")', $this->peek(5), 'search', - $this->iLineNo + $this->lineNumber ); } diff --git a/src/Parsing/SourceException.php b/src/Parsing/SourceException.php index 67092550..77adb478 100644 --- a/src/Parsing/SourceException.php +++ b/src/Parsing/SourceException.php @@ -9,17 +9,17 @@ class SourceException extends \Exception /** * @var int */ - private $iLineNo; + private $lineNumber; /** * @param string $sMessage - * @param int $iLineNo + * @param int $lineNumber */ - public function __construct($sMessage, $iLineNo = 0) + public function __construct($sMessage, $lineNumber = 0) { - $this->iLineNo = $iLineNo; - if ($iLineNo !== 0) { - $sMessage .= " [line no: $iLineNo]"; + $this->lineNumber = $lineNumber; + if ($lineNumber !== 0) { + $sMessage .= " [line no: $lineNumber]"; } parent::__construct($sMessage); } @@ -29,6 +29,6 @@ public function __construct($sMessage, $iLineNo = 0) */ public function getLineNo() { - return $this->iLineNo; + return $this->lineNumber; } } diff --git a/src/Parsing/UnexpectedTokenException.php b/src/Parsing/UnexpectedTokenException.php index 55effb64..3620ff01 100644 --- a/src/Parsing/UnexpectedTokenException.php +++ b/src/Parsing/UnexpectedTokenException.php @@ -30,9 +30,9 @@ class UnexpectedTokenException extends SourceException * @param string $sExpected * @param string $sFound * @param string $sMatchType - * @param int $iLineNo + * @param int $lineNumber */ - public function __construct($sExpected, $sFound, $sMatchType = 'literal', $iLineNo = 0) + public function __construct($sExpected, $sFound, $sMatchType = 'literal', $lineNumber = 0) { $this->sExpected = $sExpected; $this->sFound = $sFound; @@ -48,6 +48,6 @@ public function __construct($sExpected, $sFound, $sMatchType = 'literal', $iLine $sMessage = \trim("$sExpected $sFound"); } - parent::__construct($sMessage, $iLineNo); + parent::__construct($sMessage, $lineNumber); } } diff --git a/src/Property/CSSNamespace.php b/src/Property/CSSNamespace.php index 1a827434..fcce5d08 100644 --- a/src/Property/CSSNamespace.php +++ b/src/Property/CSSNamespace.php @@ -25,7 +25,7 @@ class CSSNamespace implements AtRule /** * @var int */ - private $iLineNo; + private $lineNumber; /** * @var array @@ -35,13 +35,13 @@ class CSSNamespace implements AtRule /** * @param string $mUrl * @param string|null $sPrefix - * @param int $iLineNo + * @param int $lineNumber */ - public function __construct($mUrl, $sPrefix = null, $iLineNo = 0) + public function __construct($mUrl, $sPrefix = null, $lineNumber = 0) { $this->mUrl = $mUrl; $this->sPrefix = $sPrefix; - $this->iLineNo = $iLineNo; + $this->lineNumber = $lineNumber; $this->comments = []; } @@ -50,7 +50,7 @@ public function __construct($mUrl, $sPrefix = null, $iLineNo = 0) */ public function getLineNo() { - return $this->iLineNo; + return $this->lineNumber; } public function __toString(): string diff --git a/src/Property/Charset.php b/src/Property/Charset.php index a36e55a2..ac704b48 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -26,7 +26,7 @@ class Charset implements AtRule /** * @var int */ - protected $iLineNo; + protected $lineNumber; /** * @var array @@ -35,12 +35,12 @@ class Charset implements AtRule /** * @param CSSString $oCharset - * @param int $iLineNo + * @param int $lineNumber */ - public function __construct(CSSString $oCharset, $iLineNo = 0) + public function __construct(CSSString $oCharset, $lineNumber = 0) { $this->oCharset = $oCharset; - $this->iLineNo = $iLineNo; + $this->lineNumber = $lineNumber; $this->comments = []; } @@ -49,7 +49,7 @@ public function __construct(CSSString $oCharset, $iLineNo = 0) */ public function getLineNo() { - return $this->iLineNo; + return $this->lineNumber; } /** diff --git a/src/Property/Import.php b/src/Property/Import.php index ec8c97df..12106766 100644 --- a/src/Property/Import.php +++ b/src/Property/Import.php @@ -26,7 +26,7 @@ class Import implements AtRule /** * @var int */ - protected $iLineNo; + protected $lineNumber; /** * @var array @@ -36,13 +36,13 @@ class Import implements AtRule /** * @param URL $oLocation * @param string $sMediaQuery - * @param int $iLineNo + * @param int $lineNumber */ - public function __construct(URL $oLocation, $sMediaQuery, $iLineNo = 0) + public function __construct(URL $oLocation, $sMediaQuery, $lineNumber = 0) { $this->oLocation = $oLocation; $this->sMediaQuery = $sMediaQuery; - $this->iLineNo = $iLineNo; + $this->lineNumber = $lineNumber; $this->comments = []; } @@ -51,7 +51,7 @@ public function __construct(URL $oLocation, $sMediaQuery, $iLineNo = 0) */ public function getLineNo() { - return $this->iLineNo; + return $this->lineNumber; } /** diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index d598a366..3f01a3fd 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -44,7 +44,7 @@ class Rule implements Renderable, Commentable /** * @var int */ - protected $iLineNo; + protected $lineNumber; /** * @var int @@ -58,16 +58,16 @@ class Rule implements Renderable, Commentable /** * @param string $sRule - * @param int $iLineNo + * @param int $lineNumber * @param int $iColNo */ - public function __construct($sRule, $iLineNo = 0, $iColNo = 0) + public function __construct($sRule, $lineNumber = 0, $iColNo = 0) { $this->sRule = $sRule; $this->mValue = null; $this->bIsImportant = false; $this->aIeHack = []; - $this->iLineNo = $iLineNo; + $this->lineNumber = $lineNumber; $this->iColNo = $iColNo; $this->comments = []; } @@ -141,7 +141,7 @@ private static function listDelimiterForRule($sRule): array */ public function getLineNo() { - return $this->iLineNo; + return $this->lineNumber; } /** @@ -159,7 +159,7 @@ public function getColNo() public function setPosition($iLine, $iColumn): void { $this->iColNo = $iColumn; - $this->iLineNo = $iLine; + $this->lineNumber = $iLine; } /** @@ -208,7 +208,7 @@ public function addValue($mValue, $sType = ' '): void } 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->lineNumber); if ($mCurrentValue) { $this->mValue->addListComponent($mCurrentValue); } diff --git a/src/RuleSet/AtRuleSet.php b/src/RuleSet/AtRuleSet.php index b52ad127..4a9329a8 100644 --- a/src/RuleSet/AtRuleSet.php +++ b/src/RuleSet/AtRuleSet.php @@ -28,11 +28,11 @@ class AtRuleSet extends RuleSet implements AtRule /** * @param string $sType * @param string $sArgs - * @param int $iLineNo + * @param int $lineNumber */ - public function __construct($sType, $sArgs = '', $iLineNo = 0) + public function __construct($sType, $sArgs = '', $lineNumber = 0) { - parent::__construct($iLineNo); + parent::__construct($lineNumber); $this->sType = $sType; $this->sArgs = $sArgs; } diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 4a26e6d5..f4ce86bd 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -36,11 +36,11 @@ class DeclarationBlock extends RuleSet private $aSelectors; /** - * @param int $iLineNo + * @param int $lineNumber */ - public function __construct($iLineNo = 0) + public function __construct($lineNumber = 0) { - parent::__construct($iLineNo); + parent::__construct($lineNumber); $this->aSelectors = []; } @@ -396,8 +396,8 @@ public function expandBackgroundShorthand(): void '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->lineNumber), + new Size(0, '%', false, $this->lineNumber), ], ]; $mRuleValue = $oRule->getValue(); @@ -749,7 +749,7 @@ public function createFontShorthand(): void $aLHValues = $mRuleValue->getListComponents(); } if ($aLHValues[0] !== 'normal') { - $val = new RuleValueList('/', $this->iLineNo); + $val = new RuleValueList('/', $this->lineNumber); $val->addListComponent($aFSValues[0]); $val->addListComponent($aLHValues[0]); $oNewRule->addValue($val); @@ -765,7 +765,7 @@ public function createFontShorthand(): void } else { $aFFValues = $mRuleValue->getListComponents(); } - $oFFValue = new RuleValueList(',', $this->iLineNo); + $oFFValue = new RuleValueList(',', $this->lineNumber); $oFFValue->setListComponents($aFFValues); $oNewRule->addValue($oFFValue); @@ -791,7 +791,7 @@ public function render(OutputFormat $oOutputFormat): string $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->lineNumber); } $sResult .= $oOutputFormat->sBeforeDeclarationBlock; $sResult .= $oOutputFormat->implode( diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 2920830c..b05b9c8a 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -32,7 +32,7 @@ abstract class RuleSet implements Renderable, Commentable /** * @var int */ - protected $iLineNo; + protected $lineNumber; /** * @var array @@ -40,12 +40,12 @@ abstract class RuleSet implements Renderable, Commentable protected $comments; /** - * @param int $iLineNo + * @param int $lineNumber */ - public function __construct($iLineNo = 0) + public function __construct($lineNumber = 0) { $this->aRules = []; - $this->iLineNo = $iLineNo; + $this->lineNumber = $lineNumber; $this->comments = []; } @@ -94,7 +94,7 @@ public static function parseRuleSet(ParserState $oParserState, RuleSet $oRuleSet */ public function getLineNo() { - return $this->iLineNo; + return $this->lineNumber; } /** diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index 6f493d63..0cb3488b 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -22,17 +22,17 @@ class CSSFunction extends ValueList * @param string $sName * @param RuleValueList|array $aArguments * @param string $sSeparator - * @param int $iLineNo + * @param int $lineNumber */ - public function __construct($sName, $aArguments, $sSeparator = ',', $iLineNo = 0) + public function __construct($sName, $aArguments, $sSeparator = ',', $lineNumber = 0) { if ($aArguments instanceof RuleValueList) { $sSeparator = $aArguments->getListSeparator(); $aArguments = $aArguments->getListComponents(); } $this->sName = $sName; - $this->iLineNo = $iLineNo; - parent::__construct($aArguments, $sSeparator, $iLineNo); + $this->lineNumber = $lineNumber; + parent::__construct($aArguments, $sSeparator, $lineNumber); } /** diff --git a/src/Value/CSSString.php b/src/Value/CSSString.php index f212bdd8..64bd4091 100644 --- a/src/Value/CSSString.php +++ b/src/Value/CSSString.php @@ -24,12 +24,12 @@ class CSSString extends PrimitiveValue /** * @param string $sString - * @param int $iLineNo + * @param int $lineNumber */ - public function __construct($sString, $iLineNo = 0) + public function __construct($sString, $lineNumber = 0) { $this->sString = $sString; - parent::__construct($iLineNo); + parent::__construct($lineNumber); } /** diff --git a/src/Value/CalcRuleValueList.php b/src/Value/CalcRuleValueList.php index 042e697b..508296d9 100644 --- a/src/Value/CalcRuleValueList.php +++ b/src/Value/CalcRuleValueList.php @@ -9,11 +9,11 @@ class CalcRuleValueList extends RuleValueList { /** - * @param int $iLineNo + * @param int $lineNumber */ - public function __construct($iLineNo = 0) + public function __construct($lineNumber = 0) { - parent::__construct(',', $iLineNo); + parent::__construct(',', $lineNumber); } /** diff --git a/src/Value/LineName.php b/src/Value/LineName.php index 2d334c0b..9011acad 100644 --- a/src/Value/LineName.php +++ b/src/Value/LineName.php @@ -13,11 +13,11 @@ class LineName extends ValueList { /** * @param array $aComponents - * @param int $iLineNo + * @param int $lineNumber */ - public function __construct(array $aComponents = [], $iLineNo = 0) + public function __construct(array $aComponents = [], $lineNumber = 0) { - parent::__construct($aComponents, ' ', $iLineNo); + parent::__construct($aComponents, ' ', $lineNumber); } /** diff --git a/src/Value/PrimitiveValue.php b/src/Value/PrimitiveValue.php index f9afd851..4cccf35c 100644 --- a/src/Value/PrimitiveValue.php +++ b/src/Value/PrimitiveValue.php @@ -7,10 +7,10 @@ abstract class PrimitiveValue extends Value { /** - * @param int $iLineNo + * @param int $lineNumber */ - public function __construct($iLineNo = 0) + public function __construct($lineNumber = 0) { - parent::__construct($iLineNo); + parent::__construct($lineNumber); } } diff --git a/src/Value/RuleValueList.php b/src/Value/RuleValueList.php index 93d8e617..b68df0ea 100644 --- a/src/Value/RuleValueList.php +++ b/src/Value/RuleValueList.php @@ -13,10 +13,10 @@ class RuleValueList extends ValueList { /** * @param string $sSeparator - * @param int $iLineNo + * @param int $lineNumber */ - public function __construct($sSeparator = ',', $iLineNo = 0) + public function __construct($sSeparator = ',', $lineNumber = 0) { - parent::__construct([], $sSeparator, $iLineNo); + parent::__construct([], $sSeparator, $lineNumber); } } diff --git a/src/Value/Size.php b/src/Value/Size.php index 0ba4bd66..bf7ce6b8 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -71,11 +71,11 @@ class Size extends PrimitiveValue * @param float|int|string $fSize * @param string|null $sUnit * @param bool $bIsColorComponent - * @param int $iLineNo + * @param int $lineNumber */ - public function __construct($fSize, $sUnit = null, $bIsColorComponent = false, $iLineNo = 0) + public function __construct($fSize, $sUnit = null, $bIsColorComponent = false, $lineNumber = 0) { - parent::__construct($iLineNo); + parent::__construct($lineNumber); $this->fSize = (float) $fSize; $this->sUnit = $sUnit; $this->bIsColorComponent = $bIsColorComponent; diff --git a/src/Value/URL.php b/src/Value/URL.php index 0561719f..ef180eab 100644 --- a/src/Value/URL.php +++ b/src/Value/URL.php @@ -21,11 +21,11 @@ class URL extends PrimitiveValue private $oURL; /** - * @param int $iLineNo + * @param int $lineNumber */ - public function __construct(CSSString $oURL, $iLineNo = 0) + public function __construct(CSSString $oURL, $lineNumber = 0) { - parent::__construct($iLineNo); + parent::__construct($lineNumber); $this->oURL = $oURL; } diff --git a/src/Value/Value.php b/src/Value/Value.php index 3a7f21db..9c8ad164 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -19,14 +19,14 @@ abstract class Value implements Renderable /** * @var int */ - protected $iLineNo; + protected $lineNumber; /** - * @param int $iLineNo + * @param int $lineNumber */ - public function __construct($iLineNo = 0) + public function __construct($lineNumber = 0) { - $this->iLineNo = $iLineNo; + $this->lineNumber = $lineNumber; } /** @@ -214,6 +214,6 @@ private static function parseUnicodeRangeValue(ParserState $oParserState): strin */ public function getLineNo() { - return $this->iLineNo; + return $this->lineNumber; } } diff --git a/src/Value/ValueList.php b/src/Value/ValueList.php index 4a245ed1..445bba26 100644 --- a/src/Value/ValueList.php +++ b/src/Value/ValueList.php @@ -27,11 +27,11 @@ abstract class ValueList extends Value /** * @param array|Value|string $aComponents * @param string $sSeparator - * @param int $iLineNo + * @param int $lineNumber */ - public function __construct($aComponents = [], $sSeparator = ',', $iLineNo = 0) + public function __construct($aComponents = [], $sSeparator = ',', $lineNumber = 0) { - parent::__construct($iLineNo); + parent::__construct($lineNumber); if (!\is_array($aComponents)) { $aComponents = [$aComponents]; } From 9f5c7dc3af40449a1012bb8101ae9e27265a3108 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 27 Jan 2025 19:59:51 +0100 Subject: [PATCH 122/555] [DOCS] Integrate the `8.x` changelog into the `main` changelog (#806) Now the changelog up to the latest feature release 8.7.0 is part of the `main` changelog. This makes it easier to see what has changed after the last feature release. Also, this makes it clear in which release some methods were deprecated, avoid the impression that methods got both deprecated and removed in the same release. --- CHANGELOG.md | 70 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ca30ce7..113ffb4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,29 +13,17 @@ Please also have a look at our - Partial support for CSS Color Module Level 4: - `rgb` and `rgba`, and `hsl` and `hsla` are now aliases (#797} - Parse color functions that use the "modern" syntax (#800) -- Add official support for PHP 8.4 (#657) -- Support arithmetic operators in CSS function arguments (#607) -- Add support for inserting an item in a CSS list (#545) - Add a class diagram to the README (#482) -- Add support for the `dvh`, `lvh` and `svh` length units (#415) - Add more tests (#449) ### Changed - Use more native type declarations and strict mode (#641, #772, #774, #778, #804) -- Mark parsing-internal classes and methods as `@internal` (#674) -- Block installations on unsupported higher PHP versions (#691) -- Improve performance of `Value::parseValue` with many delimiters by refactoring - to remove `array_search()` (#413) - Add visibility to all class/interface constants (#469) ### Deprecated -- Deprecate `Parser::setCharset()` and `Parser::getCharset()` (#688) -- Deprecate the expansion of shorthand properties (#578, #580, #579, #577, #576, - #575, #574, #573, #572, #571, #570, #569, #566, #567, #558) - ### Removed - Remove `Parser::setCharset/getCharset` (#808) @@ -50,11 +38,6 @@ Please also have a look at our - Don't render `rgb` colors with percentage values using hex notation (#803) - Parse `@font-face` `src` property as comma-delimited list (#790) -- Fix type errors in PHP strict mode (#664) -- Fix undefined local variable in `CalcFunction::parse()` (#593) -- 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) ### Documentation @@ -63,6 +46,59 @@ Please also have a look at our @ziegenberg is a new contributor to this release and did a lot of the heavy lifting. Thanks! :heart: +## 8.7.0: Add support for PHP 8.4 + +### Added + +- Add support for PHP 8.4 (#643, #657) + +### Changed + +- Mark parsing-internal classes and methods as `@internal` (#674) +- Block installations on unsupported higher PHP versions (#691) + +### Deprecated + +- Deprecate the expansion of shorthand properties + (#578, #580, #579, #577, #576, #575, #574, #573, #572, #571, #570, #569, #566, + #567, #558, #714) +- Deprecate `Parser::setCharset()` and `Parser::getCharset()` (#688) + +### Fixed + +- Fix type errors in PHP strict mode (#664) + +## 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 + +- Mark all class constants as `@internal` (#472) + +### Fixed + +- Fix undefined local variable in `CalcFunction::parse()` (#593) + +## 8.5.1 + +### 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 ### Added From 48ecde44f1d26026e6587ecd47103d41e29334c3 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 27 Jan 2025 23:08:29 +0100 Subject: [PATCH 123/555] [CLEANUP] Avoid Hungarian notation for `oParserState` (#834) Part of #756 --- src/CSSList/CSSList.php | 100 +++++++++++++-------------- src/CSSList/Document.php | 6 +- src/Parser.php | 6 +- src/Parsing/Anchor.php | 8 +-- src/Rule/Rule.php | 44 ++++++------ src/RuleSet/DeclarationBlock.php | 28 ++++---- src/RuleSet/RuleSet.php | 26 +++---- src/Value/CSSFunction.php | 20 +++--- src/Value/CSSString.php | 22 +++--- src/Value/CalcFunction.php | 66 +++++++++--------- src/Value/LineName.php | 22 +++--- src/Value/Size.php | 26 +++---- src/Value/URL.php | 20 +++--- src/Value/Value.php | 112 +++++++++++++++---------------- 14 files changed, 253 insertions(+), 253 deletions(-) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index d1c328c4..f91ff68d 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -62,25 +62,25 @@ public function __construct($lineNumber = 0) * @throws UnexpectedTokenException * @throws SourceException */ - public static function parseList(ParserState $oParserState, CSSList $oList): void + public static function parseList(ParserState $parserState, CSSList $oList): void { $bIsRoot = $oList instanceof Document; - if (\is_string($oParserState)) { - $oParserState = new ParserState($oParserState, Settings::create()); + if (\is_string($parserState)) { + $parserState = new ParserState($parserState, Settings::create()); } - $bLenientParsing = $oParserState->getSettings()->bLenientParsing; + $bLenientParsing = $parserState->getSettings()->bLenientParsing; $aComments = []; - while (!$oParserState->isEnd()) { - $aComments = \array_merge($aComments, $oParserState->consumeWhiteSpace()); + while (!$parserState->isEnd()) { + $aComments = \array_merge($aComments, $parserState->consumeWhiteSpace()); $oListItem = null; if ($bLenientParsing) { try { - $oListItem = self::parseListItem($oParserState, $oList); + $oListItem = self::parseListItem($parserState, $oList); } catch (UnexpectedTokenException $e) { $oListItem = false; } } else { - $oListItem = self::parseListItem($oParserState, $oList); + $oListItem = self::parseListItem($parserState, $oList); } if ($oListItem === null) { // List parsing finished @@ -90,11 +90,11 @@ public static function parseList(ParserState $oParserState, CSSList $oList): voi $oListItem->addComments($aComments); $oList->append($oListItem); } - $aComments = $oParserState->consumeWhiteSpace(); + $aComments = $parserState->consumeWhiteSpace(); } $oList->addComments($aComments); if (!$bIsRoot && !$bLenientParsing) { - throw new SourceException('Unexpected end of document', $oParserState->currentLine()); + throw new SourceException('Unexpected end of document', $parserState->currentLine()); } } @@ -105,18 +105,18 @@ public static function parseList(ParserState $oParserState, CSSList $oList): voi * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ - private static function parseListItem(ParserState $oParserState, CSSList $oList) + private static function parseListItem(ParserState $parserState, CSSList $oList) { $bIsRoot = $oList instanceof Document; - if ($oParserState->comes('@')) { - $oAtRule = self::parseAtRule($oParserState); + if ($parserState->comes('@')) { + $oAtRule = self::parseAtRule($parserState); if ($oAtRule instanceof Charset) { if (!$bIsRoot) { throw new UnexpectedTokenException( '@charset may only occur in root document', '', 'custom', - $oParserState->currentLine() + $parserState->currentLine() ); } if (\count($oList->getContents()) > 0) { @@ -124,30 +124,30 @@ private static function parseListItem(ParserState $oParserState, CSSList $oList) '@charset must be the first parseable token in a document', '', 'custom', - $oParserState->currentLine() + $parserState->currentLine() ); } - $oParserState->setCharset($oAtRule->getCharset()); + $parserState->setCharset($oAtRule->getCharset()); } return $oAtRule; - } elseif ($oParserState->comes('}')) { + } elseif ($parserState->comes('}')) { if ($bIsRoot) { - if ($oParserState->getSettings()->bLenientParsing) { - return DeclarationBlock::parse($oParserState); + if ($parserState->getSettings()->bLenientParsing) { + return DeclarationBlock::parse($parserState); } else { - throw new SourceException('Unopened {', $oParserState->currentLine()); + throw new SourceException('Unopened {', $parserState->currentLine()); } } else { // End of list return null; } } else { - return DeclarationBlock::parse($oParserState, $oList); + return DeclarationBlock::parse($parserState, $oList); } } /** - * @param ParserState $oParserState + * @param ParserState $parserState * * @return AtRuleBlockList|KeyFrame|Charset|CSSNamespace|Import|AtRuleSet|null * @@ -155,46 +155,46 @@ private static function parseListItem(ParserState $oParserState, CSSList $oList) * @throws UnexpectedTokenException * @throws UnexpectedEOFException */ - private static function parseAtRule(ParserState $oParserState) + private static function parseAtRule(ParserState $parserState) { - $oParserState->consume('@'); - $sIdentifier = $oParserState->parseIdentifier(); - $iIdentifierLineNum = $oParserState->currentLine(); - $oParserState->consumeWhiteSpace(); + $parserState->consume('@'); + $sIdentifier = $parserState->parseIdentifier(); + $iIdentifierLineNum = $parserState->currentLine(); + $parserState->consumeWhiteSpace(); if ($sIdentifier === 'import') { - $oLocation = URL::parse($oParserState); - $oParserState->consumeWhiteSpace(); + $oLocation = URL::parse($parserState); + $parserState->consumeWhiteSpace(); $sMediaQuery = null; - if (!$oParserState->comes(';')) { - $sMediaQuery = \trim($oParserState->consumeUntil([';', ParserState::EOF])); + if (!$parserState->comes(';')) { + $sMediaQuery = \trim($parserState->consumeUntil([';', ParserState::EOF])); if ($sMediaQuery === '') { $sMediaQuery = null; } } - $oParserState->consumeUntil([';', ParserState::EOF], true, true); + $parserState->consumeUntil([';', ParserState::EOF], true, true); return new Import($oLocation, $sMediaQuery, $iIdentifierLineNum); } elseif ($sIdentifier === 'charset') { - $oCharsetString = CSSString::parse($oParserState); - $oParserState->consumeWhiteSpace(); - $oParserState->consumeUntil([';', ParserState::EOF], true, true); + $oCharsetString = CSSString::parse($parserState); + $parserState->consumeWhiteSpace(); + $parserState->consumeUntil([';', ParserState::EOF], true, true); return new Charset($oCharsetString, $iIdentifierLineNum); } elseif (self::identifierIs($sIdentifier, 'keyframes')) { $oResult = new KeyFrame($iIdentifierLineNum); $oResult->setVendorKeyFrame($sIdentifier); - $oResult->setAnimationName(\trim($oParserState->consumeUntil('{', false, true))); - CSSList::parseList($oParserState, $oResult); - if ($oParserState->comes('}')) { - $oParserState->consume('}'); + $oResult->setAnimationName(\trim($parserState->consumeUntil('{', false, true))); + CSSList::parseList($parserState, $oResult); + if ($parserState->comes('}')) { + $parserState->consume('}'); } return $oResult; } elseif ($sIdentifier === 'namespace') { $sPrefix = null; - $mUrl = Value::parsePrimitiveValue($oParserState); - if (!$oParserState->comes(';')) { + $mUrl = Value::parsePrimitiveValue($parserState); + if (!$parserState->comes(';')) { $sPrefix = $mUrl; - $mUrl = Value::parsePrimitiveValue($oParserState); + $mUrl = Value::parsePrimitiveValue($parserState); } - $oParserState->consumeUntil([';', ParserState::EOF], true, true); + $parserState->consumeUntil([';', ParserState::EOF], true, true); if ($sPrefix !== null && !\is_string($sPrefix)) { throw new UnexpectedTokenException('Wrong namespace prefix', $sPrefix, 'custom', $iIdentifierLineNum); } @@ -209,12 +209,12 @@ private static function parseAtRule(ParserState $oParserState) return new CSSNamespace($mUrl, $sPrefix, $iIdentifierLineNum); } else { // Unknown other at rule (font-face or such) - $sArgs = \trim($oParserState->consumeUntil('{', false, true)); + $sArgs = \trim($parserState->consumeUntil('{', false, true)); if (\substr_count($sArgs, '(') != \substr_count($sArgs, ')')) { - if ($oParserState->getSettings()->bLenientParsing) { + if ($parserState->getSettings()->bLenientParsing) { return null; } else { - throw new SourceException('Unmatched brace count in media query', $oParserState->currentLine()); + throw new SourceException('Unmatched brace count in media query', $parserState->currentLine()); } } $bUseRuleSet = true; @@ -226,12 +226,12 @@ private static function parseAtRule(ParserState $oParserState) } if ($bUseRuleSet) { $oAtRule = new AtRuleSet($sIdentifier, $sArgs, $iIdentifierLineNum); - RuleSet::parseRuleSet($oParserState, $oAtRule); + RuleSet::parseRuleSet($parserState, $oAtRule); } else { $oAtRule = new AtRuleBlockList($sIdentifier, $sArgs, $iIdentifierLineNum); - CSSList::parseList($oParserState, $oAtRule); - if ($oParserState->comes('}')) { - $oParserState->consume('}'); + CSSList::parseList($parserState, $oAtRule); + if ($parserState->comes('}')) { + $parserState->consume('}'); } } return $oAtRule; diff --git a/src/CSSList/Document.php b/src/CSSList/Document.php index 7667587f..d577ffac 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -29,10 +29,10 @@ public function __construct($lineNumber = 0) /** * @throws SourceException */ - public static function parse(ParserState $oParserState): Document + public static function parse(ParserState $parserState): Document { - $oDocument = new Document($oParserState->currentLine()); - CSSList::parseList($oParserState, $oDocument); + $oDocument = new Document($parserState->currentLine()); + CSSList::parseList($parserState, $oDocument); return $oDocument; } diff --git a/src/Parser.php b/src/Parser.php index 2b09e507..9f9f4595 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -16,7 +16,7 @@ class Parser /** * @var ParserState */ - private $oParserState; + private $parserState; /** * @param string $sText the complete CSS as text (i.e., usually the contents of a CSS file) @@ -28,7 +28,7 @@ public function __construct($sText, ?Settings $oParserSettings = null, $lineNumb if ($oParserSettings === null) { $oParserSettings = Settings::create(); } - $this->oParserState = new ParserState($sText, $oParserSettings, $lineNumber); + $this->parserState = new ParserState($sText, $oParserSettings, $lineNumber); } /** @@ -38,6 +38,6 @@ public function __construct($sText, ?Settings $oParserSettings = null, $lineNumb */ public function parse(): Document { - return Document::parse($this->oParserState); + return Document::parse($this->parserState); } } diff --git a/src/Parsing/Anchor.php b/src/Parsing/Anchor.php index 545cb035..039f783e 100644 --- a/src/Parsing/Anchor.php +++ b/src/Parsing/Anchor.php @@ -17,19 +17,19 @@ class Anchor /** * @var ParserState */ - private $oParserState; + private $parserState; /** * @param int $iPosition */ - public function __construct($iPosition, ParserState $oParserState) + public function __construct($iPosition, ParserState $parserState) { $this->iPosition = $iPosition; - $this->oParserState = $oParserState; + $this->parserState = $parserState; } public function backtrack(): void { - $this->oParserState->setPosition($this->iPosition); + $this->parserState->setPosition($this->iPosition); } } diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index 3f01a3fd..8cdb05d7 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -76,39 +76,39 @@ public function __construct($sRule, $lineNumber = 0, $iColNo = 0) * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ - public static function parse(ParserState $oParserState): Rule + public static function parse(ParserState $parserState): Rule { - $aComments = $oParserState->consumeWhiteSpace(); + $aComments = $parserState->consumeWhiteSpace(); $oRule = new Rule( - $oParserState->parseIdentifier(!$oParserState->comes('--')), - $oParserState->currentLine(), - $oParserState->currentColumn() + $parserState->parseIdentifier(!$parserState->comes('--')), + $parserState->currentLine(), + $parserState->currentColumn() ); $oRule->setComments($aComments); - $oRule->addComments($oParserState->consumeWhiteSpace()); - $oParserState->consume(':'); - $oValue = Value::parseValue($oParserState, self::listDelimiterForRule($oRule->getRule())); + $oRule->addComments($parserState->consumeWhiteSpace()); + $parserState->consume(':'); + $oValue = Value::parseValue($parserState, self::listDelimiterForRule($oRule->getRule())); $oRule->setValue($oValue); - if ($oParserState->getSettings()->bLenientParsing) { - while ($oParserState->comes('\\')) { - $oParserState->consume('\\'); - $oRule->addIeHack($oParserState->consume()); - $oParserState->consumeWhiteSpace(); + if ($parserState->getSettings()->bLenientParsing) { + while ($parserState->comes('\\')) { + $parserState->consume('\\'); + $oRule->addIeHack($parserState->consume()); + $parserState->consumeWhiteSpace(); } } - $oParserState->consumeWhiteSpace(); - if ($oParserState->comes('!')) { - $oParserState->consume('!'); - $oParserState->consumeWhiteSpace(); - $oParserState->consume('important'); + $parserState->consumeWhiteSpace(); + if ($parserState->comes('!')) { + $parserState->consume('!'); + $parserState->consumeWhiteSpace(); + $parserState->consume('important'); $oRule->setIsImportant(true); } - $oParserState->consumeWhiteSpace(); - while ($oParserState->comes(';')) { - $oParserState->consume(';'); + $parserState->consumeWhiteSpace(); + while ($parserState->comes(';')) { + $parserState->consume(';'); } - $oParserState->consumeWhiteSpace(); + $parserState->consumeWhiteSpace(); return $oRule; } diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index f4ce86bd..76ead48d 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -52,32 +52,32 @@ public function __construct($lineNumber = 0) * @throws UnexpectedTokenException * @throws UnexpectedEOFException */ - public static function parse(ParserState $oParserState, $oList = null) + public static function parse(ParserState $parserState, $oList = null) { $aComments = []; - $oResult = new DeclarationBlock($oParserState->currentLine()); + $oResult = new DeclarationBlock($parserState->currentLine()); try { $aSelectorParts = []; $sStringWrapperChar = false; do { - $aSelectorParts[] = $oParserState->consume(1) - . $oParserState->consumeUntil(['{', '}', '\'', '"'], false, false, $aComments); - if (\in_array($oParserState->peek(), ['\'', '"'], true) && \substr(\end($aSelectorParts), -1) != '\\') { + $aSelectorParts[] = $parserState->consume(1) + . $parserState->consumeUntil(['{', '}', '\'', '"'], false, false, $aComments); + if (\in_array($parserState->peek(), ['\'', '"'], true) && \substr(\end($aSelectorParts), -1) != '\\') { if ($sStringWrapperChar === false) { - $sStringWrapperChar = $oParserState->peek(); - } elseif ($sStringWrapperChar == $oParserState->peek()) { + $sStringWrapperChar = $parserState->peek(); + } elseif ($sStringWrapperChar == $parserState->peek()) { $sStringWrapperChar = false; } } - } while (!\in_array($oParserState->peek(), ['{', '}'], true) || $sStringWrapperChar !== false); + } while (!\in_array($parserState->peek(), ['{', '}'], true) || $sStringWrapperChar !== false); $oResult->setSelectors(\implode('', $aSelectorParts), $oList); - if ($oParserState->comes('{')) { - $oParserState->consume(1); + if ($parserState->comes('{')) { + $parserState->consume(1); } } catch (UnexpectedTokenException $e) { - if ($oParserState->getSettings()->bLenientParsing) { - if (!$oParserState->comes('}')) { - $oParserState->consumeUntil('}', false, true); + if ($parserState->getSettings()->bLenientParsing) { + if (!$parserState->comes('}')) { + $parserState->consumeUntil('}', false, true); } return false; } else { @@ -85,7 +85,7 @@ public static function parse(ParserState $oParserState, $oList = null) } } $oResult->setComments($aComments); - RuleSet::parseRuleSet($oParserState, $oResult); + RuleSet::parseRuleSet($parserState, $oResult); return $oResult; } diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index b05b9c8a..71c86d1d 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -53,25 +53,25 @@ public function __construct($lineNumber = 0) * @throws UnexpectedTokenException * @throws UnexpectedEOFException */ - public static function parseRuleSet(ParserState $oParserState, RuleSet $oRuleSet): void + public static function parseRuleSet(ParserState $parserState, RuleSet $oRuleSet): void { - while ($oParserState->comes(';')) { - $oParserState->consume(';'); + while ($parserState->comes(';')) { + $parserState->consume(';'); } - while (!$oParserState->comes('}')) { + while (!$parserState->comes('}')) { $oRule = null; - if ($oParserState->getSettings()->bLenientParsing) { + if ($parserState->getSettings()->bLenientParsing) { try { - $oRule = Rule::parse($oParserState); + $oRule = Rule::parse($parserState); } catch (UnexpectedTokenException $e) { try { - $sConsume = $oParserState->consumeUntil(["\n", ';', '}'], true); + $sConsume = $parserState->consumeUntil(["\n", ';', '}'], true); // We need to “unfind” the matches to the end of the ruleSet as this will be matched later - if ($oParserState->streql(\substr($sConsume, -1), '}')) { - $oParserState->backtrack(1); + if ($parserState->streql(\substr($sConsume, -1), '}')) { + $parserState->backtrack(1); } else { - while ($oParserState->comes(';')) { - $oParserState->consume(';'); + while ($parserState->comes(';')) { + $parserState->consume(';'); } } } catch (UnexpectedTokenException $e) { @@ -80,13 +80,13 @@ public static function parseRuleSet(ParserState $oParserState, RuleSet $oRuleSet } } } else { - $oRule = Rule::parse($oParserState); + $oRule = Rule::parse($parserState); } if ($oRule instanceof Rule) { $oRuleSet->addRule($oRule); } } - $oParserState->consume('}'); + $parserState->consume('}'); } /** diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index 0cb3488b..ac0d14f1 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -40,14 +40,14 @@ public function __construct($sName, $aArguments, $sSeparator = ',', $lineNumber * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ - public static function parse(ParserState $oParserState, bool $bIgnoreCase = false): CSSFunction + public static function parse(ParserState $parserState, bool $bIgnoreCase = false): CSSFunction { - $sName = self::parseName($oParserState, $bIgnoreCase); - $oParserState->consume('('); - $mArguments = self::parseArguments($oParserState); + $sName = self::parseName($parserState, $bIgnoreCase); + $parserState->consume('('); + $mArguments = self::parseArguments($parserState); - $oResult = new CSSFunction($sName, $mArguments, ',', $oParserState->currentLine()); - $oParserState->consume(')'); + $oResult = new CSSFunction($sName, $mArguments, ',', $parserState->currentLine()); + $parserState->consume(')'); return $oResult; } @@ -57,9 +57,9 @@ public static function parse(ParserState $oParserState, bool $bIgnoreCase = fals * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ - private static function parseName(ParserState $oParserState, bool $bIgnoreCase = false): string + private static function parseName(ParserState $parserState, bool $bIgnoreCase = false): string { - return $oParserState->parseIdentifier($bIgnoreCase); + return $parserState->parseIdentifier($bIgnoreCase); } /** @@ -69,9 +69,9 @@ private static function parseName(ParserState $oParserState, bool $bIgnoreCase = * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ - private static function parseArguments(ParserState $oParserState) + private static function parseArguments(ParserState $parserState) { - return Value::parseValue($oParserState, ['=', ' ', ',']); + return Value::parseValue($parserState, ['=', ' ', ',']); } /** diff --git a/src/Value/CSSString.php b/src/Value/CSSString.php index 64bd4091..a1ca1c70 100644 --- a/src/Value/CSSString.php +++ b/src/Value/CSSString.php @@ -37,9 +37,9 @@ public function __construct($sString, $lineNumber = 0) * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ - public static function parse(ParserState $oParserState): CSSString + public static function parse(ParserState $parserState): CSSString { - $sBegin = $oParserState->peek(); + $sBegin = $parserState->peek(); $sQuote = null; if ($sBegin === "'") { $sQuote = "'"; @@ -47,29 +47,29 @@ public static function parse(ParserState $oParserState): CSSString $sQuote = '"'; } if ($sQuote !== null) { - $oParserState->consume($sQuote); + $parserState->consume($sQuote); } $sResult = ''; $sContent = null; if ($sQuote === null) { // Unquoted strings end in whitespace or with braces, brackets, parentheses - while (\preg_match('/[\\s{}()<>\\[\\]]/isu', $oParserState->peek()) !== 1) { - $sResult .= $oParserState->parseCharacter(false); + while (\preg_match('/[\\s{}()<>\\[\\]]/isu', $parserState->peek()) !== 1) { + $sResult .= $parserState->parseCharacter(false); } } else { - while (!$oParserState->comes($sQuote)) { - $sContent = $oParserState->parseCharacter(false); + while (!$parserState->comes($sQuote)) { + $sContent = $parserState->parseCharacter(false); if ($sContent === null) { throw new SourceException( - "Non-well-formed quoted string {$oParserState->peek(3)}", - $oParserState->currentLine() + "Non-well-formed quoted string {$parserState->peek(3)}", + $parserState->currentLine() ); } $sResult .= $sContent; } - $oParserState->consume($sQuote); + $parserState->consume($sQuote); } - return new CSSString($sResult, $oParserState->currentLine()); + return new CSSString($sResult, $parserState->currentLine()); } /** diff --git a/src/Value/CalcFunction.php b/src/Value/CalcFunction.php index 0a7e8275..8288c7cd 100644 --- a/src/Value/CalcFunction.php +++ b/src/Value/CalcFunction.php @@ -24,80 +24,80 @@ class CalcFunction extends CSSFunction * @throws UnexpectedTokenException * @throws UnexpectedEOFException */ - public static function parse(ParserState $oParserState, bool $bIgnoreCase = false): CSSFunction + public static function parse(ParserState $parserState, bool $bIgnoreCase = false): CSSFunction { $aOperators = ['+', '-', '*', '/']; - $sFunction = $oParserState->parseIdentifier(); - if ($oParserState->peek() != '(') { + $sFunction = $parserState->parseIdentifier(); + if ($parserState->peek() != '(') { // Found ; or end of line before an opening bracket - throw new UnexpectedTokenException('(', $oParserState->peek(), 'literal', $oParserState->currentLine()); + throw new UnexpectedTokenException('(', $parserState->peek(), 'literal', $parserState->currentLine()); } elseif (!\in_array($sFunction, ['calc', '-moz-calc', '-webkit-calc'], true)) { // Found invalid calc definition. Example calc (... - throw new UnexpectedTokenException('calc', $sFunction, 'literal', $oParserState->currentLine()); + throw new UnexpectedTokenException('calc', $sFunction, 'literal', $parserState->currentLine()); } - $oParserState->consume('('); - $oCalcList = new CalcRuleValueList($oParserState->currentLine()); - $oList = new RuleValueList(',', $oParserState->currentLine()); + $parserState->consume('('); + $oCalcList = new CalcRuleValueList($parserState->currentLine()); + $oList = new RuleValueList(',', $parserState->currentLine()); $iNestingLevel = 0; $iLastComponentType = null; - while (!$oParserState->comes(')') || $iNestingLevel > 0) { - if ($oParserState->isEnd() && $iNestingLevel === 0) { + while (!$parserState->comes(')') || $iNestingLevel > 0) { + if ($parserState->isEnd() && $iNestingLevel === 0) { break; } - $oParserState->consumeWhiteSpace(); - if ($oParserState->comes('(')) { + $parserState->consumeWhiteSpace(); + if ($parserState->comes('(')) { $iNestingLevel++; - $oCalcList->addListComponent($oParserState->consume(1)); - $oParserState->consumeWhiteSpace(); + $oCalcList->addListComponent($parserState->consume(1)); + $parserState->consumeWhiteSpace(); continue; - } elseif ($oParserState->comes(')')) { + } elseif ($parserState->comes(')')) { $iNestingLevel--; - $oCalcList->addListComponent($oParserState->consume(1)); - $oParserState->consumeWhiteSpace(); + $oCalcList->addListComponent($parserState->consume(1)); + $parserState->consumeWhiteSpace(); continue; } if ($iLastComponentType != CalcFunction::T_OPERAND) { - $oVal = Value::parsePrimitiveValue($oParserState); + $oVal = Value::parsePrimitiveValue($parserState); $oCalcList->addListComponent($oVal); $iLastComponentType = CalcFunction::T_OPERAND; } else { - if (\in_array($oParserState->peek(), $aOperators, true)) { - if (($oParserState->comes('-') || $oParserState->comes('+'))) { + if (\in_array($parserState->peek(), $aOperators, true)) { + if (($parserState->comes('-') || $parserState->comes('+'))) { if ( - $oParserState->peek(1, -1) != ' ' - || !($oParserState->comes('- ') - || $oParserState->comes('+ ')) + $parserState->peek(1, -1) != ' ' + || !($parserState->comes('- ') + || $parserState->comes('+ ')) ) { throw new UnexpectedTokenException( - " {$oParserState->peek()} ", - $oParserState->peek(1, -1) . $oParserState->peek(2), + " {$parserState->peek()} ", + $parserState->peek(1, -1) . $parserState->peek(2), 'literal', - $oParserState->currentLine() + $parserState->currentLine() ); } } - $oCalcList->addListComponent($oParserState->consume(1)); + $oCalcList->addListComponent($parserState->consume(1)); $iLastComponentType = CalcFunction::T_OPERATOR; } else { throw new UnexpectedTokenException( \sprintf( 'Next token was expected to be an operand of type %s. Instead "%s" was found.', \implode(', ', $aOperators), - $oParserState->peek() + $parserState->peek() ), '', 'custom', - $oParserState->currentLine() + $parserState->currentLine() ); } } - $oParserState->consumeWhiteSpace(); + $parserState->consumeWhiteSpace(); } $oList->addListComponent($oCalcList); - if (!$oParserState->isEnd()) { - $oParserState->consume(')'); + if (!$parserState->isEnd()) { + $parserState->consume(')'); } - return new CalcFunction($sFunction, $oList, ',', $oParserState->currentLine()); + return new CalcFunction($sFunction, $oList, ',', $parserState->currentLine()); } } diff --git a/src/Value/LineName.php b/src/Value/LineName.php index 9011acad..18a53333 100644 --- a/src/Value/LineName.php +++ b/src/Value/LineName.php @@ -24,27 +24,27 @@ public function __construct(array $aComponents = [], $lineNumber = 0) * @throws UnexpectedTokenException * @throws UnexpectedEOFException */ - public static function parse(ParserState $oParserState): LineName + public static function parse(ParserState $parserState): LineName { - $oParserState->consume('['); - $oParserState->consumeWhiteSpace(); + $parserState->consume('['); + $parserState->consumeWhiteSpace(); $aNames = []; do { - if ($oParserState->getSettings()->bLenientParsing) { + if ($parserState->getSettings()->bLenientParsing) { try { - $aNames[] = $oParserState->parseIdentifier(); + $aNames[] = $parserState->parseIdentifier(); } catch (UnexpectedTokenException $e) { - if (!$oParserState->comes(']')) { + if (!$parserState->comes(']')) { throw $e; } } } else { - $aNames[] = $oParserState->parseIdentifier(); + $aNames[] = $parserState->parseIdentifier(); } - $oParserState->consumeWhiteSpace(); - } while (!$oParserState->comes(']')); - $oParserState->consume(']'); - return new LineName($aNames, $oParserState->currentLine()); + $parserState->consumeWhiteSpace(); + } while (!$parserState->comes(']')); + $parserState->consume(']'); + return new LineName($aNames, $parserState->currentLine()); } public function __toString(): string diff --git a/src/Value/Size.php b/src/Value/Size.php index bf7ce6b8..82fdc261 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -87,39 +87,39 @@ public function __construct($fSize, $sUnit = null, $bIsColorComponent = false, $ * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ - public static function parse(ParserState $oParserState, $bIsColorComponent = false): Size + public static function parse(ParserState $parserState, $bIsColorComponent = false): Size { $sSize = ''; - if ($oParserState->comes('-')) { - $sSize .= $oParserState->consume('-'); + if ($parserState->comes('-')) { + $sSize .= $parserState->consume('-'); } - while (\is_numeric($oParserState->peek()) || $oParserState->comes('.') || $oParserState->comes('e', true)) { - if ($oParserState->comes('.')) { - $sSize .= $oParserState->consume('.'); - } elseif ($oParserState->comes('e', true)) { - $sLookahead = $oParserState->peek(1, 1); + while (\is_numeric($parserState->peek()) || $parserState->comes('.') || $parserState->comes('e', true)) { + if ($parserState->comes('.')) { + $sSize .= $parserState->consume('.'); + } elseif ($parserState->comes('e', true)) { + $sLookahead = $parserState->peek(1, 1); if (\is_numeric($sLookahead) || $sLookahead === '+' || $sLookahead === '-') { - $sSize .= $oParserState->consume(2); + $sSize .= $parserState->consume(2); } else { break; // Reached the unit part of the number like "em" or "ex" } } else { - $sSize .= $oParserState->consume(1); + $sSize .= $parserState->consume(1); } } $sUnit = null; $aSizeUnits = self::getSizeUnits(); foreach ($aSizeUnits as $iLength => &$aValues) { - $sKey = \strtolower($oParserState->peek($iLength)); + $sKey = \strtolower($parserState->peek($iLength)); if (\array_key_exists($sKey, $aValues)) { if (($sUnit = $aValues[$sKey]) !== null) { - $oParserState->consume($iLength); + $parserState->consume($iLength); break; } } } - return new Size((float) $sSize, $sUnit, $bIsColorComponent, $oParserState->currentLine()); + return new Size((float) $sSize, $sUnit, $bIsColorComponent, $parserState->currentLine()); } /** diff --git a/src/Value/URL.php b/src/Value/URL.php index ef180eab..cefaef53 100644 --- a/src/Value/URL.php +++ b/src/Value/URL.php @@ -34,29 +34,29 @@ public function __construct(CSSString $oURL, $lineNumber = 0) * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ - public static function parse(ParserState $oParserState): URL + public static function parse(ParserState $parserState): URL { - $oAnchor = $oParserState->anchor(); + $oAnchor = $parserState->anchor(); $sIdentifier = ''; for ($i = 0; $i < 3; $i++) { - $sChar = $oParserState->parseCharacter(true); + $sChar = $parserState->parseCharacter(true); if ($sChar === null) { break; } $sIdentifier .= $sChar; } - $bUseUrl = $oParserState->streql($sIdentifier, 'url'); + $bUseUrl = $parserState->streql($sIdentifier, 'url'); if ($bUseUrl) { - $oParserState->consumeWhiteSpace(); - $oParserState->consume('('); + $parserState->consumeWhiteSpace(); + $parserState->consume('('); } else { $oAnchor->backtrack(); } - $oParserState->consumeWhiteSpace(); - $oResult = new URL(CSSString::parse($oParserState), $oParserState->currentLine()); + $parserState->consumeWhiteSpace(); + $oResult = new URL(CSSString::parse($parserState), $parserState->currentLine()); if ($bUseUrl) { - $oParserState->consumeWhiteSpace(); - $oParserState->consume(')'); + $parserState->consumeWhiteSpace(); + $parserState->consume(')'); } return $oResult; } diff --git a/src/Value/Value.php b/src/Value/Value.php index 9c8ad164..b11c1085 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -37,24 +37,24 @@ public function __construct($lineNumber = 0) * @throws UnexpectedTokenException * @throws UnexpectedEOFException */ - public static function parseValue(ParserState $oParserState, array $aListDelimiters = []) + public static function parseValue(ParserState $parserState, array $aListDelimiters = []) { /** @var array $aStack */ $aStack = []; - $oParserState->consumeWhiteSpace(); + $parserState->consumeWhiteSpace(); //Build a list of delimiters and parsed values while ( - !($oParserState->comes('}') || $oParserState->comes(';') || $oParserState->comes('!') - || $oParserState->comes(')') - || $oParserState->comes('\\') - || $oParserState->isEnd()) + !($parserState->comes('}') || $parserState->comes(';') || $parserState->comes('!') + || $parserState->comes(')') + || $parserState->comes('\\') + || $parserState->isEnd()) ) { if (\count($aStack) > 0) { $bFoundDelimiter = false; foreach ($aListDelimiters as $sDelimiter) { - if ($oParserState->comes($sDelimiter)) { - \array_push($aStack, $oParserState->consume($sDelimiter)); - $oParserState->consumeWhiteSpace(); + if ($parserState->comes($sDelimiter)) { + \array_push($aStack, $parserState->consume($sDelimiter)); + $parserState->consumeWhiteSpace(); $bFoundDelimiter = true; break; } @@ -64,8 +64,8 @@ public static function parseValue(ParserState $oParserState, array $aListDelimit \array_push($aStack, ' '); } } - \array_push($aStack, self::parsePrimitiveValue($oParserState)); - $oParserState->consumeWhiteSpace(); + \array_push($aStack, self::parsePrimitiveValue($parserState)); + $parserState->consumeWhiteSpace(); } // Convert the list to list objects foreach ($aListDelimiters as $sDelimiter) { @@ -85,7 +85,7 @@ public static function parseValue(ParserState $oParserState, array $aListDelimit break; } } - $oList = new RuleValueList($sDelimiter, $oParserState->currentLine()); + $oList = new RuleValueList($sDelimiter, $parserState->currentLine()); for ($i = $iStartPosition; $i - $iStartPosition < $iLength * 2; $i += 2) { $oList->addListComponent($aStack[$i]); } @@ -96,10 +96,10 @@ public static function parseValue(ParserState $oParserState, array $aListDelimit } if (!isset($aStack[0])) { throw new UnexpectedTokenException( - " {$oParserState->peek()} ", - $oParserState->peek(1, -1) . $oParserState->peek(2), + " {$parserState->peek()} ", + $parserState->peek(1, -1) . $parserState->peek(2), 'literal', - $oParserState->currentLine() + $parserState->currentLine() ); } return $aStack[0]; @@ -113,23 +113,23 @@ public static function parseValue(ParserState $oParserState, array $aListDelimit * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ - public static function parseIdentifierOrFunction(ParserState $oParserState, $bIgnoreCase = false) + public static function parseIdentifierOrFunction(ParserState $parserState, $bIgnoreCase = false) { - $oAnchor = $oParserState->anchor(); - $mResult = $oParserState->parseIdentifier($bIgnoreCase); + $oAnchor = $parserState->anchor(); + $mResult = $parserState->parseIdentifier($bIgnoreCase); - if ($oParserState->comes('(')) { + if ($parserState->comes('(')) { $oAnchor->backtrack(); - if ($oParserState->streql('url', $mResult)) { - $mResult = URL::parse($oParserState); + if ($parserState->streql('url', $mResult)) { + $mResult = URL::parse($parserState); } elseif ( - $oParserState->streql('calc', $mResult) - || $oParserState->streql('-webkit-calc', $mResult) - || $oParserState->streql('-moz-calc', $mResult) + $parserState->streql('calc', $mResult) + || $parserState->streql('-webkit-calc', $mResult) + || $parserState->streql('-moz-calc', $mResult) ) { - $mResult = CalcFunction::parse($oParserState); + $mResult = CalcFunction::parse($parserState); } else { - $mResult = CSSFunction::parse($oParserState, $bIgnoreCase); + $mResult = CSSFunction::parse($parserState, $bIgnoreCase); } } @@ -143,40 +143,40 @@ public static function parseIdentifierOrFunction(ParserState $oParserState, $bIg * @throws UnexpectedTokenException * @throws SourceException */ - public static function parsePrimitiveValue(ParserState $oParserState) + public static function parsePrimitiveValue(ParserState $parserState) { $oValue = null; - $oParserState->consumeWhiteSpace(); + $parserState->consumeWhiteSpace(); if ( - \is_numeric($oParserState->peek()) - || ($oParserState->comes('-.') - && \is_numeric($oParserState->peek(1, 2))) - || (($oParserState->comes('-') || $oParserState->comes('.')) && \is_numeric($oParserState->peek(1, 1))) + \is_numeric($parserState->peek()) + || ($parserState->comes('-.') + && \is_numeric($parserState->peek(1, 2))) + || (($parserState->comes('-') || $parserState->comes('.')) && \is_numeric($parserState->peek(1, 1))) ) { - $oValue = Size::parse($oParserState); - } elseif ($oParserState->comes('#') || $oParserState->comes('rgb', true) || $oParserState->comes('hsl', true)) { - $oValue = Color::parse($oParserState); - } elseif ($oParserState->comes("'") || $oParserState->comes('"')) { - $oValue = CSSString::parse($oParserState); - } elseif ($oParserState->comes('progid:') && $oParserState->getSettings()->bLenientParsing) { - $oValue = self::parseMicrosoftFilter($oParserState); - } elseif ($oParserState->comes('[')) { - $oValue = LineName::parse($oParserState); - } elseif ($oParserState->comes('U+')) { - $oValue = self::parseUnicodeRangeValue($oParserState); + $oValue = Size::parse($parserState); + } elseif ($parserState->comes('#') || $parserState->comes('rgb', true) || $parserState->comes('hsl', true)) { + $oValue = Color::parse($parserState); + } elseif ($parserState->comes("'") || $parserState->comes('"')) { + $oValue = CSSString::parse($parserState); + } elseif ($parserState->comes('progid:') && $parserState->getSettings()->bLenientParsing) { + $oValue = self::parseMicrosoftFilter($parserState); + } elseif ($parserState->comes('[')) { + $oValue = LineName::parse($parserState); + } elseif ($parserState->comes('U+')) { + $oValue = self::parseUnicodeRangeValue($parserState); } else { - $sNextChar = $oParserState->peek(1); + $sNextChar = $parserState->peek(1); try { - $oValue = self::parseIdentifierOrFunction($oParserState); + $oValue = self::parseIdentifierOrFunction($parserState); } catch (UnexpectedTokenException $e) { if (\in_array($sNextChar, ['+', '-', '*', '/'], true)) { - $oValue = $oParserState->consume(1); + $oValue = $parserState->consume(1); } else { throw $e; } } } - $oParserState->consumeWhiteSpace(); + $parserState->consumeWhiteSpace(); return $oValue; } @@ -184,28 +184,28 @@ public static function parsePrimitiveValue(ParserState $oParserState) * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ - private static function parseMicrosoftFilter(ParserState $oParserState): CSSFunction + private static function parseMicrosoftFilter(ParserState $parserState): CSSFunction { - $sFunction = $oParserState->consumeUntil('(', false, true); - $aArguments = Value::parseValue($oParserState, [',', '=']); - return new CSSFunction($sFunction, $aArguments, ',', $oParserState->currentLine()); + $sFunction = $parserState->consumeUntil('(', false, true); + $aArguments = Value::parseValue($parserState, [',', '=']); + return new CSSFunction($sFunction, $aArguments, ',', $parserState->currentLine()); } /** * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ - private static function parseUnicodeRangeValue(ParserState $oParserState): string + private static function parseUnicodeRangeValue(ParserState $parserState): string { $iCodepointMaxLength = 6; // Code points outside BMP can use up to six digits $sRange = ''; - $oParserState->consume('U+'); + $parserState->consume('U+'); do { - if ($oParserState->comes('-')) { + if ($parserState->comes('-')) { $iCodepointMaxLength = 13; // Max length is 2 six-digit code points + the dash(-) between them } - $sRange .= $oParserState->consume(1); - } while (\strlen($sRange) < $iCodepointMaxLength && \preg_match('/[A-Fa-f0-9\\?-]/', $oParserState->peek())); + $sRange .= $parserState->consume(1); + } while (\strlen($sRange) < $iCodepointMaxLength && \preg_match('/[A-Fa-f0-9\\?-]/', $parserState->peek())); return "U+{$sRange}"; } From e740aacabb3c4de684bafd9b2203ca2af72ff7b5 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 27 Jan 2025 23:18:17 +0100 Subject: [PATCH 124/555] [TASK] Move `SizeTest` to unit tests (#836) Also make an anonymous function in the test static. Part of #758. --- tests/{ => Unit}/Value/SizeTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename tests/{ => Unit}/Value/SizeTest.php (93%) diff --git a/tests/Value/SizeTest.php b/tests/Unit/Value/SizeTest.php similarity index 93% rename from tests/Value/SizeTest.php rename to tests/Unit/Value/SizeTest.php index d4c5438a..51da2838 100644 --- a/tests/Value/SizeTest.php +++ b/tests/Unit/Value/SizeTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Sabberworm\CSS\Tests\Value; +namespace Sabberworm\CSS\Tests\Unit\Value; use PHPUnit\Framework\TestCase; use Sabberworm\CSS\Parsing\ParserState; @@ -53,7 +53,7 @@ public static function provideUnit(): array return \array_combine( $units, \array_map( - function (string $unit): array { + static function (string $unit): array { return [$unit]; }, $units From 5ee1d9ab65d04b9f43342af958c42b7e01725842 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 27 Jan 2025 23:20:31 +0100 Subject: [PATCH 125/555] [TASK] Move `ValueTest` to unit tests (#837) Also make an anonymous function in the test static. Also fix a type in an annotation in the test. Part of #758. --- tests/{ => Unit}/Value/ValueTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename tests/{ => Unit}/Value/ValueTest.php (95%) diff --git a/tests/Value/ValueTest.php b/tests/Unit/Value/ValueTest.php similarity index 95% rename from tests/Value/ValueTest.php rename to tests/Unit/Value/ValueTest.php index a67cbe81..d96485fa 100644 --- a/tests/Value/ValueTest.php +++ b/tests/Unit/Value/ValueTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Sabberworm\CSS\Tests\Value; +namespace Sabberworm\CSS\Tests\Unit\Value; use PHPUnit\Framework\TestCase; use Sabberworm\CSS\Parsing\ParserState; @@ -17,7 +17,7 @@ final class ValueTest extends TestCase /** * the default set of delimiters for parsing most values * - * @see \Rule\Rule::listDelimiterForRule + * @see \Sabberworm\CSS\Rule\Rule::listDelimiterForRule * * @var array */ @@ -33,7 +33,7 @@ public static function provideArithmeticOperator(): array return \array_combine( $units, \array_map( - function (string $unit): array { + static function (string $unit): array { return [$unit]; }, $units From 1d7608804484599737e6af85574f38d4bc2885a1 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 27 Jan 2025 23:24:20 +0100 Subject: [PATCH 126/555] [TASK] Drop expansion of shorthand properties (#838) Those were deprecated in version 8.7.0. Fixes #511 --- CHANGELOG.md | 1 + src/CSSList/Document.php | 24 - src/RuleSet/DeclarationBlock.php | 627 ------------------------- tests/ParserTest.php | 41 -- tests/RuleSet/DeclarationBlockTest.php | 347 -------------- tests/fixtures/create-shorthands.css | 6 - tests/fixtures/expand-shorthands.css | 7 - 7 files changed, 1 insertion(+), 1052 deletions(-) delete mode 100644 tests/fixtures/create-shorthands.css delete mode 100644 tests/fixtures/expand-shorthands.css diff --git a/CHANGELOG.md b/CHANGELOG.md index 113ffb4c..9ca3c21c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ Please also have a look at our ### Removed +- Remove expansion of shorthand properties (#838) - Remove `Parser::setCharset/getCharset` (#808) - Remove `Rule::getValues()` (#582) - Remove `Rule::setValues()` (#562) diff --git a/src/CSSList/Document.php b/src/CSSList/Document.php index d577ffac..8d855409 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -111,30 +111,6 @@ public function getSelectorsBySpecificity($sSpecificitySearch = null): array return $aResult; } - /** - * Expands all shorthand properties to their long value. - * - * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 - */ - public function expandShorthands(): void - { - foreach ($this->getAllDeclarationBlocks() as $oDeclaration) { - $oDeclaration->expandShorthands(); - } - } - - /** - * Create shorthands properties whenever possible. - * - * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 - */ - public function createShorthands(): void - { - foreach ($this->getAllDeclarationBlocks() as $oDeclaration) { - $oDeclaration->createShorthands(); - } - } - /** * Overrides `render()` to make format argument optional. * diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 76ead48d..72a37304 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -13,12 +13,6 @@ use Sabberworm\CSS\Parsing\UnexpectedTokenException; use Sabberworm\CSS\Property\KeyframeSelector; use Sabberworm\CSS\Property\Selector; -use Sabberworm\CSS\Rule\Rule; -use Sabberworm\CSS\Value\Color; -use Sabberworm\CSS\Value\RuleValueList; -use Sabberworm\CSS\Value\Size; -use Sabberworm\CSS\Value\URL; -use Sabberworm\CSS\Value\Value; /** * This class represents a `RuleSet` constrained by a `Selector`. @@ -154,627 +148,6 @@ public function getSelectors() return $this->aSelectors; } - /** - * Splits shorthand declarations (e.g. `margin` or `font`) into their constituent parts. - * - * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 - */ - public function expandShorthands(): void - { - // border must be expanded before dimensions - $this->expandBorderShorthand(); - $this->expandDimensionsShorthand(); - $this->expandFontShorthand(); - $this->expandBackgroundShorthand(); - $this->expandListStyleShorthand(); - } - - /** - * Creates shorthand declarations (e.g. `margin` or `font`) whenever possible. - * - * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 - */ - public function createShorthands(): void - { - $this->createBackgroundShorthand(); - $this->createDimensionsShorthand(); - // border must be shortened after dimensions - $this->createBorderShorthand(); - $this->createFontShorthand(); - $this->createListStyleShorthand(); - } - - /** - * Splits shorthand border declarations (e.g. `border: 1px red;`). - * - * Additional splitting happens in expandDimensionsShorthand. - * - * Multiple borders are not yet supported as of 3. - * - * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 - */ - public function expandBorderShorthand(): void - { - $aBorderRules = [ - 'border', - 'border-left', - 'border-right', - 'border-top', - 'border-bottom', - ]; - $aBorderSizes = [ - 'thin', - 'medium', - 'thick', - ]; - $aRules = $this->getRulesAssoc(); - foreach ($aBorderRules as $sBorderRule) { - if (!isset($aRules[$sBorderRule])) { - continue; - } - $oRule = $aRules[$sBorderRule]; - $mRuleValue = $oRule->getValue(); - $aValues = []; - if (!$mRuleValue instanceof RuleValueList) { - $aValues[] = $mRuleValue; - } else { - $aValues = $mRuleValue->getListComponents(); - } - foreach ($aValues as $mValue) { - if ($mValue instanceof Value) { - $mNewValue = clone $mValue; - } else { - $mNewValue = $mValue; - } - if ($mValue instanceof Size) { - $sNewRuleName = $sBorderRule . '-width'; - } elseif ($mValue instanceof Color) { - $sNewRuleName = $sBorderRule . '-color'; - } else { - if (\in_array($mValue, $aBorderSizes, true)) { - $sNewRuleName = $sBorderRule . '-width'; - } else { - $sNewRuleName = $sBorderRule . '-style'; - } - } - $oNewRule = new Rule($sNewRuleName, $oRule->getLineNo(), $oRule->getColNo()); - $oNewRule->setIsImportant($oRule->getIsImportant()); - $oNewRule->addValue([$mNewValue]); - $this->addRule($oNewRule); - } - $this->removeRule($sBorderRule); - } - } - - /** - * Splits shorthand dimensional declarations (e.g. `margin: 0px auto;`) - * into their constituent parts. - * - * Handles `margin`, `padding`, `border-color`, `border-style` and `border-width`. - * - * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 - */ - public function expandDimensionsShorthand(): void - { - $aExpansions = [ - 'margin' => 'margin-%s', - 'padding' => 'padding-%s', - 'border-color' => 'border-%s-color', - 'border-style' => 'border-%s-style', - 'border-width' => 'border-%s-width', - ]; - $aRules = $this->getRulesAssoc(); - foreach ($aExpansions as $sProperty => $sExpanded) { - if (!isset($aRules[$sProperty])) { - continue; - } - $oRule = $aRules[$sProperty]; - $mRuleValue = $oRule->getValue(); - $aValues = []; - if (!$mRuleValue instanceof RuleValueList) { - $aValues[] = $mRuleValue; - } else { - $aValues = $mRuleValue->getListComponents(); - } - $top = $right = $bottom = $left = null; - switch (\count($aValues)) { - case 1: - $top = $right = $bottom = $left = $aValues[0]; - break; - case 2: - $top = $bottom = $aValues[0]; - $left = $right = $aValues[1]; - break; - case 3: - $top = $aValues[0]; - $left = $right = $aValues[1]; - $bottom = $aValues[2]; - break; - case 4: - $top = $aValues[0]; - $right = $aValues[1]; - $bottom = $aValues[2]; - $left = $aValues[3]; - break; - } - foreach (['top', 'right', 'bottom', 'left'] as $sPosition) { - $oNewRule = new Rule(\sprintf($sExpanded, $sPosition), $oRule->getLineNo(), $oRule->getColNo()); - $oNewRule->setIsImportant($oRule->getIsImportant()); - $oNewRule->addValue(${$sPosition}); - $this->addRule($oNewRule); - } - $this->removeRule($sProperty); - } - } - - /** - * Converts shorthand font declarations - * (e.g. `font: 300 italic 11px/14px verdana, helvetica, sans-serif;`) - * into their constituent parts. - * - * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 - */ - public function expandFontShorthand(): void - { - $aRules = $this->getRulesAssoc(); - if (!isset($aRules['font'])) { - return; - } - $oRule = $aRules['font']; - // reset properties to 'normal' per http://www.w3.org/TR/21/fonts.html#font-shorthand - $aFontProperties = [ - 'font-style' => 'normal', - 'font-variant' => 'normal', - 'font-weight' => 'normal', - 'font-size' => 'normal', - 'line-height' => 'normal', - ]; - $mRuleValue = $oRule->getValue(); - $aValues = []; - if (!$mRuleValue instanceof RuleValueList) { - $aValues[] = $mRuleValue; - } else { - $aValues = $mRuleValue->getListComponents(); - } - foreach ($aValues as $mValue) { - if (!$mValue instanceof Value) { - $mValue = \mb_strtolower($mValue); - } - if (\in_array($mValue, ['normal', 'inherit'], true)) { - foreach (['font-style', 'font-weight', 'font-variant'] as $sProperty) { - if (!isset($aFontProperties[$sProperty])) { - $aFontProperties[$sProperty] = $mValue; - } - } - } elseif (\in_array($mValue, ['italic', 'oblique'], true)) { - $aFontProperties['font-style'] = $mValue; - } elseif ($mValue == 'small-caps') { - $aFontProperties['font-variant'] = $mValue; - } elseif ( - \in_array($mValue, ['bold', 'bolder', 'lighter'], true) - || ($mValue instanceof Size && \in_array($mValue->getSize(), \range(100.0, 900.0, 100.0), true)) - ) { - $aFontProperties['font-weight'] = $mValue; - } elseif ($mValue instanceof RuleValueList && $mValue->getListSeparator() == '/') { - [$oSize, $oHeight] = $mValue->getListComponents(); - $aFontProperties['font-size'] = $oSize; - $aFontProperties['line-height'] = $oHeight; - } elseif ($mValue instanceof Size && $mValue->getUnit() !== null) { - $aFontProperties['font-size'] = $mValue; - } else { - $aFontProperties['font-family'] = $mValue; - } - } - foreach ($aFontProperties as $sProperty => $mValue) { - $oNewRule = new Rule($sProperty, $oRule->getLineNo(), $oRule->getColNo()); - $oNewRule->addValue($mValue); - $oNewRule->setIsImportant($oRule->getIsImportant()); - $this->addRule($oNewRule); - } - $this->removeRule('font'); - } - - /** - * Converts shorthand background declarations - * (e.g. `background: url("chess.png") gray 50% repeat fixed;`) - * into their constituent parts. - * - * @see http://www.w3.org/TR/21/colors.html#propdef-background - * - * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 - */ - public function expandBackgroundShorthand(): void - { - $aRules = $this->getRulesAssoc(); - if (!isset($aRules['background'])) { - return; - } - $oRule = $aRules['background']; - $aBgProperties = [ - 'background-color' => ['transparent'], - 'background-image' => ['none'], - 'background-repeat' => ['repeat'], - 'background-attachment' => ['scroll'], - 'background-position' => [ - new Size(0, '%', false, $this->lineNumber), - new Size(0, '%', false, $this->lineNumber), - ], - ]; - $mRuleValue = $oRule->getValue(); - $aValues = []; - if (!$mRuleValue instanceof RuleValueList) { - $aValues[] = $mRuleValue; - } else { - $aValues = $mRuleValue->getListComponents(); - } - if (\count($aValues) == 1 && $aValues[0] == 'inherit') { - foreach ($aBgProperties as $sProperty => $mValue) { - $oNewRule = new Rule($sProperty, $oRule->getLineNo(), $oRule->getColNo()); - $oNewRule->addValue('inherit'); - $oNewRule->setIsImportant($oRule->getIsImportant()); - $this->addRule($oNewRule); - } - $this->removeRule('background'); - return; - } - $iNumBgPos = 0; - foreach ($aValues as $mValue) { - if (!$mValue instanceof Value) { - $mValue = \mb_strtolower($mValue); - } - if ($mValue instanceof URL) { - $aBgProperties['background-image'] = $mValue; - } elseif ($mValue instanceof Color) { - $aBgProperties['background-color'] = $mValue; - } elseif (\in_array($mValue, ['scroll', 'fixed'], true)) { - $aBgProperties['background-attachment'] = $mValue; - } elseif (\in_array($mValue, ['repeat', 'no-repeat', 'repeat-x', 'repeat-y'], true)) { - $aBgProperties['background-repeat'] = $mValue; - } elseif ( - \in_array($mValue, ['left', 'center', 'right', 'top', 'bottom'], true) - || $mValue instanceof Size - ) { - if ($iNumBgPos == 0) { - $aBgProperties['background-position'][0] = $mValue; - $aBgProperties['background-position'][1] = 'center'; - } else { - $aBgProperties['background-position'][$iNumBgPos] = $mValue; - } - $iNumBgPos++; - } - } - foreach ($aBgProperties as $sProperty => $mValue) { - $oNewRule = new Rule($sProperty, $oRule->getLineNo(), $oRule->getColNo()); - $oNewRule->setIsImportant($oRule->getIsImportant()); - $oNewRule->addValue($mValue); - $this->addRule($oNewRule); - } - $this->removeRule('background'); - } - - /** - * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 - */ - public function expandListStyleShorthand(): void - { - $aListProperties = [ - 'list-style-type' => 'disc', - 'list-style-position' => 'outside', - 'list-style-image' => 'none', - ]; - $aListStyleTypes = [ - 'none', - 'disc', - 'circle', - 'square', - 'decimal-leading-zero', - 'decimal', - 'lower-roman', - 'upper-roman', - 'lower-greek', - 'lower-alpha', - 'lower-latin', - 'upper-alpha', - 'upper-latin', - 'hebrew', - 'armenian', - 'georgian', - 'cjk-ideographic', - 'hiragana', - 'hira-gana-iroha', - 'katakana-iroha', - 'katakana', - ]; - $aListStylePositions = [ - 'inside', - 'outside', - ]; - $aRules = $this->getRulesAssoc(); - if (!isset($aRules['list-style'])) { - return; - } - $oRule = $aRules['list-style']; - $mRuleValue = $oRule->getValue(); - $aValues = []; - if (!$mRuleValue instanceof RuleValueList) { - $aValues[] = $mRuleValue; - } else { - $aValues = $mRuleValue->getListComponents(); - } - if (\count($aValues) == 1 && $aValues[0] == 'inherit') { - foreach ($aListProperties as $sProperty => $mValue) { - $oNewRule = new Rule($sProperty, $oRule->getLineNo(), $oRule->getColNo()); - $oNewRule->addValue('inherit'); - $oNewRule->setIsImportant($oRule->getIsImportant()); - $this->addRule($oNewRule); - } - $this->removeRule('list-style'); - return; - } - foreach ($aValues as $mValue) { - if (!$mValue instanceof Value) { - $mValue = \mb_strtolower($mValue); - } - if ($mValue instanceof Url) { - $aListProperties['list-style-image'] = $mValue; - } elseif (\in_array($mValue, $aListStyleTypes, true)) { - $aListProperties['list-style-types'] = $mValue; - } elseif (\in_array($mValue, $aListStylePositions, true)) { - $aListProperties['list-style-position'] = $mValue; - } - } - foreach ($aListProperties as $sProperty => $mValue) { - $oNewRule = new Rule($sProperty, $oRule->getLineNo(), $oRule->getColNo()); - $oNewRule->setIsImportant($oRule->getIsImportant()); - $oNewRule->addValue($mValue); - $this->addRule($oNewRule); - } - $this->removeRule('list-style'); - } - - /** - * @param array $aProperties - * @param string $sShorthand - * - * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 - */ - public function createShorthandProperties(array $aProperties, $sShorthand): void - { - $aRules = $this->getRulesAssoc(); - $oRule = null; - $aNewValues = []; - foreach ($aProperties as $sProperty) { - if (!isset($aRules[$sProperty])) { - continue; - } - $oRule = $aRules[$sProperty]; - if (!$oRule->getIsImportant()) { - $mRuleValue = $oRule->getValue(); - $aValues = []; - if (!$mRuleValue instanceof RuleValueList) { - $aValues[] = $mRuleValue; - } else { - $aValues = $mRuleValue->getListComponents(); - } - foreach ($aValues as $mValue) { - $aNewValues[] = $mValue; - } - $this->removeRule($sProperty); - } - } - if ($aNewValues !== [] && $oRule instanceof Rule) { - $oNewRule = new Rule($sShorthand, $oRule->getLineNo(), $oRule->getColNo()); - foreach ($aNewValues as $mValue) { - $oNewRule->addValue($mValue); - } - $this->addRule($oNewRule); - } - } - - /** - * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 - */ - public function createBackgroundShorthand(): void - { - $aProperties = [ - 'background-color', - 'background-image', - 'background-repeat', - 'background-position', - 'background-attachment', - ]; - $this->createShorthandProperties($aProperties, 'background'); - } - - /** - * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 - */ - public function createListStyleShorthand(): void - { - $aProperties = [ - 'list-style-type', - 'list-style-position', - 'list-style-image', - ]; - $this->createShorthandProperties($aProperties, 'list-style'); - } - - /** - * Combines `border-color`, `border-style` and `border-width` into `border`. - * - * Should be run after `create_dimensions_shorthand`! - * - * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 - */ - public function createBorderShorthand(): void - { - $aProperties = [ - 'border-width', - 'border-style', - 'border-color', - ]; - $this->createShorthandProperties($aProperties, 'border'); - } - - /** - * Looks for long format CSS dimensional properties - * (margin, padding, border-color, border-style and border-width) - * and converts them into shorthand CSS properties. - * - * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 - */ - public function createDimensionsShorthand(): void - { - $aPositions = ['top', 'right', 'bottom', 'left']; - $aExpansions = [ - 'margin' => 'margin-%s', - 'padding' => 'padding-%s', - 'border-color' => 'border-%s-color', - 'border-style' => 'border-%s-style', - 'border-width' => 'border-%s-width', - ]; - $aRules = $this->getRulesAssoc(); - foreach ($aExpansions as $sProperty => $sExpanded) { - $aFoldable = []; - foreach ($aRules as $sRuleName => $oRule) { - foreach ($aPositions as $sPosition) { - if ($sRuleName == \sprintf($sExpanded, $sPosition)) { - $aFoldable[$sRuleName] = $oRule; - } - } - } - // All four dimensions must be present - if (\count($aFoldable) == 4) { - $aValues = []; - foreach ($aPositions as $sPosition) { - $oRule = $aRules[\sprintf($sExpanded, $sPosition)]; - $mRuleValue = $oRule->getValue(); - $aRuleValues = []; - if (!$mRuleValue instanceof RuleValueList) { - $aRuleValues[] = $mRuleValue; - } else { - $aRuleValues = $mRuleValue->getListComponents(); - } - $aValues[$sPosition] = $aRuleValues; - } - $oNewRule = new Rule($sProperty, $oRule->getLineNo(), $oRule->getColNo()); - if ((string) $aValues['left'][0] == (string) $aValues['right'][0]) { - if ((string) $aValues['top'][0] == (string) $aValues['bottom'][0]) { - if ((string) $aValues['top'][0] == (string) $aValues['left'][0]) { - // All 4 sides are equal - $oNewRule->addValue($aValues['top']); - } else { - // Top and bottom are equal, left and right are equal - $oNewRule->addValue($aValues['top']); - $oNewRule->addValue($aValues['left']); - } - } else { - // Only left and right are equal - $oNewRule->addValue($aValues['top']); - $oNewRule->addValue($aValues['left']); - $oNewRule->addValue($aValues['bottom']); - } - } else { - // No sides are equal - $oNewRule->addValue($aValues['top']); - $oNewRule->addValue($aValues['left']); - $oNewRule->addValue($aValues['bottom']); - $oNewRule->addValue($aValues['right']); - } - $this->addRule($oNewRule); - foreach ($aPositions as $sPosition) { - $this->removeRule(\sprintf($sExpanded, $sPosition)); - } - } - } - } - - /** - * Looks for long format CSS font properties (e.g. `font-weight`) and - * tries to convert them into a shorthand CSS `font` property. - * - * At least `font-size` AND `font-family` must be present in order to create a shorthand declaration. - * - * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 - */ - public function createFontShorthand(): void - { - $aFontProperties = [ - 'font-style', - 'font-variant', - 'font-weight', - 'font-size', - 'line-height', - 'font-family', - ]; - $aRules = $this->getRulesAssoc(); - if (!isset($aRules['font-size']) || !isset($aRules['font-family'])) { - return; - } - $oOldRule = $aRules['font-size'] ?? $aRules['font-family']; - $oNewRule = new Rule('font', $oOldRule->getLineNo(), $oOldRule->getColNo()); - unset($oOldRule); - foreach (['font-style', 'font-variant', 'font-weight'] as $sProperty) { - if (isset($aRules[$sProperty])) { - $oRule = $aRules[$sProperty]; - $mRuleValue = $oRule->getValue(); - $aValues = []; - if (!$mRuleValue instanceof RuleValueList) { - $aValues[] = $mRuleValue; - } else { - $aValues = $mRuleValue->getListComponents(); - } - if ($aValues[0] !== 'normal') { - $oNewRule->addValue($aValues[0]); - } - } - } - // Get the font-size value - $oRule = $aRules['font-size']; - $mRuleValue = $oRule->getValue(); - $aFSValues = []; - if (!$mRuleValue instanceof RuleValueList) { - $aFSValues[] = $mRuleValue; - } else { - $aFSValues = $mRuleValue->getListComponents(); - } - // But wait to know if we have line-height to add it - if (isset($aRules['line-height'])) { - $oRule = $aRules['line-height']; - $mRuleValue = $oRule->getValue(); - $aLHValues = []; - if (!$mRuleValue instanceof RuleValueList) { - $aLHValues[] = $mRuleValue; - } else { - $aLHValues = $mRuleValue->getListComponents(); - } - if ($aLHValues[0] !== 'normal') { - $val = new RuleValueList('/', $this->lineNumber); - $val->addListComponent($aFSValues[0]); - $val->addListComponent($aLHValues[0]); - $oNewRule->addValue($val); - } - } else { - $oNewRule->addValue($aFSValues[0]); - } - $oRule = $aRules['font-family']; - $mRuleValue = $oRule->getValue(); - $aFFValues = []; - if (!$mRuleValue instanceof RuleValueList) { - $aFFValues[] = $mRuleValue; - } else { - $aFFValues = $mRuleValue->getListComponents(); - } - $oFFValue = new RuleValueList(',', $this->lineNumber); - $oFFValue->setListComponents($aFFValues); - $oNewRule->addValue($oFFValue); - - $this->addRule($oNewRule); - foreach ($aFontProperties as $sProperty) { - $this->removeRule($sProperty); - } - } - /** * @throws OutputException */ diff --git a/tests/ParserTest.php b/tests/ParserTest.php index ae91bf1d..fbff74e8 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -492,47 +492,6 @@ public function functionSyntax(): void self::assertSame($expected, $document->render()); } - /** - * @test - */ - public function expandShorthands(): void - { - $document = self::parsedStructureForFile('expand-shorthands'); - $expected = 'body {font: italic 500 14px/1.618 "Trebuchet MS",Georgia,serif;border: 2px solid #f0f;' - . 'background: #ccc url("/images/foo.png") no-repeat left top;margin: 1em !important;' - . 'padding: 2px 6px 3px;}'; - self::assertSame($expected, $document->render()); - $document->expandShorthands(); - $expected = 'body {margin-top: 1em !important;margin-right: 1em !important;margin-bottom: 1em !important;' - . 'margin-left: 1em !important;padding-top: 2px;padding-right: 6px;padding-bottom: 3px;' - . 'padding-left: 6px;border-top-color: #f0f;border-right-color: #f0f;border-bottom-color: #f0f;' - . 'border-left-color: #f0f;border-top-style: solid;border-right-style: solid;' - . 'border-bottom-style: solid;border-left-style: solid;border-top-width: 2px;' - . 'border-right-width: 2px;border-bottom-width: 2px;border-left-width: 2px;font-style: italic;' - . 'font-variant: normal;font-weight: 500;font-size: 14px;line-height: 1.618;' - . '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($expected, $document->render()); - } - - /** - * @test - */ - public function createShorthands(): void - { - $document = self::parsedStructureForFile('create-shorthands'); - $expected = 'body {font-size: 2em;font-family: Helvetica,Arial,sans-serif;font-weight: bold;' - . 'border-width: 2px;border-color: #999;border-style: dotted;background-color: #fff;' - . 'background-image: url("foobar.png");background-repeat: repeat-y;margin-top: 2px;margin-right: 3px;' - . 'margin-bottom: 4px;margin-left: 5px;}'; - self::assertSame($expected, $document->render()); - $document->createShorthands(); - $expected = '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($expected, $document->render()); - } - /** * @test */ diff --git a/tests/RuleSet/DeclarationBlockTest.php b/tests/RuleSet/DeclarationBlockTest.php index 15430ae9..2de33b96 100644 --- a/tests/RuleSet/DeclarationBlockTest.php +++ b/tests/RuleSet/DeclarationBlockTest.php @@ -16,325 +16,6 @@ */ final class DeclarationBlockTest extends TestCase { - /** - * @dataProvider expandBorderShorthandProvider - * - * @test - */ - public function expandBorderShorthand(string $sCss, string $sExpected): void - { - $parser = new Parser($sCss); - $document = $parser->parse(); - foreach ($document->getAllDeclarationBlocks() as $declarationBlock) { - $declarationBlock->expandBorderShorthand(); - } - self::assertSame(\trim((string) $document), $sExpected); - } - - /** - * @return array> - */ - public static function expandBorderShorthandProvider(): array - { - return [ - ['body{ border: 2px solid #000 }', 'body {border-width: 2px;border-style: solid;border-color: #000;}'], - ['body{ border: none }', 'body {border-style: none;}'], - ['body{ border: 2px }', 'body {border-width: 2px;}'], - ['body{ border: #f00 }', 'body {border-color: #f00;}'], - ['body{ border: 1em solid }', 'body {border-width: 1em;border-style: solid;}'], - ['body{ margin: 1em; }', 'body {margin: 1em;}'], - ]; - } - - /** - * @dataProvider expandFontShorthandProvider - * - * @test - */ - public function expandFontShorthand(string $sCss, string $sExpected): void - { - $parser = new Parser($sCss); - $document = $parser->parse(); - foreach ($document->getAllDeclarationBlocks() as $declarationBlock) { - $declarationBlock->expandFontShorthand(); - } - self::assertSame(\trim((string) $document), $sExpected); - } - - /** - * @return array> - */ - public static function expandFontShorthandProvider(): array - { - return [ - [ - 'body{ margin: 1em; }', - 'body {margin: 1em;}', - ], - [ - 'body {font: 12px serif;}', - 'body {font-style: normal;font-variant: normal;font-weight: normal;font-size: 12px;' - . 'line-height: normal;font-family: serif;}', - ], - [ - 'body {font: italic 12px serif;}', - 'body {font-style: italic;font-variant: normal;font-weight: normal;font-size: 12px;' - . 'line-height: normal;font-family: serif;}', - ], - [ - 'body {font: italic bold 12px serif;}', - 'body {font-style: italic;font-variant: normal;font-weight: bold;font-size: 12px;' - . 'line-height: normal;font-family: serif;}', - ], - [ - 'body {font: italic bold 12px/1.6 serif;}', - 'body {font-style: italic;font-variant: normal;font-weight: bold;font-size: 12px;' - . 'line-height: 1.6;font-family: serif;}', - ], - [ - 'body {font: italic small-caps bold 12px/1.6 serif;}', - 'body {font-style: italic;font-variant: small-caps;font-weight: bold;font-size: 12px;' - . 'line-height: 1.6;font-family: serif;}', - ], - ]; - } - - /** - * @dataProvider expandBackgroundShorthandProvider - * - * @test - */ - public function expandBackgroundShorthand(string $sCss, string $sExpected): void - { - $parser = new Parser($sCss); - $document = $parser->parse(); - foreach ($document->getAllDeclarationBlocks() as $declarationBlock) { - $declarationBlock->expandBackgroundShorthand(); - } - self::assertSame(\trim((string) $document), $sExpected); - } - - /** - * @return array> - */ - public static function expandBackgroundShorthandProvider(): array - { - return [ - ['body {border: 1px;}', 'body {border: 1px;}'], - [ - 'body {background: #f00;}', - 'body {background-color: #f00;background-image: none;background-repeat: repeat;' - . 'background-attachment: scroll;background-position: 0% 0%;}', - ], - [ - 'body {background: #f00 url("foobar.png");}', - 'body {background-color: #f00;background-image: url("foobar.png");background-repeat: repeat;' - . 'background-attachment: scroll;background-position: 0% 0%;}', - ], - [ - 'body {background: #f00 url("foobar.png") no-repeat;}', - 'body {background-color: #f00;background-image: url("foobar.png");background-repeat: no-repeat;' - . 'background-attachment: scroll;background-position: 0% 0%;}', - ], - [ - 'body {background: #f00 url("foobar.png") no-repeat center;}', - 'body {background-color: #f00;background-image: url("foobar.png");background-repeat: no-repeat;' - . 'background-attachment: scroll;background-position: center center;}', - ], - [ - 'body {background: #f00 url("foobar.png") no-repeat top left;}', - 'body {background-color: #f00;background-image: url("foobar.png");background-repeat: no-repeat;' - . 'background-attachment: scroll;background-position: top left;}', - ], - ]; - } - - /** - * @dataProvider expandDimensionsShorthandProvider - * - * @test - */ - public function expandDimensionsShorthand(string $sCss, string $sExpected): void - { - $parser = new Parser($sCss); - $document = $parser->parse(); - foreach ($document->getAllDeclarationBlocks() as $declarationBlock) { - $declarationBlock->expandDimensionsShorthand(); - } - self::assertSame(\trim((string) $document), $sExpected); - } - - /** - * @return array> - */ - public static function expandDimensionsShorthandProvider(): array - { - return [ - ['body {border: 1px;}', 'body {border: 1px;}'], - ['body {margin-top: 1px;}', 'body {margin-top: 1px;}'], - ['body {margin: 1em;}', 'body {margin-top: 1em;margin-right: 1em;margin-bottom: 1em;margin-left: 1em;}'], - [ - 'body {margin: 1em 2em;}', - 'body {margin-top: 1em;margin-right: 2em;margin-bottom: 1em;margin-left: 2em;}', - ], - [ - 'body {margin: 1em 2em 3em;}', - 'body {margin-top: 1em;margin-right: 2em;margin-bottom: 3em;margin-left: 2em;}', - ], - ]; - } - - /** - * @dataProvider createBorderShorthandProvider - * - * @test - */ - public function createBorderShorthand(string $sCss, string $sExpected): void - { - $parser = new Parser($sCss); - $document = $parser->parse(); - foreach ($document->getAllDeclarationBlocks() as $declarationBlock) { - $declarationBlock->createBorderShorthand(); - } - self::assertSame(\trim((string) $document), $sExpected); - } - - /** - * @return array> - */ - public static function createBorderShorthandProvider(): array - { - return [ - ['body {border-width: 2px;border-style: solid;border-color: #000;}', 'body {border: 2px solid #000;}'], - ['body {border-style: none;}', 'body {border: none;}'], - ['body {border-width: 1em;border-style: solid;}', 'body {border: 1em solid;}'], - ['body {margin: 1em;}', 'body {margin: 1em;}'], - ]; - } - - /** - * @dataProvider createFontShorthandProvider - * - * @test - */ - public function createFontShorthand(string $sCss, string $sExpected): void - { - $parser = new Parser($sCss); - $document = $parser->parse(); - foreach ($document->getAllDeclarationBlocks() as $declarationBlock) { - $declarationBlock->createFontShorthand(); - } - self::assertSame(\trim((string) $document), $sExpected); - } - - /** - * @return array> - */ - public static function createFontShorthandProvider(): array - { - return [ - ['body {font-size: 12px; font-family: serif}', 'body {font: 12px serif;}'], - ['body {font-size: 12px; font-family: serif; font-style: italic;}', 'body {font: italic 12px serif;}'], - [ - 'body {font-size: 12px; font-family: serif; font-style: italic; font-weight: bold;}', - 'body {font: italic bold 12px serif;}', - ], - [ - 'body {font-size: 12px; font-family: serif; font-style: italic; font-weight: bold; line-height: 1.6;}', - 'body {font: italic bold 12px/1.6 serif;}', - ], - [ - 'body {font-size: 12px; font-family: serif; font-style: italic; font-weight: bold; ' - . 'line-height: 1.6; font-variant: small-caps;}', - 'body {font: italic small-caps bold 12px/1.6 serif;}', - ], - ['body {margin: 1em;}', 'body {margin: 1em;}'], - ]; - } - - /** - * @dataProvider createDimensionsShorthandProvider - * - * @test - */ - public function createDimensionsShorthand(string $sCss, string $sExpected): void - { - $parser = new Parser($sCss); - $document = $parser->parse(); - foreach ($document->getAllDeclarationBlocks() as $declarationBlock) { - $declarationBlock->createDimensionsShorthand(); - } - self::assertSame(\trim((string) $document), $sExpected); - } - - /** - * @return array> - */ - public static function createDimensionsShorthandProvider(): array - { - return [ - ['body {border: 1px;}', 'body {border: 1px;}'], - ['body {margin-top: 1px;}', 'body {margin-top: 1px;}'], - ['body {margin-top: 1em; margin-right: 1em; margin-bottom: 1em; margin-left: 1em;}', 'body {margin: 1em;}'], - [ - 'body {margin-top: 1em; margin-right: 2em; margin-bottom: 1em; margin-left: 2em;}', - 'body {margin: 1em 2em;}', - ], - [ - 'body {margin-top: 1em; margin-right: 2em; margin-bottom: 3em; margin-left: 2em;}', - 'body {margin: 1em 2em 3em;}', - ], - ]; - } - - /** - * @dataProvider createBackgroundShorthandProvider - * - * @test - */ - public function createBackgroundShorthand(string $sCss, string $sExpected): void - { - $parser = new Parser($sCss); - $document = $parser->parse(); - foreach ($document->getAllDeclarationBlocks() as $declarationBlock) { - $declarationBlock->createBackgroundShorthand(); - } - self::assertSame(\trim((string) $document), $sExpected); - } - - /** - * @return array> - */ - public static function createBackgroundShorthandProvider(): array - { - return [ - ['body {border: 1px;}', 'body {border: 1px;}'], - ['body {background-color: #f00;}', 'body {background: #f00;}'], - [ - 'body {background-color: #f00;background-image: url(foobar.png);}', - 'body {background: #f00 url("foobar.png");}', - ], - [ - 'body {background-color: #f00;background-image: url(foobar.png);background-repeat: no-repeat;}', - 'body {background: #f00 url("foobar.png") no-repeat;}', - ], - [ - 'body {background-color: #f00;background-image: url(foobar.png);background-repeat: no-repeat;}', - 'body {background: #f00 url("foobar.png") no-repeat;}', - ], - [ - 'body {background-color: #f00;background-image: url(foobar.png);background-repeat: no-repeat;' - . 'background-position: center;}', - 'body {background: #f00 url("foobar.png") no-repeat center;}', - ], - [ - 'body {background-color: #f00;background-image: url(foobar.png);background-repeat: no-repeat;' - . 'background-position: top left;}', - 'body {background: #f00 url("foobar.png") no-repeat top left;}', - ], - ]; - } - /** * @test */ @@ -403,34 +84,6 @@ public function ruleInsertion(): void ); } - /** - * @test - * - * TODO: The order is different on PHP 5.6 than on PHP >= 7.0. - */ - public function orderOfElementsMatchingOriginalOrderAfterExpandingShorthands(): void - { - $sCss = '.rule{padding:5px;padding-top: 20px}'; - $parser = new Parser($sCss); - $document = $parser->parse(); - $declarationBlocks = $document->getAllDeclarationBlocks(); - - self::assertCount(1, $declarationBlocks); - - $lastDeclarationBlock = \array_pop($declarationBlocks); - $lastDeclarationBlock->expandShorthands(); - - self::assertEquals( - [ - 'padding-top' => 'padding-top: 20px;', - 'padding-right' => 'padding-right: 5px;', - 'padding-bottom' => 'padding-bottom: 5px;', - 'padding-left' => 'padding-left: 5px;', - ], - \array_map('strval', $lastDeclarationBlock->getRulesAssoc()) - ); - } - /** * @return array */ diff --git a/tests/fixtures/create-shorthands.css b/tests/fixtures/create-shorthands.css deleted file mode 100644 index 72198043..00000000 --- a/tests/fixtures/create-shorthands.css +++ /dev/null @@ -1,6 +0,0 @@ -body { - font-size: 2em; font-family: Helvetica,Arial,sans-serif; font-weight: bold; - border-width: 2px; border-color: #999; border-style: dotted; - background-color: #fff; background-image: url('foobar.png'); background-repeat: repeat-y; - margin-top: 2px; margin-right: 3px; margin-bottom: 4px; margin-left: 5px; -} diff --git a/tests/fixtures/expand-shorthands.css b/tests/fixtures/expand-shorthands.css deleted file mode 100644 index 89aab1e2..00000000 --- a/tests/fixtures/expand-shorthands.css +++ /dev/null @@ -1,7 +0,0 @@ -body { - font: italic 500 14px/1.618 "Trebuchet MS", Georgia, serif; - border: 2px solid #f0f; - background: #ccc url("/images/foo.png") no-repeat left top; - margin: 1em !important; - padding: 2px 6px 3px; -} From d5374c5b6e5c606768e6e081c34b8af9804b0c4a Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 28 Jan 2025 00:31:49 +0100 Subject: [PATCH 127/555] [CLEANUP] Avoid Hungarian notation for `oRule`/`oRuleSet` (#835) Part of #756 --- src/CSSList/CSSBlockList.php | 4 ++-- src/Rule/Rule.php | 16 ++++++------- src/RuleSet/RuleSet.php | 44 ++++++++++++++++++------------------ 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/CSSList/CSSBlockList.php b/src/CSSList/CSSBlockList.php index 137faff8..9a07293d 100644 --- a/src/CSSList/CSSBlockList.php +++ b/src/CSSList/CSSBlockList.php @@ -73,8 +73,8 @@ protected function allValues( $this->allValues($oContent, $result, $searchString, $searchInFunctionArguments); } } elseif ($element instanceof RuleSet) { - foreach ($element->getRules($searchString) as $oRule) { - $this->allValues($oRule, $result, $searchString, $searchInFunctionArguments); + foreach ($element->getRules($searchString) as $rule) { + $this->allValues($rule, $result, $searchString, $searchInFunctionArguments); } } elseif ($element instanceof Rule) { $this->allValues($element->getValue(), $result, $searchString, $searchInFunctionArguments); diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index 8cdb05d7..f4033468 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -79,20 +79,20 @@ public function __construct($sRule, $lineNumber = 0, $iColNo = 0) public static function parse(ParserState $parserState): Rule { $aComments = $parserState->consumeWhiteSpace(); - $oRule = new Rule( + $rule = new Rule( $parserState->parseIdentifier(!$parserState->comes('--')), $parserState->currentLine(), $parserState->currentColumn() ); - $oRule->setComments($aComments); - $oRule->addComments($parserState->consumeWhiteSpace()); + $rule->setComments($aComments); + $rule->addComments($parserState->consumeWhiteSpace()); $parserState->consume(':'); - $oValue = Value::parseValue($parserState, self::listDelimiterForRule($oRule->getRule())); - $oRule->setValue($oValue); + $oValue = Value::parseValue($parserState, self::listDelimiterForRule($rule->getRule())); + $rule->setValue($oValue); if ($parserState->getSettings()->bLenientParsing) { while ($parserState->comes('\\')) { $parserState->consume('\\'); - $oRule->addIeHack($parserState->consume()); + $rule->addIeHack($parserState->consume()); $parserState->consumeWhiteSpace(); } } @@ -101,7 +101,7 @@ public static function parse(ParserState $parserState): Rule $parserState->consume('!'); $parserState->consumeWhiteSpace(); $parserState->consume('important'); - $oRule->setIsImportant(true); + $rule->setIsImportant(true); } $parserState->consumeWhiteSpace(); while ($parserState->comes(';')) { @@ -110,7 +110,7 @@ public static function parse(ParserState $parserState): Rule $parserState->consumeWhiteSpace(); - return $oRule; + return $rule; } /** diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 71c86d1d..85184da3 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -53,16 +53,16 @@ public function __construct($lineNumber = 0) * @throws UnexpectedTokenException * @throws UnexpectedEOFException */ - public static function parseRuleSet(ParserState $parserState, RuleSet $oRuleSet): void + public static function parseRuleSet(ParserState $parserState, RuleSet $ruleSet): void { while ($parserState->comes(';')) { $parserState->consume(';'); } while (!$parserState->comes('}')) { - $oRule = null; + $rule = null; if ($parserState->getSettings()->bLenientParsing) { try { - $oRule = Rule::parse($parserState); + $rule = Rule::parse($parserState); } catch (UnexpectedTokenException $e) { try { $sConsume = $parserState->consumeUntil(["\n", ';', '}'], true); @@ -80,10 +80,10 @@ public static function parseRuleSet(ParserState $parserState, RuleSet $oRuleSet) } } } else { - $oRule = Rule::parse($parserState); + $rule = Rule::parse($parserState); } - if ($oRule instanceof Rule) { - $oRuleSet->addRule($oRule); + if ($rule instanceof Rule) { + $ruleSet->addRule($rule); } } $parserState->consume('}'); @@ -100,9 +100,9 @@ public function getLineNo() /** * @param Rule|null $oSibling */ - public function addRule(Rule $oRule, ?Rule $oSibling = null): void + public function addRule(Rule $rule, ?Rule $oSibling = null): void { - $sRule = $oRule->getRule(); + $sRule = $rule->getRule(); if (!isset($this->aRules[$sRule])) { $this->aRules[$sRule] = []; } @@ -113,28 +113,28 @@ public function addRule(Rule $oRule, ?Rule $oSibling = null): void $iSiblingPos = \array_search($oSibling, $this->aRules[$sRule], true); if ($iSiblingPos !== false) { $iPosition = $iSiblingPos; - $oRule->setPosition($oSibling->getLineNo(), $oSibling->getColNo() - 1); + $rule->setPosition($oSibling->getLineNo(), $oSibling->getColNo() - 1); } } - if ($oRule->getLineNo() === 0 && $oRule->getColNo() === 0) { + if ($rule->getLineNo() === 0 && $rule->getColNo() === 0) { //this node is added manually, give it the next best line $rules = $this->getRules(); $pos = \count($rules); if ($pos > 0) { $last = $rules[$pos - 1]; - $oRule->setPosition($last->getLineNo() + 1, 0); + $rule->setPosition($last->getLineNo() + 1, 0); } } - \array_splice($this->aRules[$sRule], $iPosition, 0, [$oRule]); + \array_splice($this->aRules[$sRule], $iPosition, 0, [$rule]); } /** * Returns all rules matching the given rule name * - * @example $oRuleSet->getRules('font') // returns array(0 => $oRule, …) or array(). + * @example $ruleSet->getRules('font') // returns array(0 => $rule, …) or array(). * - * @example $oRuleSet->getRules('font-') + * @example $ruleSet->getRules('font-') * //returns an array of all rules either beginning with font- or matching font. * * @param Rule|string|null $mRule @@ -206,8 +206,8 @@ public function getRulesAssoc($mRule = null) { /** @var array $aResult */ $aResult = []; - foreach ($this->getRules($mRule) as $oRule) { - $aResult[$oRule->getRule()] = $oRule; + foreach ($this->getRules($mRule) as $rule) { + $aResult[$rule->getRule()] = $rule; } return $aResult; } @@ -219,7 +219,7 @@ public function getRulesAssoc($mRule = null) * 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())`. + * remove all rules with the same name. To get the old behaviour, use `removeRule($rule->getRule())`. * * @param Rule|string|null $mRule * pattern to remove. If $mRule is null, all rules are removed. If the pattern ends in a dash, @@ -233,8 +233,8 @@ public function removeRule($mRule): void if (!isset($this->aRules[$sRule])) { return; } - foreach ($this->aRules[$sRule] as $iKey => $oRule) { - if ($oRule === $mRule) { + foreach ($this->aRules[$sRule] as $iKey => $rule) { + if ($rule === $mRule) { unset($this->aRules[$sRule][$iKey]); } } @@ -268,9 +268,9 @@ protected function renderRules(OutputFormat $oOutputFormat) $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); + foreach ($aRules as $rule) { + $sRendered = $oNextLevel->safely(function () use ($rule, $oNextLevel) { + return $rule->render($oNextLevel); }); if ($sRendered === null) { continue; From c6cfc2e85e923c07b6314c8c0833cb6755fdf416 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 28 Jan 2025 00:34:12 +0100 Subject: [PATCH 128/555] [CLEANUP] Drop redundant type annotations in `CSSList` (#839) --- src/CSSList/CSSList.php | 2 -- src/CSSList/Document.php | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index f91ff68d..9ea81b4c 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -147,8 +147,6 @@ private static function parseListItem(ParserState $parserState, CSSList $oList) } /** - * @param ParserState $parserState - * * @return AtRuleBlockList|KeyFrame|Charset|CSSNamespace|Import|AtRuleSet|null * * @throws SourceException diff --git a/src/CSSList/Document.php b/src/CSSList/Document.php index 8d855409..490a9752 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -113,8 +113,6 @@ public function getSelectorsBySpecificity($sSpecificitySearch = null): array /** * Overrides `render()` to make format argument optional. - * - * @param OutputFormat|null $oOutputFormat */ public function render(?OutputFormat $oOutputFormat = null): string { From cac9ae6af020e1f7433bb9709ba48f0a68e6699e Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 28 Jan 2025 23:24:17 +0100 Subject: [PATCH 129/555] [CLEANUP] Remove redundant type annotations in `Property` (#842) --- src/Property/Charset.php | 1 - src/Property/Import.php | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Property/Charset.php b/src/Property/Charset.php index ac704b48..c58f990c 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -34,7 +34,6 @@ class Charset implements AtRule protected $comments; /** - * @param CSSString $oCharset * @param int $lineNumber */ public function __construct(CSSString $oCharset, $lineNumber = 0) diff --git a/src/Property/Import.php b/src/Property/Import.php index 12106766..d48fa70d 100644 --- a/src/Property/Import.php +++ b/src/Property/Import.php @@ -34,7 +34,6 @@ class Import implements AtRule protected $comments; /** - * @param URL $oLocation * @param string $sMediaQuery * @param int $lineNumber */ From b4176251e1ff86f63842a3b89a860815ca8cba9a Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 28 Jan 2025 23:25:17 +0100 Subject: [PATCH 130/555] [CLEANUP] Drop redundant type annotations in `RuleSet` (#845) --- src/RuleSet/RuleSet.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 85184da3..62a3d313 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -97,9 +97,6 @@ public function getLineNo() return $this->lineNumber; } - /** - * @param Rule|null $oSibling - */ public function addRule(Rule $rule, ?Rule $oSibling = null): void { $sRule = $rule->getRule(); From 436a9544dd7050d323c9c9c557de9a5242cb84db Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 28 Jan 2025 23:26:24 +0100 Subject: [PATCH 131/555] [CLEANUP] Avoid Hungarian notation for `oSelector` (#846) Part of #756 --- src/CSSList/CSSBlockList.php | 8 ++++---- src/RuleSet/DeclarationBlock.php | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/CSSList/CSSBlockList.php b/src/CSSList/CSSBlockList.php index 9a07293d..96c25a35 100644 --- a/src/CSSList/CSSBlockList.php +++ b/src/CSSList/CSSBlockList.php @@ -100,9 +100,9 @@ protected function allSelectors(array &$result, $specificitySearch = null): void $aDeclarationBlocks = []; $this->allDeclarationBlocks($aDeclarationBlocks); foreach ($aDeclarationBlocks as $oBlock) { - foreach ($oBlock->getSelectors() as $oSelector) { + foreach ($oBlock->getSelectors() as $selector) { if ($specificitySearch === null) { - $result[] = $oSelector; + $result[] = $selector; } else { $sComparator = '==='; $aSpecificitySearch = \explode(' ', $specificitySearch); @@ -112,7 +112,7 @@ protected function allSelectors(array &$result, $specificitySearch = null): void $iTargetSpecificity = $aSpecificitySearch[1]; } $iTargetSpecificity = (int) $iTargetSpecificity; - $iSelectorSpecificity = $oSelector->getSpecificity(); + $iSelectorSpecificity = $selector->getSpecificity(); $bMatches = false; switch ($sComparator) { case '<=': @@ -132,7 +132,7 @@ protected function allSelectors(array &$result, $specificitySearch = null): void break; } if ($bMatches) { - $result[] = $oSelector; + $result[] = $selector; } } } diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 72a37304..ec4b6d65 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -131,8 +131,8 @@ public function removeSelector($mSelector): bool if ($mSelector instanceof Selector) { $mSelector = $mSelector->getSelector(); } - foreach ($this->aSelectors as $iKey => $oSelector) { - if ($oSelector->getSelector() === $mSelector) { + foreach ($this->aSelectors as $iKey => $selector) { + if ($selector->getSelector() === $mSelector) { unset($this->aSelectors[$iKey]); return true; } From bd206a2dbc28c96a806db5afdc2cf91e961715ae Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 29 Jan 2025 00:30:38 +0100 Subject: [PATCH 132/555] [TASK] Add native type declarations for anonymous functions (#841) Also make then static if possible. --- CHANGELOG.md | 2 +- src/CSSList/CSSList.php | 2 +- src/RuleSet/RuleSet.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ca3c21c..5577e6a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ Please also have a look at our ### Changed - Use more native type declarations and strict mode - (#641, #772, #774, #778, #804) + (#641, #772, #774, #778, #804, #841) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 9ea81b4c..a747e90c 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -413,7 +413,7 @@ protected function renderListContents(OutputFormat $oOutputFormat) $oNextLevel = $oOutputFormat->nextLevel(); } foreach ($this->aContents as $oContent) { - $sRendered = $oOutputFormat->safely(function () use ($oNextLevel, $oContent) { + $sRendered = $oOutputFormat->safely(static function () use ($oNextLevel, $oContent): string { return $oContent->render($oNextLevel); }); if ($sRendered === null) { diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 62a3d313..8888e9cd 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -162,7 +162,7 @@ public function getRules($mRule = null) $aResult = \array_merge($aResult, $aRules); } } - \usort($aResult, function (Rule $first, Rule $second) { + \usort($aResult, static function (Rule $first, Rule $second): int { if ($first->getLineNo() === $second->getLineNo()) { return $first->getColNo() - $second->getColNo(); } @@ -266,7 +266,7 @@ protected function renderRules(OutputFormat $oOutputFormat) $oNextLevel = $oOutputFormat->nextLevel(); foreach ($this->aRules as $aRules) { foreach ($aRules as $rule) { - $sRendered = $oNextLevel->safely(function () use ($rule, $oNextLevel) { + $sRendered = $oNextLevel->safely(static function () use ($rule, $oNextLevel): string { return $rule->render($oNextLevel); }); if ($sRendered === null) { From e24aa71cbec760142e6acefab46203f6aece5a6c Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 29 Jan 2025 00:31:55 +0100 Subject: [PATCH 133/555] [CLEANUP] Drop a redundant type annotation in `Parser` (#848) --- src/Parser.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Parser.php b/src/Parser.php index 9f9f4595..858c0d74 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -20,7 +20,6 @@ class Parser /** * @param string $sText the complete CSS as text (i.e., usually the contents of a CSS file) - * @param Settings|null $oParserSettings * @param int $lineNumber the line number (starting from 1, not from 0) */ public function __construct($sText, ?Settings $oParserSettings = null, $lineNumber = 1) From 0a05b84b89ef5d42a0dfe2d7600cffaa645e2257 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 29 Jan 2025 18:36:41 +0100 Subject: [PATCH 134/555] [CLEANUP] Avoid Hungarian notation for `aComments` (#850) Part of #756 --- src/CSSList/CSSList.php | 10 +++++----- src/OutputFormatter.php | 11 +++-------- src/Parsing/ParserState.php | 8 ++++---- src/Rule/Rule.php | 4 ++-- src/RuleSet/DeclarationBlock.php | 6 +++--- 5 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index a747e90c..5be091e6 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -69,9 +69,9 @@ public static function parseList(ParserState $parserState, CSSList $oList): void $parserState = new ParserState($parserState, Settings::create()); } $bLenientParsing = $parserState->getSettings()->bLenientParsing; - $aComments = []; + $comments = []; while (!$parserState->isEnd()) { - $aComments = \array_merge($aComments, $parserState->consumeWhiteSpace()); + $comments = \array_merge($comments, $parserState->consumeWhiteSpace()); $oListItem = null; if ($bLenientParsing) { try { @@ -87,12 +87,12 @@ public static function parseList(ParserState $parserState, CSSList $oList): void return; } if ($oListItem) { - $oListItem->addComments($aComments); + $oListItem->addComments($comments); $oList->append($oListItem); } - $aComments = $parserState->consumeWhiteSpace(); + $comments = $parserState->consumeWhiteSpace(); } - $oList->addComments($aComments); + $oList->addComments($comments); if (!$bIsRoot && !$bLenientParsing) { throw new SourceException('Unexpected end of document', $parserState->currentLine()); } diff --git a/src/OutputFormatter.php b/src/OutputFormatter.php index 6e68037f..7ef86a3c 100644 --- a/src/OutputFormatter.php +++ b/src/OutputFormatter.php @@ -178,11 +178,6 @@ public function removeLastSemicolon($sString) return \implode(';', $sString); } - /** - * @param array $aComments - * - * @return string - */ public function comments(Commentable $oCommentable): string { if (!$this->oFormat->bRenderComments) { @@ -190,10 +185,10 @@ public function comments(Commentable $oCommentable): string } $sResult = ''; - $aComments = $oCommentable->getComments(); - $iLastCommentIndex = \count($aComments) - 1; + $comments = $oCommentable->getComments(); + $iLastCommentIndex = \count($comments) - 1; - foreach ($aComments as $i => $oComment) { + foreach ($comments as $i => $oComment) { $sResult .= $oComment->render($this->oFormat); $sResult .= $i === $iLastCommentIndex ? $this->spaceAfterBlocks() : $this->spaceBetweenBlocks(); } diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index e1e98386..c14674d2 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -231,7 +231,7 @@ public function parseCharacter($bIsForIdentifier) */ public function consumeWhiteSpace(): array { - $aComments = []; + $comments = []; do { while (\preg_match('/\\s/isSu', $this->peek()) === 1) { $this->consume(1); @@ -241,16 +241,16 @@ public function consumeWhiteSpace(): array $oComment = $this->consumeComment(); } catch (UnexpectedEOFException $e) { $this->iCurrentPosition = $this->iLength; - return $aComments; + return $comments; } } else { $oComment = $this->consumeComment(); } if ($oComment !== false) { - $aComments[] = $oComment; + $comments[] = $oComment; } } while ($oComment !== false); - return $aComments; + return $comments; } /** diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index f4033468..9375cef3 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -78,13 +78,13 @@ public function __construct($sRule, $lineNumber = 0, $iColNo = 0) */ public static function parse(ParserState $parserState): Rule { - $aComments = $parserState->consumeWhiteSpace(); + $comments = $parserState->consumeWhiteSpace(); $rule = new Rule( $parserState->parseIdentifier(!$parserState->comes('--')), $parserState->currentLine(), $parserState->currentColumn() ); - $rule->setComments($aComments); + $rule->setComments($comments); $rule->addComments($parserState->consumeWhiteSpace()); $parserState->consume(':'); $oValue = Value::parseValue($parserState, self::listDelimiterForRule($rule->getRule())); diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index ec4b6d65..c0c26fc5 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -48,14 +48,14 @@ public function __construct($lineNumber = 0) */ public static function parse(ParserState $parserState, $oList = null) { - $aComments = []; + $comments = []; $oResult = new DeclarationBlock($parserState->currentLine()); try { $aSelectorParts = []; $sStringWrapperChar = false; do { $aSelectorParts[] = $parserState->consume(1) - . $parserState->consumeUntil(['{', '}', '\'', '"'], false, false, $aComments); + . $parserState->consumeUntil(['{', '}', '\'', '"'], false, false, $comments); if (\in_array($parserState->peek(), ['\'', '"'], true) && \substr(\end($aSelectorParts), -1) != '\\') { if ($sStringWrapperChar === false) { $sStringWrapperChar = $parserState->peek(); @@ -78,7 +78,7 @@ public static function parse(ParserState $parserState, $oList = null) throw $e; } } - $oResult->setComments($aComments); + $oResult->setComments($comments); RuleSet::parseRuleSet($parserState, $oResult); return $oResult; } From dbf016e9aa3efb840e95f44b23c7178ef560a658 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Thu, 30 Jan 2025 09:53:23 +0000 Subject: [PATCH 135/555] [BUGFIX] Render RGB functions with "modern" syntax if required (#840) The "legacy" syntax does not allow a mixture of `percentage`s and `number`s for the red, green and blue components. So if `rgb`/`rgba` functions that have such a mixture are rendered in the "legacy" syntax, an invalid property value will result. An `OutputFormat` option to use the "modern" syntax throughout will be added later (see #801). References: - https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgb#formal_syntax - https://www.w3.org/TR/css-color-4/#rgb-functions --- CHANGELOG.md | 1 + src/Value/Color.php | 87 ++++++++++++++++++++++++++++++++++ tests/Unit/Value/ColorTest.php | 5 -- 3 files changed, 88 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5577e6a6..bf3db03a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Please also have a look at our - Partial support for CSS Color Module Level 4: - `rgb` and `rgba`, and `hsl` and `hsla` are now aliases (#797} - Parse color functions that use the "modern" syntax (#800) + - Render RGB functions with "modern" syntax when required (#840) - Add a class diagram to the README (#482) - Add more tests (#449) diff --git a/src/Value/Color.php b/src/Value/Color.php index 5dd832a6..af6efa95 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -233,6 +233,10 @@ public function render(OutputFormat $outputFormat): string return $this->renderAsHex(); } + if ($this->shouldRenderInModernSyntax()) { + return $this->renderInModernSyntax($outputFormat); + } + return parent::render($outputFormat); } @@ -282,4 +286,87 @@ private function renderAsHex(): string return '#' . ($canUseShortVariant ? $result[0] . $result[2] . $result[4] : $result); } + + /** + * The "legacy" syntax does not allow RGB colors to have a mixture of `percentage`s and `number`s. + * + * The "legacy" and "modern" monikers are part of the formal W3C syntax. + * See the following for more information: + * - {@link + * https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgb#formal_syntax + * Description of the formal syntax for `rgb()` on MDN + * }; + * - {@link + * https://www.w3.org/TR/css-color-4/#rgb-functions + * The same in the CSS Color Module Level 4 W3C Candidate Recommendation Draft + * } (as of 13 February 2024, at time of writing). + */ + private function shouldRenderInModernSyntax(): bool + { + if (!$this->colorFunctionMayHaveMixedValueTypes($this->getRealName())) { + return false; + } + + $hasPercentage = false; + $hasNumber = false; + foreach ($this->aComponents as $key => $value) { + if ($key === 'a') { + // Alpha can have units that don't match those of the RGB components in the "legacy" syntax. + // So it is not necessary to check it. It's also always last, hence `break` rather than `continue`. + break; + } + if (!($value instanceof Size)) { + // Unexpected, unknown, or modified via the API + return false; + } + $unit = $value->getUnit(); + // `switch` only does loose comparison + if ($unit === null) { + $hasNumber = true; + } elseif ($unit === '%') { + $hasPercentage = true; + } else { + // Invalid unit + return false; + } + } + + return $hasPercentage && $hasNumber; + } + + /** + * Some color functions, such as `rgb`, + * may have a mixture of `percentage`, `number`, or possibly other types in their arguments. + * + * Note that this excludes the alpha component, which is treated separately. + */ + private function colorFunctionMayHaveMixedValueTypes(string $function): bool + { + $functionsThatMayHaveMixedValueTypes = ['rgb', 'rgba']; + + return \in_array($function, $functionsThatMayHaveMixedValueTypes, true); + } + + /** + * @return non-empty-string + */ + private function renderInModernSyntax(OutputFormat $outputFormat): string + { + // Maybe not yet without alpha, but will be... + $componentsWithoutAlpha = $this->aComponents; + \end($componentsWithoutAlpha); + if (\key($componentsWithoutAlpha) === 'a') { + $alpha = $this->aComponents['a']; + unset($componentsWithoutAlpha['a']); + } + + $arguments = $outputFormat->implode(' ', $componentsWithoutAlpha); + if (isset($alpha)) { + $separator = $outputFormat->spaceBeforeListArgumentSeparator('/') + . '/' . $outputFormat->spaceAfterListArgumentSeparator('/'); + $arguments = $outputFormat->implode($separator, [$arguments, $alpha]); + } + + return $this->getName() . '(' . $arguments . ')'; + } } diff --git a/tests/Unit/Value/ColorTest.php b/tests/Unit/Value/ColorTest.php index 2b533a7f..5c502f82 100644 --- a/tests/Unit/Value/ColorTest.php +++ b/tests/Unit/Value/ColorTest.php @@ -86,8 +86,6 @@ public static function provideValidColorAndExpectedRendering(): array 'rgb(0 119 0)', '#070', ], - // The "legacy" syntax currently used for rendering does not allow a mixture of percentages and numbers. - /* 'modern rgb with percentage R' => [ 'rgb(0% 119 0)', 'rgb(0% 119 0)', @@ -112,7 +110,6 @@ public static function provideValidColorAndExpectedRendering(): array 'rgb(0 60% 0%)', 'rgb(0 60% 0%)', ], - //*/ 'modern rgb with percentage components' => [ 'rgb(0% 60% 0%)', 'rgb(0%,60%,0%)', @@ -131,7 +128,6 @@ public static function provideValidColorAndExpectedRendering(): array 'rgb(0 119 0 / 50%)', 'rgba(0,119,0,50%)', ], - /* 'modern rgba with percentage R' => [ 'rgb(0% 119 0 / 0.5)', 'rgba(0% 119 0/.5)', @@ -144,7 +140,6 @@ public static function provideValidColorAndExpectedRendering(): array 'rgb(0 119 0% / 0.5)', 'rgba(0 119 0%/.5)', ], - //*/ 'modern rgba with percentage RGB' => [ 'rgb(0% 60% 0% / 0.5)', 'rgba(0%,60%,0%,.5)', From c8a7b5fbafd141e9fc594b31d49e6aeb76050e71 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 30 Jan 2025 18:38:41 +0100 Subject: [PATCH 136/555] [CLEANUP] Avoid Hungarian notation in `CSSBlockList` (part 2) (#851) Part of #756 --- src/CSSList/CSSBlockList.php | 40 ++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/CSSList/CSSBlockList.php b/src/CSSList/CSSBlockList.php index 96c25a35..2d1e014e 100644 --- a/src/CSSList/CSSBlockList.php +++ b/src/CSSList/CSSBlockList.php @@ -96,42 +96,42 @@ protected function allValues( */ protected function allSelectors(array &$result, $specificitySearch = null): void { - /** @var array $aDeclarationBlocks */ - $aDeclarationBlocks = []; - $this->allDeclarationBlocks($aDeclarationBlocks); - foreach ($aDeclarationBlocks as $oBlock) { + /** @var array $declarationBlocks */ + $declarationBlocks = []; + $this->allDeclarationBlocks($declarationBlocks); + foreach ($declarationBlocks as $oBlock) { foreach ($oBlock->getSelectors() as $selector) { if ($specificitySearch === null) { $result[] = $selector; } else { - $sComparator = '==='; - $aSpecificitySearch = \explode(' ', $specificitySearch); - $iTargetSpecificity = $aSpecificitySearch[0]; - if (\count($aSpecificitySearch) > 1) { - $sComparator = $aSpecificitySearch[0]; - $iTargetSpecificity = $aSpecificitySearch[1]; + $comparator = '==='; + $expressionParts = \explode(' ', $specificitySearch); + $targetSpecificity = $expressionParts[0]; + if (\count($expressionParts) > 1) { + $comparator = $expressionParts[0]; + $targetSpecificity = $expressionParts[1]; } - $iTargetSpecificity = (int) $iTargetSpecificity; - $iSelectorSpecificity = $selector->getSpecificity(); - $bMatches = false; - switch ($sComparator) { + $targetSpecificity = (int) $targetSpecificity; + $selectorSpecificity = $selector->getSpecificity(); + $comparatorMatched = false; + switch ($comparator) { case '<=': - $bMatches = $iSelectorSpecificity <= $iTargetSpecificity; + $comparatorMatched = $selectorSpecificity <= $targetSpecificity; break; case '<': - $bMatches = $iSelectorSpecificity < $iTargetSpecificity; + $comparatorMatched = $selectorSpecificity < $targetSpecificity; break; case '>=': - $bMatches = $iSelectorSpecificity >= $iTargetSpecificity; + $comparatorMatched = $selectorSpecificity >= $targetSpecificity; break; case '>': - $bMatches = $iSelectorSpecificity > $iTargetSpecificity; + $comparatorMatched = $selectorSpecificity > $targetSpecificity; break; default: - $bMatches = $iSelectorSpecificity === $iTargetSpecificity; + $comparatorMatched = $selectorSpecificity === $targetSpecificity; break; } - if ($bMatches) { + if ($comparatorMatched) { $result[] = $selector; } } From 3a147fd4cb2cc2ccc4f9ed2f50fe7d00bc05b722 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Thu, 30 Jan 2025 20:34:24 +0000 Subject: [PATCH 137/555] [CLEANUP] Extract method `Color::shouldRenderAsHex` (#853) Resolves #852. --- src/Value/Color.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Value/Color.php b/src/Value/Color.php index af6efa95..b657d66d 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -224,12 +224,7 @@ public function __toString(): string public function render(OutputFormat $outputFormat): string { - // Shorthand RGB color values - if ( - $outputFormat->getRGBHashNotation() - && $this->getRealName() === 'rgb' - && $this->allComponentsAreNumbers() - ) { + if ($this->shouldRenderAsHex($outputFormat)) { return $this->renderAsHex(); } @@ -240,6 +235,14 @@ public function render(OutputFormat $outputFormat): string return parent::render($outputFormat); } + private function shouldRenderAsHex(OutputFormat $outputFormat): bool + { + return + $outputFormat->getRGBHashNotation() + && $this->getRealName() === 'rgb' + && $this->allComponentsAreNumbers(); + } + /** * The function name is a concatenation of the array keys of the components, which is passed to the constructor. * However, this can be changed by calling {@see CSSFunction::setName}, From 24e69d430b21ee68a46ee2c566603eba9222c4df Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 30 Jan 2025 23:22:59 +0100 Subject: [PATCH 138/555] [CLEANUP] Avoid Hungarian notation for `aContents` and items (#854) Part of #756 --- src/CSSList/CSSBlockList.php | 20 +++++------ src/CSSList/CSSList.php | 64 ++++++++++++++++++------------------ 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/CSSList/CSSBlockList.php b/src/CSSList/CSSBlockList.php index 2d1e014e..e72850b1 100644 --- a/src/CSSList/CSSBlockList.php +++ b/src/CSSList/CSSBlockList.php @@ -33,11 +33,11 @@ public function __construct($lineNumber = 0) */ protected function allDeclarationBlocks(array &$result): void { - foreach ($this->aContents as $mContent) { - if ($mContent instanceof DeclarationBlock) { - $result[] = $mContent; - } elseif ($mContent instanceof CSSBlockList) { - $mContent->allDeclarationBlocks($result); + foreach ($this->contents as $item) { + if ($item instanceof DeclarationBlock) { + $result[] = $item; + } elseif ($item instanceof CSSBlockList) { + $item->allDeclarationBlocks($result); } } } @@ -47,11 +47,11 @@ protected function allDeclarationBlocks(array &$result): void */ protected function allRuleSets(array &$result): void { - foreach ($this->aContents as $mContent) { - if ($mContent instanceof RuleSet) { - $result[] = $mContent; - } elseif ($mContent instanceof CSSBlockList) { - $mContent->allRuleSets($result); + foreach ($this->contents as $item) { + if ($item instanceof RuleSet) { + $result[] = $item; + } elseif ($item instanceof CSSBlockList) { + $item->allRuleSets($result); } } } diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 5be091e6..898983cf 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -41,7 +41,7 @@ abstract class CSSList implements Renderable, Commentable /** * @var array */ - protected $aContents; + protected $contents; /** * @var int @@ -54,7 +54,7 @@ abstract class CSSList implements Renderable, Commentable public function __construct($lineNumber = 0) { $this->comments = []; - $this->aContents = []; + $this->contents = []; $this->lineNumber = $lineNumber; } @@ -259,21 +259,21 @@ public function getLineNo() /** * Prepends an item to the list of contents. * - * @param RuleSet|CSSList|Import|Charset $oItem + * @param RuleSet|CSSList|Import|Charset $item */ - public function prepend($oItem): void + public function prepend($item): void { - \array_unshift($this->aContents, $oItem); + \array_unshift($this->contents, $item); } /** * Appends an item to the list of contents. * - * @param RuleSet|CSSList|Import|Charset $oItem + * @param RuleSet|CSSList|Import|Charset $item */ - public function append($oItem): void + public function append($item): void { - $this->aContents[] = $oItem; + $this->contents[] = $item; } /** @@ -285,7 +285,7 @@ public function append($oItem): void */ public function splice($iOffset, $iLength = null, $mReplacement = null): void { - \array_splice($this->aContents, $iOffset, $iLength, $mReplacement); + \array_splice($this->contents, $iOffset, $iLength, $mReplacement); } /** @@ -297,7 +297,7 @@ public function splice($iOffset, $iLength = null, $mReplacement = null): void */ public function insertBefore($item, $sibling): void { - if (\in_array($sibling, $this->aContents, true)) { + if (\in_array($sibling, $this->contents, true)) { $this->replace($sibling, [$item, $sibling]); } else { $this->append($item); @@ -307,17 +307,17 @@ public function insertBefore($item, $sibling): void /** * Removes an item from the CSS list. * - * @param RuleSet|Import|Charset|CSSList $oItemToRemove + * @param RuleSet|Import|Charset|CSSList $itemToRemove * May be a `RuleSet` (most likely a `DeclarationBlock`), an `Import`, * a `Charset` or another `CSSList` (most likely a `MediaQuery`) * * @return bool whether the item was removed */ - public function remove($oItemToRemove) + public function remove($itemToRemove) { - $iKey = \array_search($oItemToRemove, $this->aContents, true); + $iKey = \array_search($itemToRemove, $this->contents, true); if ($iKey !== false) { - unset($this->aContents[$iKey]); + unset($this->contents[$iKey]); return true; } return false; @@ -326,20 +326,20 @@ public function remove($oItemToRemove) /** * Replaces an item from the CSS list. * - * @param RuleSet|Import|Charset|CSSList $oOldItem + * @param RuleSet|Import|Charset|CSSList $oldItem * May be a `RuleSet` (most likely a `DeclarationBlock`), an `Import`, a `Charset` * or another `CSSList` (most likely a `MediaQuery`) * * @return bool */ - public function replace($oOldItem, $mNewItem) + public function replace($oldItem, $newItem) { - $iKey = \array_search($oOldItem, $this->aContents, true); + $iKey = \array_search($oldItem, $this->contents, true); if ($iKey !== false) { - if (\is_array($mNewItem)) { - \array_splice($this->aContents, $iKey, 1, $mNewItem); + if (\is_array($newItem)) { + \array_splice($this->contents, $iKey, 1, $newItem); } else { - \array_splice($this->aContents, $iKey, 1, [$mNewItem]); + \array_splice($this->contents, $iKey, 1, [$newItem]); } return true; } @@ -347,12 +347,12 @@ public function replace($oOldItem, $mNewItem) } /** - * @param array $aContents + * @param array $contents */ - public function setContents(array $aContents): void + public function setContents(array $contents): void { - $this->aContents = []; - foreach ($aContents as $content) { + $this->contents = []; + foreach ($contents as $content) { $this->append($content); } } @@ -383,12 +383,12 @@ public function removeDeclarationBlockBySelector($mSelector, $bRemoveAll = false $mSel = new Selector($mSel); } } - foreach ($this->aContents as $iKey => $mItem) { - if (!($mItem instanceof DeclarationBlock)) { + foreach ($this->contents as $iKey => $item) { + if (!($item instanceof DeclarationBlock)) { continue; } - if ($mItem->getSelectors() == $mSelector) { - unset($this->aContents[$iKey]); + if ($item->getSelectors() == $mSelector) { + unset($this->contents[$iKey]); if (!$bRemoveAll) { return; } @@ -412,9 +412,9 @@ protected function renderListContents(OutputFormat $oOutputFormat) if (!$this->isRootList()) { $oNextLevel = $oOutputFormat->nextLevel(); } - foreach ($this->aContents as $oContent) { - $sRendered = $oOutputFormat->safely(static function () use ($oNextLevel, $oContent): string { - return $oContent->render($oNextLevel); + foreach ($this->contents as $listItem) { + $sRendered = $oOutputFormat->safely(static function () use ($oNextLevel, $listItem): string { + return $listItem->render($oNextLevel); }); if ($sRendered === null) { continue; @@ -450,7 +450,7 @@ abstract public function isRootList(); */ public function getContents() { - return $this->aContents; + return $this->contents; } /** From 3cd934ae8966568609a3ad50f17cef848c62fe38 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 31 Jan 2025 19:31:25 +0100 Subject: [PATCH 139/555] [CLEANUP] Avoid Hungarian notation for `iKey` (#855) Part of #756 --- src/CSSList/CSSList.php | 20 ++++++++++---------- src/RuleSet/DeclarationBlock.php | 10 +++++----- src/RuleSet/RuleSet.php | 4 ++-- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 898983cf..be05b94f 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -315,9 +315,9 @@ public function insertBefore($item, $sibling): void */ public function remove($itemToRemove) { - $iKey = \array_search($itemToRemove, $this->contents, true); - if ($iKey !== false) { - unset($this->contents[$iKey]); + $key = \array_search($itemToRemove, $this->contents, true); + if ($key !== false) { + unset($this->contents[$key]); return true; } return false; @@ -334,12 +334,12 @@ public function remove($itemToRemove) */ public function replace($oldItem, $newItem) { - $iKey = \array_search($oldItem, $this->contents, true); - if ($iKey !== false) { + $key = \array_search($oldItem, $this->contents, true); + if ($key !== false) { if (\is_array($newItem)) { - \array_splice($this->contents, $iKey, 1, $newItem); + \array_splice($this->contents, $key, 1, $newItem); } else { - \array_splice($this->contents, $iKey, 1, [$newItem]); + \array_splice($this->contents, $key, 1, [$newItem]); } return true; } @@ -371,7 +371,7 @@ public function removeDeclarationBlockBySelector($mSelector, $bRemoveAll = false if (!\is_array($mSelector)) { $mSelector = \explode(',', $mSelector); } - foreach ($mSelector as $iKey => &$mSel) { + foreach ($mSelector as $key => &$mSel) { if (!($mSel instanceof Selector)) { if (!Selector::isValid($mSel)) { throw new UnexpectedTokenException( @@ -383,12 +383,12 @@ public function removeDeclarationBlockBySelector($mSelector, $bRemoveAll = false $mSel = new Selector($mSel); } } - foreach ($this->contents as $iKey => $item) { + foreach ($this->contents as $key => $item) { if (!($item instanceof DeclarationBlock)) { continue; } if ($item->getSelectors() == $mSelector) { - unset($this->contents[$iKey]); + unset($this->contents[$key]); if (!$bRemoveAll) { return; } diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index c0c26fc5..4d87bd83 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -96,7 +96,7 @@ public function setSelectors($mSelector, $oList = null): void } else { $this->aSelectors = \explode(',', $mSelector); } - foreach ($this->aSelectors as $iKey => $mSelector) { + foreach ($this->aSelectors as $key => $mSelector) { if (!($mSelector instanceof Selector)) { if ($oList === null || !($oList instanceof KeyFrame)) { if (!Selector::isValid($mSelector)) { @@ -106,7 +106,7 @@ public function setSelectors($mSelector, $oList = null): void 'custom' ); } - $this->aSelectors[$iKey] = new Selector($mSelector); + $this->aSelectors[$key] = new Selector($mSelector); } else { if (!KeyframeSelector::isValid($mSelector)) { throw new UnexpectedTokenException( @@ -115,7 +115,7 @@ public function setSelectors($mSelector, $oList = null): void 'custom' ); } - $this->aSelectors[$iKey] = new KeyframeSelector($mSelector); + $this->aSelectors[$key] = new KeyframeSelector($mSelector); } } } @@ -131,9 +131,9 @@ public function removeSelector($mSelector): bool if ($mSelector instanceof Selector) { $mSelector = $mSelector->getSelector(); } - foreach ($this->aSelectors as $iKey => $selector) { + foreach ($this->aSelectors as $key => $selector) { if ($selector->getSelector() === $mSelector) { - unset($this->aSelectors[$iKey]); + unset($this->aSelectors[$key]); return true; } } diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 8888e9cd..458f0af5 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -230,9 +230,9 @@ public function removeRule($mRule): void if (!isset($this->aRules[$sRule])) { return; } - foreach ($this->aRules[$sRule] as $iKey => $rule) { + foreach ($this->aRules[$sRule] as $key => $rule) { if ($rule === $mRule) { - unset($this->aRules[$sRule][$iKey]); + unset($this->aRules[$sRule][$key]); } } } else { From 400f3fd735cebb2016a4b791e3267cc82605c8f1 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 31 Jan 2025 19:34:03 +0100 Subject: [PATCH 140/555] [CLEANUP] Improve readability of `instanceof` calls (#856) - Use parentheses for negated `instanceof` calls. - Use `assertInstanceOf` in the tests. --- src/Rule/Rule.php | 2 +- src/Value/Color.php | 2 +- tests/ParserTest.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index 9375cef3..0fb9d494 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -206,7 +206,7 @@ public function addValue($mValue, $sType = ' '): void if (!\is_array($mValue)) { $mValue = [$mValue]; } - if (!$this->mValue instanceof RuleValueList || $this->mValue->getListSeparator() !== $sType) { + if (!($this->mValue instanceof RuleValueList) || $this->mValue->getListSeparator() !== $sType) { $mCurrentValue = $this->mValue; $this->mValue = new RuleValueList($sType, $this->lineNumber); if ($mCurrentValue) { diff --git a/src/Value/Color.php b/src/Value/Color.php index b657d66d..8b913d54 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -260,7 +260,7 @@ private function getRealName(): string private function allComponentsAreNumbers(): bool { foreach ($this->aComponents as $component) { - if (!$component instanceof Size || $component->getUnit() !== null) { + if (!($component instanceof Size) || $component->getUnit() !== null) { return false; } } diff --git a/tests/ParserTest.php b/tests/ParserTest.php index fbff74e8..bdd1a1f4 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -97,7 +97,7 @@ public function colorParsing(): void { $document = self::parsedStructureForFile('colortest'); foreach ($document->getAllRuleSets() as $ruleSet) { - if (!$ruleSet instanceof DeclarationBlock) { + if (!($ruleSet instanceof DeclarationBlock)) { continue; } $selectors = $ruleSet->getSelectors(); @@ -405,7 +405,7 @@ public function ruleGetters(): void self::assertSame('background-color', $backgroundHeaderRules[1]->getRule()); $backgroundHeaderRules = $headerBlock->getRulesAssoc('background-'); self::assertCount(1, $backgroundHeaderRules); - self::assertTrue($backgroundHeaderRules['background-color']->getValue() instanceof Color); + self::assertInstanceOf(Color::class, $backgroundHeaderRules['background-color']->getValue()); self::assertSame('rgba', $backgroundHeaderRules['background-color']->getValue()->getColorDescription()); $headerBlock->removeRule($backgroundHeaderRules['background-color']); $backgroundHeaderRules = $headerBlock->getRules('background-'); From 116ab69902bcdec6393180f6dfd31020294e2d42 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 31 Jan 2025 19:38:57 +0100 Subject: [PATCH 141/555] [CLEANUP] Avoid Hungarian notation for `oList` and `oListItem` (#857) Part of #756 --- src/CSSList/CSSList.php | 30 +++++++++++++++--------------- src/RuleSet/DeclarationBlock.php | 12 ++++++------ src/Value/CalcFunction.php | 6 +++--- src/Value/Value.php | 6 +++--- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index be05b94f..14c373ef 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -62,9 +62,9 @@ public function __construct($lineNumber = 0) * @throws UnexpectedTokenException * @throws SourceException */ - public static function parseList(ParserState $parserState, CSSList $oList): void + public static function parseList(ParserState $parserState, CSSList $list): void { - $bIsRoot = $oList instanceof Document; + $bIsRoot = $list instanceof Document; if (\is_string($parserState)) { $parserState = new ParserState($parserState, Settings::create()); } @@ -72,27 +72,27 @@ public static function parseList(ParserState $parserState, CSSList $oList): void $comments = []; while (!$parserState->isEnd()) { $comments = \array_merge($comments, $parserState->consumeWhiteSpace()); - $oListItem = null; + $listItem = null; if ($bLenientParsing) { try { - $oListItem = self::parseListItem($parserState, $oList); + $listItem = self::parseListItem($parserState, $list); } catch (UnexpectedTokenException $e) { - $oListItem = false; + $listItem = false; } } else { - $oListItem = self::parseListItem($parserState, $oList); + $listItem = self::parseListItem($parserState, $list); } - if ($oListItem === null) { + if ($listItem === null) { // List parsing finished return; } - if ($oListItem) { - $oListItem->addComments($comments); - $oList->append($oListItem); + if ($listItem) { + $listItem->addComments($comments); + $list->append($listItem); } $comments = $parserState->consumeWhiteSpace(); } - $oList->addComments($comments); + $list->addComments($comments); if (!$bIsRoot && !$bLenientParsing) { throw new SourceException('Unexpected end of document', $parserState->currentLine()); } @@ -105,9 +105,9 @@ public static function parseList(ParserState $parserState, CSSList $oList): void * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ - private static function parseListItem(ParserState $parserState, CSSList $oList) + private static function parseListItem(ParserState $parserState, CSSList $list) { - $bIsRoot = $oList instanceof Document; + $bIsRoot = $list instanceof Document; if ($parserState->comes('@')) { $oAtRule = self::parseAtRule($parserState); if ($oAtRule instanceof Charset) { @@ -119,7 +119,7 @@ private static function parseListItem(ParserState $parserState, CSSList $oList) $parserState->currentLine() ); } - if (\count($oList->getContents()) > 0) { + if (\count($list->getContents()) > 0) { throw new UnexpectedTokenException( '@charset must be the first parseable token in a document', '', @@ -142,7 +142,7 @@ private static function parseListItem(ParserState $parserState, CSSList $oList) return null; } } else { - return DeclarationBlock::parse($parserState, $oList); + return DeclarationBlock::parse($parserState, $list); } } diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 4d87bd83..08c08f00 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -39,14 +39,14 @@ public function __construct($lineNumber = 0) } /** - * @param CSSList|null $oList + * @param CSSList|null $list * * @return DeclarationBlock|false * * @throws UnexpectedTokenException * @throws UnexpectedEOFException */ - public static function parse(ParserState $parserState, $oList = null) + public static function parse(ParserState $parserState, $list = null) { $comments = []; $oResult = new DeclarationBlock($parserState->currentLine()); @@ -64,7 +64,7 @@ public static function parse(ParserState $parserState, $oList = null) } } } while (!\in_array($parserState->peek(), ['{', '}'], true) || $sStringWrapperChar !== false); - $oResult->setSelectors(\implode('', $aSelectorParts), $oList); + $oResult->setSelectors(\implode('', $aSelectorParts), $list); if ($parserState->comes('{')) { $parserState->consume(1); } @@ -85,11 +85,11 @@ public static function parse(ParserState $parserState, $oList = null) /** * @param array|string $mSelector - * @param CSSList|null $oList + * @param CSSList|null $list * * @throws UnexpectedTokenException */ - public function setSelectors($mSelector, $oList = null): void + public function setSelectors($mSelector, $list = null): void { if (\is_array($mSelector)) { $this->aSelectors = $mSelector; @@ -98,7 +98,7 @@ public function setSelectors($mSelector, $oList = null): void } foreach ($this->aSelectors as $key => $mSelector) { if (!($mSelector instanceof Selector)) { - if ($oList === null || !($oList instanceof KeyFrame)) { + if ($list === null || !($list instanceof KeyFrame)) { if (!Selector::isValid($mSelector)) { throw new UnexpectedTokenException( "Selector did not match '" . Selector::SELECTOR_VALIDATION_RX . "'.", diff --git a/src/Value/CalcFunction.php b/src/Value/CalcFunction.php index 8288c7cd..5d84a9a8 100644 --- a/src/Value/CalcFunction.php +++ b/src/Value/CalcFunction.php @@ -37,7 +37,7 @@ public static function parse(ParserState $parserState, bool $bIgnoreCase = false } $parserState->consume('('); $oCalcList = new CalcRuleValueList($parserState->currentLine()); - $oList = new RuleValueList(',', $parserState->currentLine()); + $list = new RuleValueList(',', $parserState->currentLine()); $iNestingLevel = 0; $iLastComponentType = null; while (!$parserState->comes(')') || $iNestingLevel > 0) { @@ -94,10 +94,10 @@ public static function parse(ParserState $parserState, bool $bIgnoreCase = false } $parserState->consumeWhiteSpace(); } - $oList->addListComponent($oCalcList); + $list->addListComponent($oCalcList); if (!$parserState->isEnd()) { $parserState->consume(')'); } - return new CalcFunction($sFunction, $oList, ',', $parserState->currentLine()); + return new CalcFunction($sFunction, $list, ',', $parserState->currentLine()); } } diff --git a/src/Value/Value.php b/src/Value/Value.php index b11c1085..7ba2a7e2 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -85,11 +85,11 @@ public static function parseValue(ParserState $parserState, array $aListDelimite break; } } - $oList = new RuleValueList($sDelimiter, $parserState->currentLine()); + $list = new RuleValueList($sDelimiter, $parserState->currentLine()); for ($i = $iStartPosition; $i - $iStartPosition < $iLength * 2; $i += 2) { - $oList->addListComponent($aStack[$i]); + $list->addListComponent($aStack[$i]); } - $aNewStack[] = $oList; + $aNewStack[] = $list; $iStartPosition += $iLength * 2 - 2; } $aStack = $aNewStack; From 6cbd4956b2a9cf40b5b7adb4dcc3a7b79f0039a5 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 31 Jan 2025 23:16:11 +0100 Subject: [PATCH 142/555] [TASK] Add accessor tests for `OutputFormat` (part 1) (#847) Currently, this class uses magic getters and setters. These should be proper methods instead. But first, we should have tests for the accessors. This is part 1. --- tests/Unit/OutputFormatTest.php | 307 ++++++++++++++++++++++++++++++++ 1 file changed, 307 insertions(+) create mode 100644 tests/Unit/OutputFormatTest.php diff --git a/tests/Unit/OutputFormatTest.php b/tests/Unit/OutputFormatTest.php new file mode 100644 index 00000000..482be82f --- /dev/null +++ b/tests/Unit/OutputFormatTest.php @@ -0,0 +1,307 @@ +subject = new OutputFormat(); + } + + /** + * @test + */ + public function getStringQuotingTypeInitiallyReturnsDoubleQuote(): void + { + self::assertSame('"', $this->subject->getStringQuotingType()); + } + + /** + * @test + */ + public function setStringQuotingTypeSetsStringQuotingType(): void + { + $value = "'"; + $this->subject->setStringQuotingType($value); + + self::assertSame($value, $this->subject->getStringQuotingType()); + } + + /** + * @test + */ + public function setStringQuotingTypeProvidesFluentInterface(): void + { + self::assertSame($this->subject, $this->subject->setStringQuotingType('"')); + } + + /** + * @test + */ + public function getRGBHashNotationInitiallyReturnsTrue(): void + { + self::assertTrue($this->subject->getRGBHashNotation()); + } + + /** + * @return array + */ + public static function provideBooleans(): array + { + return [ + 'true' => [true], + 'false' => [false], + ]; + } + + /** + * @test + * + * @dataProvider provideBooleans + */ + public function setRGBHashNotationSetsRGBHashNotation(bool $value): void + { + $this->subject->setRGBHashNotation($value); + + self::assertSame($value, $this->subject->getRGBHashNotation()); + } + + /** + * @test + */ + public function setRGBHashNotationProvidesFluentInterface(): void + { + self::assertSame($this->subject, $this->subject->setRGBHashNotation(true)); + } + + /** + * @test + */ + public function getSemicolonAfterLastRuleInitiallyReturnsTrue(): void + { + self::assertTrue($this->subject->getSemicolonAfterLastRule()); + } + + /** + * @test + * + * @dataProvider provideBooleans + */ + public function setSemicolonAfterLastRuleSetsSemicolonAfterLastRule(bool $value): void + { + $this->subject->setSemicolonAfterLastRule($value); + + self::assertSame($value, $this->subject->getSemicolonAfterLastRule()); + } + + /** + * @test + */ + public function setSemicolonAfterLastRuleProvidesFluentInterface(): void + { + self::assertSame($this->subject, $this->subject->setSemicolonAfterLastRule(true)); + } + + /** + * @test + */ + public function getSpaceAfterRuleNameInitiallyReturnsSingleSpace(): void + { + self::assertSame(' ', $this->subject->getSpaceAfterRuleName()); + } + + /** + * @test + */ + public function setSpaceAfterRuleNameSetsSpaceAfterRuleName(): void + { + $value = "\n"; + $this->subject->setSpaceAfterRuleName($value); + + self::assertSame($value, $this->subject->getSpaceAfterRuleName()); + } + + /** + * @test + */ + public function setSpaceAfterRuleNameProvidesFluentInterface(): void + { + self::assertSame($this->subject, $this->subject->setSpaceAfterRuleName("\n")); + } + + /** + * @test + */ + public function getSpaceBeforeRulesInitiallyReturnsEmptyString(): void + { + self::assertSame('', $this->subject->getSpaceBeforeRules()); + } + + /** + * @test + */ + public function setSpaceBeforeRulesSetsSpaceBeforeRules(): void + { + $value = ' '; + $this->subject->setSpaceBeforeRules($value); + + self::assertSame($value, $this->subject->getSpaceBeforeRules()); + } + + /** + * @test + */ + public function setSpaceBeforeRulesProvidesFluentInterface(): void + { + self::assertSame($this->subject, $this->subject->setSpaceBeforeRules(' ')); + } + + /** + * @test + */ + public function getSpaceAfterRulesInitiallyReturnsEmptyString(): void + { + self::assertSame('', $this->subject->getSpaceAfterRules()); + } + + /** + * @test + */ + public function setSpaceAfterRulesSetsSpaceAfterRules(): void + { + $value = ' '; + $this->subject->setSpaceAfterRules($value); + + self::assertSame($value, $this->subject->getSpaceAfterRules()); + } + + /** + * @test + */ + public function setSpaceAfterRulesProvidesFluentInterface(): void + { + self::assertSame($this->subject, $this->subject->setSpaceAfterRules(' ')); + } + + /** + * @test + */ + public function getSpaceBetweenRulesInitiallyReturnsEmptyString(): void + { + self::assertSame('', $this->subject->getSpaceBetweenRules()); + } + + /** + * @test + */ + public function setSpaceBetweenRulesSetsSpaceBetweenRules(): void + { + $value = ' '; + $this->subject->setSpaceBetweenRules($value); + + self::assertSame($value, $this->subject->getSpaceBetweenRules()); + } + + /** + * @test + */ + public function setSpaceBetweenRulesProvidesFluentInterface(): void + { + self::assertSame($this->subject, $this->subject->setSpaceBetweenRules(' ')); + } + + /** + * @test + */ + public function getSpaceBeforeBlocksInitiallyReturnsEmptyString(): void + { + self::assertSame('', $this->subject->getSpaceBeforeBlocks()); + } + + /** + * @test + */ + public function setSpaceBeforeBlocksSetsSpaceBeforeBlocks(): void + { + $value = ' '; + $this->subject->setSpaceBeforeBlocks($value); + + self::assertSame($value, $this->subject->getSpaceBeforeBlocks()); + } + + /** + * @test + */ + public function setSpaceBeforeBlocksProvidesFluentInterface(): void + { + self::assertSame($this->subject, $this->subject->setSpaceBeforeBlocks(' ')); + } + + /** + * @test + */ + public function getSpaceAfterBlocksInitiallyReturnsEmptyString(): void + { + self::assertSame('', $this->subject->getSpaceAfterBlocks()); + } + + /** + * @test + */ + public function setSpaceAfterBlocksSetsSpaceAfterBlocks(): void + { + $value = ' '; + $this->subject->setSpaceAfterBlocks($value); + + self::assertSame($value, $this->subject->getSpaceAfterBlocks()); + } + + /** + * @test + */ + public function setSpaceAfterBlocksProvidesFluentInterface(): void + { + self::assertSame($this->subject, $this->subject->setSpaceAfterBlocks(' ')); + } + + /** + * @test + */ + public function getSpaceBetweenBlocksInitiallyReturnsNewline(): void + { + self::assertSame("\n", $this->subject->getSpaceBetweenBlocks()); + } + + /** + * @test + */ + public function setSpaceBetweenBlocksSetsSpaceBetweenBlocks(): void + { + $value = ' '; + $this->subject->setSpaceBetweenBlocks($value); + + self::assertSame($value, $this->subject->getSpaceBetweenBlocks()); + } + + /** + * @test + */ + public function setSpaceBetweenBlocksProvidesFluentInterface(): void + { + self::assertSame($this->subject, $this->subject->setSpaceBetweenBlocks(' ')); + } +} From dcb658c754b054ca34b1243d05d0bfb7a681492b Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sat, 1 Feb 2025 08:41:19 +0000 Subject: [PATCH 143/555] [FEATURE] Support `none` as color function component value (#859) Note that this keyword is only permitted in the "modern" syntax (and thus functions using it must be rendered in that syntax). --- CHANGELOG.md | 1 + src/Value/Color.php | 25 ++++++++++++--- tests/Unit/Value/ColorTest.php | 58 ++++++++++++++++++++++++++-------- 3 files changed, 66 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf3db03a..13d94fe3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Please also have a look at our - `rgb` and `rgba`, and `hsl` and `hsla` are now aliases (#797} - Parse color functions that use the "modern" syntax (#800) - Render RGB functions with "modern" syntax when required (#840) + - Support `none` as color function component value (#859) - Add a class diagram to the README (#482) - Add more tests (#449) diff --git a/src/Value/Color.php b/src/Value/Color.php index 8b913d54..c4d6a4ee 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -116,15 +116,20 @@ private static function parseColorFunction(ParserState $parserState): CSSFunctio } $containsVar = false; + $containsNone = false; $isLegacySyntax = false; $expectedArgumentCount = $parserState->strlen($colorModeForParsing); for ($argumentIndex = 0; $argumentIndex < $expectedArgumentCount; ++$argumentIndex) { $parserState->consumeWhiteSpace(); + $valueKey = $colorModeForParsing[$argumentIndex]; if ($parserState->comes('var')) { - $colorValues[$colorModeForParsing[$argumentIndex]] = CSSFunction::parseIdentifierOrFunction($parserState); + $colorValues[$valueKey] = CSSFunction::parseIdentifierOrFunction($parserState); $containsVar = true; + } elseif (!$isLegacySyntax && $parserState->comes('none')) { + $colorValues[$valueKey] = $parserState->parseIdentifier(); + $containsNone = true; } else { - $colorValues[$colorModeForParsing[$argumentIndex]] = Size::parse($parserState, true); + $colorValues[$valueKey] = Size::parse($parserState, true); } // This must be done first, to consume comments as well, so that the `comes` test will work. @@ -139,10 +144,10 @@ private static function parseColorFunction(ParserState $parserState): CSSFunctio break; } - // "Legacy" syntax is comma-delimited. + // "Legacy" syntax is comma-delimited, and does not allow the `none` keyword. // "Modern" syntax is space-delimited, with `/` as alpha delimiter. // They cannot be mixed. - if ($argumentIndex === 0) { + if ($argumentIndex === 0 && !$containsNone) { // An immediate closing parenthesis is not valid. if ($parserState->comes(')')) { throw new UnexpectedTokenException( @@ -291,7 +296,8 @@ private function renderAsHex(): string } /** - * The "legacy" syntax does not allow RGB colors to have a mixture of `percentage`s and `number`s. + * The "legacy" syntax does not allow RGB colors to have a mixture of `percentage`s and `number`s, + * and does not allow `none` as any component value. * * The "legacy" and "modern" monikers are part of the formal W3C syntax. * See the following for more information: @@ -306,6 +312,10 @@ private function renderAsHex(): string */ private function shouldRenderInModernSyntax(): bool { + if ($this->HasNoneAsComponentValue()) { + return true; + } + if (!$this->colorFunctionMayHaveMixedValueTypes($this->getRealName())) { return false; } @@ -337,6 +347,11 @@ private function shouldRenderInModernSyntax(): bool return $hasPercentage && $hasNumber; } + private function hasNoneAsComponentValue(): bool + { + return \in_array('none', $this->aComponents, true); + } + /** * Some color functions, such as `rgb`, * may have a mixture of `percentage`, `number`, or possibly other types in their arguments. diff --git a/tests/Unit/Value/ColorTest.php b/tests/Unit/Value/ColorTest.php index 5c502f82..f516d313 100644 --- a/tests/Unit/Value/ColorTest.php +++ b/tests/Unit/Value/ColorTest.php @@ -114,12 +114,18 @@ public static function provideValidColorAndExpectedRendering(): array 'rgb(0% 60% 0%)', 'rgb(0%,60%,0%)', ], - /* - 'modern rgb with none' => [ + 'modern rgb with none as red' => [ 'rgb(none 119 0)', 'rgb(none 119 0)', ], - //*/ + 'modern rgb with none as green' => [ + 'rgb(0 none 0)', + 'rgb(0 none 0)', + ], + 'modern rgb with none as blue' => [ + 'rgb(0 119 none)', + 'rgb(0 119 none)', + ], 'modern rgba with fractional alpha' => [ 'rgb(0 119 0 / 0.5)', 'rgba(0,119,0,.5)', @@ -148,12 +154,10 @@ public static function provideValidColorAndExpectedRendering(): array 'rgb(0% 60% 0% / 50%)', 'rgba(0%,60%,0%,50%)', ], - /* 'modern rgba with none as alpha' => [ 'rgb(0 119 0 / none)', - 'rgba(0 119 0 / none)', + 'rgba(0 119 0/none)', ], - //*/ 'legacy rgb with var for R' => [ 'rgb(var(--r), 119, 0)', 'rgb(var(--r),119,0)', @@ -310,22 +314,26 @@ public static function provideValidColorAndExpectedRendering(): array 'hsl(120 100% 25%)', 'hsl(120,100%,25%)', ], - /* - 'modern hsl with none' => [ + 'modern hsl with none as hue' => [ 'hsl(none 100% 25%)', 'hsl(none 100% 25%)', ], - //*/ + 'modern hsl with none as saturation' => [ + 'hsl(120 none 25%)', + 'hsl(120 none 25%)', + ], + 'modern hsl with none as lightness' => [ + 'hsl(120 100% none)', + 'hsl(120 100% none)', + ], 'modern hsla' => [ 'hsl(120 100% 25% / 0.5)', 'hsla(120,100%,25%,.5)', ], - /* 'modern hsla with none as alpha' => [ - 'hsl(120 100% 25% none)', - 'hsla(120 100% 25% none)', + 'hsl(120 100% 25% / none)', + 'hsla(120 100% 25%/none)', ], - //*/ ]; } @@ -386,6 +394,18 @@ public static function provideInvalidColor(): array 'rgb(255, 0px, 0)', ], //*/ + 'legacy rgb color with none as red' => [ + 'rgb(none, 0, 0)', + ], + 'legacy rgb color with none as green' => [ + 'rgb(255, none, 0)', + ], + 'legacy rgb color with none as blue' => [ + 'rgb(255, 0, none)', + ], + 'legacy rgba color with none as alpha' => [ + 'rgba(255, 0, 0, none)', + ], 'modern rgb color without slash separator for alpha' => [ 'rgb(255 0 0 0.5)', ], @@ -407,6 +427,18 @@ public static function provideInvalidColor(): array 'legacy hsl color with 5 arguments' => [ 'hsl(0, 100%, 50%, 0.5, 0)', ], + 'legacy hsl color with none as hue' => [ + 'hsl(none, 100%, 50%)', + ], + 'legacy hsl color with none as saturation' => [ + 'hsl(0, none, 50%)', + ], + 'legacy hsl color with none as lightness' => [ + 'hsl(0, 100%, none)', + ], + 'legacy hsla color with none as alpha' => [ + 'hsl(0, 100%, 50%, none)', + ], /* 'legacy hsl color without % for S/L units' => [ 'hsl(0, 1, 0.5)' From f019438dcf03b44c401e43354d10a9861a1178a8 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 1 Feb 2025 18:31:01 +0100 Subject: [PATCH 144/555] [CLEANUP] Avoid Hungarian notation for `sMediaQuery` (#860) Part of #756 --- src/CSSList/CSSList.php | 10 +++++----- src/Property/Import.php | 16 ++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 14c373ef..bb6426b7 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -162,15 +162,15 @@ private static function parseAtRule(ParserState $parserState) if ($sIdentifier === 'import') { $oLocation = URL::parse($parserState); $parserState->consumeWhiteSpace(); - $sMediaQuery = null; + $mediaQuery = null; if (!$parserState->comes(';')) { - $sMediaQuery = \trim($parserState->consumeUntil([';', ParserState::EOF])); - if ($sMediaQuery === '') { - $sMediaQuery = null; + $mediaQuery = \trim($parserState->consumeUntil([';', ParserState::EOF])); + if ($mediaQuery === '') { + $mediaQuery = null; } } $parserState->consumeUntil([';', ParserState::EOF], true, true); - return new Import($oLocation, $sMediaQuery, $iIdentifierLineNum); + return new Import($oLocation, $mediaQuery, $iIdentifierLineNum); } elseif ($sIdentifier === 'charset') { $oCharsetString = CSSString::parse($parserState); $parserState->consumeWhiteSpace(); diff --git a/src/Property/Import.php b/src/Property/Import.php index d48fa70d..80cd4c56 100644 --- a/src/Property/Import.php +++ b/src/Property/Import.php @@ -21,7 +21,7 @@ class Import implements AtRule /** * @var string */ - private $sMediaQuery; + private $mediaQuery; /** * @var int @@ -34,13 +34,13 @@ class Import implements AtRule protected $comments; /** - * @param string $sMediaQuery + * @param string $mediaQuery * @param int $lineNumber */ - public function __construct(URL $oLocation, $sMediaQuery, $lineNumber = 0) + public function __construct(URL $oLocation, $mediaQuery, $lineNumber = 0) { $this->oLocation = $oLocation; - $this->sMediaQuery = $sMediaQuery; + $this->mediaQuery = $mediaQuery; $this->lineNumber = $lineNumber; $this->comments = []; } @@ -77,7 +77,7 @@ public function __toString(): string public function render(OutputFormat $oOutputFormat): string { return $oOutputFormat->comments($this) . '@import ' . $this->oLocation->render($oOutputFormat) - . ($this->sMediaQuery === null ? '' : ' ' . $this->sMediaQuery) . ';'; + . ($this->mediaQuery === null ? '' : ' ' . $this->mediaQuery) . ';'; } public function atRuleName(): string @@ -91,8 +91,8 @@ public function atRuleName(): string public function atRuleArgs(): array { $aResult = [$this->oLocation]; - if ($this->sMediaQuery) { - \array_push($aResult, $this->sMediaQuery); + if ($this->mediaQuery) { + \array_push($aResult, $this->mediaQuery); } return $aResult; } @@ -126,6 +126,6 @@ public function setComments(array $comments): void */ public function getMediaQuery() { - return $this->sMediaQuery; + return $this->mediaQuery; } } From 02d859a7f1a4c4fbedc35757ab581e410fcc8d63 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 1 Feb 2025 18:35:30 +0100 Subject: [PATCH 145/555] [CLEANUP] Avoid Hungarian notation for `bLenientParsing` (#861) Keep the name of the public property in `Settings` unchanged, though, as it currently probably is part of the API. Part of #756. --- src/CSSList/CSSList.php | 6 +++--- src/Settings.php | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index bb6426b7..a701cd91 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -68,12 +68,12 @@ public static function parseList(ParserState $parserState, CSSList $list): void if (\is_string($parserState)) { $parserState = new ParserState($parserState, Settings::create()); } - $bLenientParsing = $parserState->getSettings()->bLenientParsing; + $usesLenientParsing = $parserState->getSettings()->bLenientParsing; $comments = []; while (!$parserState->isEnd()) { $comments = \array_merge($comments, $parserState->consumeWhiteSpace()); $listItem = null; - if ($bLenientParsing) { + if ($usesLenientParsing) { try { $listItem = self::parseListItem($parserState, $list); } catch (UnexpectedTokenException $e) { @@ -93,7 +93,7 @@ public static function parseList(ParserState $parserState, CSSList $list): void $comments = $parserState->consumeWhiteSpace(); } $list->addComments($comments); - if (!$bIsRoot && !$bLenientParsing) { + if (!$bIsRoot && !$usesLenientParsing) { throw new SourceException('Unexpected end of document', $parserState->currentLine()); } } diff --git a/src/Settings.php b/src/Settings.php index 17e5fa00..6dd4d3e4 100644 --- a/src/Settings.php +++ b/src/Settings.php @@ -80,13 +80,13 @@ public function withDefaultCharset($sDefaultCharset): self /** * Configures whether the parser should silently ignore invalid rules. * - * @param bool $bLenientParsing + * @param bool $usesLenientParsing * * @return $this fluent interface */ - public function withLenientParsing($bLenientParsing = true): self + public function withLenientParsing($usesLenientParsing = true): self { - $this->bLenientParsing = $bLenientParsing; + $this->bLenientParsing = $usesLenientParsing; return $this; } From 95005c964351f07d1d0eb473823f359db2107d19 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 1 Feb 2025 20:21:48 +0100 Subject: [PATCH 146/555] [TASK] Add accessor tests for `OutputFormat` (part 2) (#862) Currently, this class uses magic getters and setters. These should be proper methods instead. But first, we should have tests for the accessors. This is part 2. Co-authored-by: JakeQZ --- tests/Unit/OutputFormatTest.php | 326 ++++++++++++++++++++++++++++++++ 1 file changed, 326 insertions(+) diff --git a/tests/Unit/OutputFormatTest.php b/tests/Unit/OutputFormatTest.php index 482be82f..ddb87579 100644 --- a/tests/Unit/OutputFormatTest.php +++ b/tests/Unit/OutputFormatTest.php @@ -304,4 +304,330 @@ public function setSpaceBetweenBlocksProvidesFluentInterface(): void { self::assertSame($this->subject, $this->subject->setSpaceBetweenBlocks(' ')); } + + /** + * @test + */ + public function getBeforeAtRuleBlockInitiallyReturnsEmptyString(): void + { + self::assertSame('', $this->subject->getBeforeAtRuleBlock()); + } + + /** + * @test + */ + public function setBeforeAtRuleBlockSetsBeforeAtRuleBlock(): void + { + $value = ' '; + $this->subject->setBeforeAtRuleBlock($value); + + self::assertSame($value, $this->subject->getBeforeAtRuleBlock()); + } + + /** + * @test + */ + public function setBeforeAtRuleBlockProvidesFluentInterface(): void + { + self::assertSame($this->subject, $this->subject->setBeforeAtRuleBlock(' ')); + } + + /** + * @test + */ + public function getAfterAtRuleBlockInitiallyReturnsEmptyString(): void + { + self::assertSame('', $this->subject->getAfterAtRuleBlock()); + } + + /** + * @test + */ + public function setAfterAtRuleBlockSetsAfterAtRuleBlock(): void + { + $value = ' '; + $this->subject->setAfterAtRuleBlock($value); + + self::assertSame($value, $this->subject->getAfterAtRuleBlock()); + } + + /** + * @test + */ + public function setAfterAtRuleBlockProvidesFluentInterface(): void + { + self::assertSame($this->subject, $this->subject->setAfterAtRuleBlock(' ')); + } + + /** + * @test + */ + public function getSpaceBeforeSelectorSeparatorInitiallyReturnsEmptyString(): void + { + self::assertSame('', $this->subject->getSpaceBeforeSelectorSeparator()); + } + + /** + * @test + */ + public function setSpaceBeforeSelectorSeparatorSetsSpaceBeforeSelectorSeparator(): void + { + $value = ' '; + $this->subject->setSpaceBeforeSelectorSeparator($value); + + self::assertSame($value, $this->subject->getSpaceBeforeSelectorSeparator()); + } + + /** + * @test + */ + public function setSpaceBeforeSelectorSeparatorProvidesFluentInterface(): void + { + self::assertSame($this->subject, $this->subject->setSpaceBeforeSelectorSeparator(' ')); + } + + /** + * @test + */ + public function getSpaceAfterSelectorSeparatorInitiallyReturnsSpace(): void + { + self::assertSame(' ', $this->subject->getSpaceAfterSelectorSeparator()); + } + + /** + * @test + */ + public function setSpaceAfterSelectorSeparatorSetsSpaceAfterSelectorSeparator(): void + { + $value = ' '; + $this->subject->setSpaceAfterSelectorSeparator($value); + + self::assertSame($value, $this->subject->getSpaceAfterSelectorSeparator()); + } + + /** + * @test + */ + public function setSpaceAfterSelectorSeparatorProvidesFluentInterface(): void + { + self::assertSame($this->subject, $this->subject->setSpaceAfterSelectorSeparator(' ')); + } + + /** + * @test + */ + public function getSpaceBeforeListArgumentSeparatorInitiallyReturnsEmptyString(): void + { + self::assertSame('', $this->subject->getSpaceBeforeListArgumentSeparator()); + } + + /** + * @test + */ + public function setSpaceBeforeListArgumentSeparatorSetsSpaceBeforeListArgumentSeparator(): void + { + $value = ' '; + $this->subject->setSpaceBeforeListArgumentSeparator($value); + + self::assertSame($value, $this->subject->getSpaceBeforeListArgumentSeparator()); + } + + /** + * @test + */ + public function setSpaceBeforeListArgumentSeparatorProvidesFluentInterface(): void + { + self::assertSame($this->subject, $this->subject->setSpaceBeforeListArgumentSeparator(' ')); + } + + /** + * @test + */ + public function getSpaceAfterListArgumentSeparatorInitiallyReturnsEmptyString(): void + { + self::assertSame('', $this->subject->getSpaceAfterListArgumentSeparator()); + } + + /** + * @test + */ + public function setSpaceAfterListArgumentSeparatorSetsSpaceAfterListArgumentSeparator(): void + { + $value = ' '; + $this->subject->setSpaceAfterListArgumentSeparator($value); + + self::assertSame($value, $this->subject->getSpaceAfterListArgumentSeparator()); + } + + /** + * @test + */ + public function setSpaceAfterListArgumentSeparatorProvidesFluentInterface(): void + { + self::assertSame($this->subject, $this->subject->setSpaceAfterListArgumentSeparator(' ')); + } + + /** + * @test + */ + public function getBeforeDeclarationBlockInitiallyReturnsEmptyString(): void + { + self::assertSame('', $this->subject->getBeforeDeclarationBlock()); + } + + /** + * @test + */ + public function setBeforeDeclarationBlockSetsBeforeDeclarationBlock(): void + { + $value = ' '; + $this->subject->setBeforeDeclarationBlock($value); + + self::assertSame($value, $this->subject->getBeforeDeclarationBlock()); + } + + /** + * @test + */ + public function setBeforeDeclarationBlockProvidesFluentInterface(): void + { + self::assertSame($this->subject, $this->subject->setBeforeDeclarationBlock(' ')); + } + + /** + * @test + */ + public function getAfterDeclarationBlockSelectorsInitiallyReturnsEmptyString(): void + { + self::assertSame('', $this->subject->getAfterDeclarationBlockSelectors()); + } + + /** + * @test + */ + public function setAfterDeclarationBlockSelectorsSetsAfterDeclarationBlockSelectors(): void + { + $value = ' '; + $this->subject->setAfterDeclarationBlockSelectors($value); + + self::assertSame($value, $this->subject->getAfterDeclarationBlockSelectors()); + } + + /** + * @test + */ + public function setAfterDeclarationBlockSelectorsProvidesFluentInterface(): void + { + self::assertSame($this->subject, $this->subject->setAfterDeclarationBlockSelectors(' ')); + } + + /** + * @test + */ + public function getAfterDeclarationBlockInitiallyReturnsEmptyString(): void + { + self::assertSame('', $this->subject->getAfterDeclarationBlock()); + } + + /** + * @test + */ + public function setAfterDeclarationBlockSetsAfterDeclarationBlock(): void + { + $value = ' '; + $this->subject->setAfterDeclarationBlock($value); + + self::assertSame($value, $this->subject->getAfterDeclarationBlock()); + } + + /** + * @test + */ + public function setAfterDeclarationBlockProvidesFluentInterface(): void + { + self::assertSame($this->subject, $this->subject->setAfterDeclarationBlock(' ')); + } + + /** + * @test + */ + public function getIndentationInitiallyReturnsTab(): void + { + self::assertSame("\t", $this->subject->getIndentation()); + } + + /** + * @test + */ + public function setIndentationSetsIndentation(): void + { + $value = ' '; + $this->subject->setIndentation($value); + + self::assertSame($value, $this->subject->getIndentation()); + } + + /** + * @test + */ + public function setIndentationProvidesFluentInterface(): void + { + self::assertSame($this->subject, $this->subject->setIndentation(' ')); + } + + /** + * @test + */ + public function getIgnoreExceptionsInitiallyReturnsFalse(): void + { + self::assertFalse($this->subject->getIgnoreExceptions()); + } + + /** + * @test + * + * @dataProvider provideBooleans + */ + public function setIgnoreExceptionsSetsIgnoreExceptions(bool $value): void + { + $this->subject->setIgnoreExceptions($value); + + self::assertSame($value, $this->subject->getIgnoreExceptions()); + } + + /** + * @test + */ + public function setIgnoreExceptionsProvidesFluentInterface(): void + { + self::assertSame($this->subject, $this->subject->setIgnoreExceptions(true)); + } + + /** + * @test + */ + public function getRenderCommentsInitiallyReturnsFalse(): void + { + self::assertFalse($this->subject->getRenderComments()); + } + + /** + * @test + * + * @dataProvider provideBooleans + */ + public function setRenderCommentsSetsRenderComments(bool $value): void + { + $this->subject->setRenderComments($value); + + self::assertSame($value, $this->subject->getRenderComments()); + } + + /** + * @test + */ + public function setRenderCommentsProvidesFluentInterface(): void + { + self::assertSame($this->subject, $this->subject->setRenderComments(true)); + } } From b5847603c30582dccc644af201fcad81f973162f Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 2 Feb 2025 10:47:25 +0100 Subject: [PATCH 147/555] [CLEANUP] Avoid Hungarian notation for `sIdentifier` (#863) Part of #756 --- src/CSSList/CSSList.php | 26 +++++++++++++------------- src/Value/URL.php | 6 +++--- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index a701cd91..8d4a1c10 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -156,10 +156,10 @@ private static function parseListItem(ParserState $parserState, CSSList $list) private static function parseAtRule(ParserState $parserState) { $parserState->consume('@'); - $sIdentifier = $parserState->parseIdentifier(); + $identifier = $parserState->parseIdentifier(); $iIdentifierLineNum = $parserState->currentLine(); $parserState->consumeWhiteSpace(); - if ($sIdentifier === 'import') { + if ($identifier === 'import') { $oLocation = URL::parse($parserState); $parserState->consumeWhiteSpace(); $mediaQuery = null; @@ -171,21 +171,21 @@ private static function parseAtRule(ParserState $parserState) } $parserState->consumeUntil([';', ParserState::EOF], true, true); return new Import($oLocation, $mediaQuery, $iIdentifierLineNum); - } elseif ($sIdentifier === 'charset') { + } elseif ($identifier === 'charset') { $oCharsetString = CSSString::parse($parserState); $parserState->consumeWhiteSpace(); $parserState->consumeUntil([';', ParserState::EOF], true, true); return new Charset($oCharsetString, $iIdentifierLineNum); - } elseif (self::identifierIs($sIdentifier, 'keyframes')) { + } elseif (self::identifierIs($identifier, 'keyframes')) { $oResult = new KeyFrame($iIdentifierLineNum); - $oResult->setVendorKeyFrame($sIdentifier); + $oResult->setVendorKeyFrame($identifier); $oResult->setAnimationName(\trim($parserState->consumeUntil('{', false, true))); CSSList::parseList($parserState, $oResult); if ($parserState->comes('}')) { $parserState->consume('}'); } return $oResult; - } elseif ($sIdentifier === 'namespace') { + } elseif ($identifier === 'namespace') { $sPrefix = null; $mUrl = Value::parsePrimitiveValue($parserState); if (!$parserState->comes(';')) { @@ -217,16 +217,16 @@ private static function parseAtRule(ParserState $parserState) } $bUseRuleSet = true; foreach (\explode('/', AtRule::BLOCK_RULES) as $sBlockRuleName) { - if (self::identifierIs($sIdentifier, $sBlockRuleName)) { + if (self::identifierIs($identifier, $sBlockRuleName)) { $bUseRuleSet = false; break; } } if ($bUseRuleSet) { - $oAtRule = new AtRuleSet($sIdentifier, $sArgs, $iIdentifierLineNum); + $oAtRule = new AtRuleSet($identifier, $sArgs, $iIdentifierLineNum); RuleSet::parseRuleSet($parserState, $oAtRule); } else { - $oAtRule = new AtRuleBlockList($sIdentifier, $sArgs, $iIdentifierLineNum); + $oAtRule = new AtRuleBlockList($identifier, $sArgs, $iIdentifierLineNum); CSSList::parseList($parserState, $oAtRule); if ($parserState->comes('}')) { $parserState->consume('}'); @@ -240,12 +240,12 @@ private static function parseAtRule(ParserState $parserState) * Tests an identifier for a given value. Since identifiers are all keywords, they can be vendor-prefixed. * We need to check for these versions too. * - * @param string $sIdentifier + * @param string $identifier */ - private static function identifierIs($sIdentifier, string $sMatch): bool + private static function identifierIs($identifier, string $sMatch): bool { - return (\strcasecmp($sIdentifier, $sMatch) === 0) - ?: \preg_match("/^(-\\w+-)?$sMatch$/i", $sIdentifier) === 1; + return (\strcasecmp($identifier, $sMatch) === 0) + ?: \preg_match("/^(-\\w+-)?$sMatch$/i", $identifier) === 1; } /** diff --git a/src/Value/URL.php b/src/Value/URL.php index cefaef53..6084417a 100644 --- a/src/Value/URL.php +++ b/src/Value/URL.php @@ -37,15 +37,15 @@ public function __construct(CSSString $oURL, $lineNumber = 0) public static function parse(ParserState $parserState): URL { $oAnchor = $parserState->anchor(); - $sIdentifier = ''; + $identifier = ''; for ($i = 0; $i < 3; $i++) { $sChar = $parserState->parseCharacter(true); if ($sChar === null) { break; } - $sIdentifier .= $sChar; + $identifier .= $sChar; } - $bUseUrl = $parserState->streql($sIdentifier, 'url'); + $bUseUrl = $parserState->streql($identifier, 'url'); if ($bUseUrl) { $parserState->consumeWhiteSpace(); $parserState->consume('('); From 8e0d0af1dfa4c4c267f62e7f751ee0b3b5ee1d26 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 2 Feb 2025 18:28:20 +0100 Subject: [PATCH 148/555] [CLEANUP] Avoid Hungarian notation for `oLocation` (#864) Part of #756 --- src/CSSList/CSSList.php | 4 ++-- src/Property/Import.php | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 8d4a1c10..3d606cc3 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -160,7 +160,7 @@ private static function parseAtRule(ParserState $parserState) $iIdentifierLineNum = $parserState->currentLine(); $parserState->consumeWhiteSpace(); if ($identifier === 'import') { - $oLocation = URL::parse($parserState); + $location = URL::parse($parserState); $parserState->consumeWhiteSpace(); $mediaQuery = null; if (!$parserState->comes(';')) { @@ -170,7 +170,7 @@ private static function parseAtRule(ParserState $parserState) } } $parserState->consumeUntil([';', ParserState::EOF], true, true); - return new Import($oLocation, $mediaQuery, $iIdentifierLineNum); + return new Import($location, $mediaQuery, $iIdentifierLineNum); } elseif ($identifier === 'charset') { $oCharsetString = CSSString::parse($parserState); $parserState->consumeWhiteSpace(); diff --git a/src/Property/Import.php b/src/Property/Import.php index 80cd4c56..636ec58b 100644 --- a/src/Property/Import.php +++ b/src/Property/Import.php @@ -16,7 +16,7 @@ class Import implements AtRule /** * @var URL */ - private $oLocation; + private $location; /** * @var string @@ -37,9 +37,9 @@ class Import implements AtRule * @param string $mediaQuery * @param int $lineNumber */ - public function __construct(URL $oLocation, $mediaQuery, $lineNumber = 0) + public function __construct(URL $location, $mediaQuery, $lineNumber = 0) { - $this->oLocation = $oLocation; + $this->location = $location; $this->mediaQuery = $mediaQuery; $this->lineNumber = $lineNumber; $this->comments = []; @@ -54,11 +54,11 @@ public function getLineNo() } /** - * @param URL $oLocation + * @param URL $location */ - public function setLocation($oLocation): void + public function setLocation($location): void { - $this->oLocation = $oLocation; + $this->location = $location; } /** @@ -66,7 +66,7 @@ public function setLocation($oLocation): void */ public function getLocation() { - return $this->oLocation; + return $this->location; } public function __toString(): string @@ -76,7 +76,7 @@ public function __toString(): string public function render(OutputFormat $oOutputFormat): string { - return $oOutputFormat->comments($this) . '@import ' . $this->oLocation->render($oOutputFormat) + return $oOutputFormat->comments($this) . '@import ' . $this->location->render($oOutputFormat) . ($this->mediaQuery === null ? '' : ' ' . $this->mediaQuery) . ';'; } @@ -90,7 +90,7 @@ public function atRuleName(): string */ public function atRuleArgs(): array { - $aResult = [$this->oLocation]; + $aResult = [$this->location]; if ($this->mediaQuery) { \array_push($aResult, $this->mediaQuery); } From fb1d648e851eaaaccf6be34ebffbcb28d08eb272 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 2 Feb 2025 18:29:48 +0100 Subject: [PATCH 149/555] [CLEANUP] Avoid Hungarian notation for `oResult` (#865) Part of #756 --- src/CSSList/CSSList.php | 10 +++++----- src/RuleSet/DeclarationBlock.php | 10 +++++----- src/Value/CSSFunction.php | 4 ++-- src/Value/URL.php | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 3d606cc3..721f8463 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -177,14 +177,14 @@ private static function parseAtRule(ParserState $parserState) $parserState->consumeUntil([';', ParserState::EOF], true, true); return new Charset($oCharsetString, $iIdentifierLineNum); } elseif (self::identifierIs($identifier, 'keyframes')) { - $oResult = new KeyFrame($iIdentifierLineNum); - $oResult->setVendorKeyFrame($identifier); - $oResult->setAnimationName(\trim($parserState->consumeUntil('{', false, true))); - CSSList::parseList($parserState, $oResult); + $result = new KeyFrame($iIdentifierLineNum); + $result->setVendorKeyFrame($identifier); + $result->setAnimationName(\trim($parserState->consumeUntil('{', false, true))); + CSSList::parseList($parserState, $result); if ($parserState->comes('}')) { $parserState->consume('}'); } - return $oResult; + return $result; } elseif ($identifier === 'namespace') { $sPrefix = null; $mUrl = Value::parsePrimitiveValue($parserState); diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 08c08f00..231476cf 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -49,7 +49,7 @@ public function __construct($lineNumber = 0) public static function parse(ParserState $parserState, $list = null) { $comments = []; - $oResult = new DeclarationBlock($parserState->currentLine()); + $result = new DeclarationBlock($parserState->currentLine()); try { $aSelectorParts = []; $sStringWrapperChar = false; @@ -64,7 +64,7 @@ public static function parse(ParserState $parserState, $list = null) } } } while (!\in_array($parserState->peek(), ['{', '}'], true) || $sStringWrapperChar !== false); - $oResult->setSelectors(\implode('', $aSelectorParts), $list); + $result->setSelectors(\implode('', $aSelectorParts), $list); if ($parserState->comes('{')) { $parserState->consume(1); } @@ -78,9 +78,9 @@ public static function parse(ParserState $parserState, $list = null) throw $e; } } - $oResult->setComments($comments); - RuleSet::parseRuleSet($parserState, $oResult); - return $oResult; + $result->setComments($comments); + RuleSet::parseRuleSet($parserState, $result); + return $result; } /** diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index ac0d14f1..8e6372f3 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -46,10 +46,10 @@ public static function parse(ParserState $parserState, bool $bIgnoreCase = false $parserState->consume('('); $mArguments = self::parseArguments($parserState); - $oResult = new CSSFunction($sName, $mArguments, ',', $parserState->currentLine()); + $result = new CSSFunction($sName, $mArguments, ',', $parserState->currentLine()); $parserState->consume(')'); - return $oResult; + return $result; } /** diff --git a/src/Value/URL.php b/src/Value/URL.php index 6084417a..018abefc 100644 --- a/src/Value/URL.php +++ b/src/Value/URL.php @@ -53,12 +53,12 @@ public static function parse(ParserState $parserState): URL $oAnchor->backtrack(); } $parserState->consumeWhiteSpace(); - $oResult = new URL(CSSString::parse($parserState), $parserState->currentLine()); + $result = new URL(CSSString::parse($parserState), $parserState->currentLine()); if ($bUseUrl) { $parserState->consumeWhiteSpace(); $parserState->consume(')'); } - return $oResult; + return $result; } public function setURL(CSSString $oURL): void From 5fe6db9f4811fe20e97fd41b7e3cf6a71a3e4116 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 2 Feb 2025 18:44:52 +0100 Subject: [PATCH 150/555] [TASK] Add more `OutputFormat` property accessor tests (#867) --- tests/Unit/OutputFormatTest.php | 54 +++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/Unit/OutputFormatTest.php b/tests/Unit/OutputFormatTest.php index ddb87579..84fd9f23 100644 --- a/tests/Unit/OutputFormatTest.php +++ b/tests/Unit/OutputFormatTest.php @@ -467,6 +467,33 @@ public function setSpaceAfterListArgumentSeparatorProvidesFluentInterface(): voi self::assertSame($this->subject, $this->subject->setSpaceAfterListArgumentSeparator(' ')); } + /** + * @test + */ + public function getSpaceBeforeOpeningBraceInitiallyReturnsSpace(): void + { + self::assertSame(' ', $this->subject->getSpaceBeforeOpeningBrace()); + } + + /** + * @test + */ + public function setSpaceBeforeOpeningBraceSetsSpaceBeforeOpeningBrace(): void + { + $value = "\t"; + $this->subject->setSpaceBeforeOpeningBrace($value); + + self::assertSame($value, $this->subject->getSpaceBeforeOpeningBrace()); + } + + /** + * @test + */ + public function setSpaceBeforeOpeningBraceProvidesFluentInterface(): void + { + self::assertSame($this->subject, $this->subject->setSpaceBeforeOpeningBrace(' ')); + } + /** * @test */ @@ -630,4 +657,31 @@ public function setRenderCommentsProvidesFluentInterface(): void { self::assertSame($this->subject, $this->subject->setRenderComments(true)); } + + /** + * @test + */ + public function getIndentationLevelInitiallyReturnsZero(): void + { + self::assertSame(0, $this->subject->getIndentationLevel()); + } + + /** + * @test + */ + public function setIndentationLevelSetsIndentationLevel(): void + { + $value = 4; + $this->subject->setIndentationLevel($value); + + self::assertSame($value, $this->subject->getIndentationLevel()); + } + + /** + * @test + */ + public function setIndentationLevelProvidesFluentInterface(): void + { + self::assertSame($this->subject, $this->subject->setIndentationLevel(4)); + } } From be1ea196e6d018b3195c8df72231d11fd24ff708 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 2 Feb 2025 18:56:43 +0100 Subject: [PATCH 151/555] [CLEANUP] Avoid Hungarian notation in `CSSList` (#872) Part of #756. --- src/CSSList/CSSList.php | 142 ++++++++++++++++++++-------------------- 1 file changed, 71 insertions(+), 71 deletions(-) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 721f8463..f7be79fd 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -64,7 +64,7 @@ public function __construct($lineNumber = 0) */ public static function parseList(ParserState $parserState, CSSList $list): void { - $bIsRoot = $list instanceof Document; + $isRoot = $list instanceof Document; if (\is_string($parserState)) { $parserState = new ParserState($parserState, Settings::create()); } @@ -93,7 +93,7 @@ public static function parseList(ParserState $parserState, CSSList $list): void $comments = $parserState->consumeWhiteSpace(); } $list->addComments($comments); - if (!$bIsRoot && !$usesLenientParsing) { + if (!$isRoot && !$usesLenientParsing) { throw new SourceException('Unexpected end of document', $parserState->currentLine()); } } @@ -107,11 +107,11 @@ public static function parseList(ParserState $parserState, CSSList $list): void */ private static function parseListItem(ParserState $parserState, CSSList $list) { - $bIsRoot = $list instanceof Document; + $isRoot = $list instanceof Document; if ($parserState->comes('@')) { - $oAtRule = self::parseAtRule($parserState); - if ($oAtRule instanceof Charset) { - if (!$bIsRoot) { + $atRule = self::parseAtRule($parserState); + if ($atRule instanceof Charset) { + if (!$isRoot) { throw new UnexpectedTokenException( '@charset may only occur in root document', '', @@ -127,11 +127,11 @@ private static function parseListItem(ParserState $parserState, CSSList $list) $parserState->currentLine() ); } - $parserState->setCharset($oAtRule->getCharset()); + $parserState->setCharset($atRule->getCharset()); } - return $oAtRule; + return $atRule; } elseif ($parserState->comes('}')) { - if ($bIsRoot) { + if ($isRoot) { if ($parserState->getSettings()->bLenientParsing) { return DeclarationBlock::parse($parserState); } else { @@ -157,7 +157,7 @@ private static function parseAtRule(ParserState $parserState) { $parserState->consume('@'); $identifier = $parserState->parseIdentifier(); - $iIdentifierLineNum = $parserState->currentLine(); + $identifierLineNumber = $parserState->currentLine(); $parserState->consumeWhiteSpace(); if ($identifier === 'import') { $location = URL::parse($parserState); @@ -170,14 +170,14 @@ private static function parseAtRule(ParserState $parserState) } } $parserState->consumeUntil([';', ParserState::EOF], true, true); - return new Import($location, $mediaQuery, $iIdentifierLineNum); + return new Import($location, $mediaQuery, $identifierLineNumber); } elseif ($identifier === 'charset') { - $oCharsetString = CSSString::parse($parserState); + $charsetString = CSSString::parse($parserState); $parserState->consumeWhiteSpace(); $parserState->consumeUntil([';', ParserState::EOF], true, true); - return new Charset($oCharsetString, $iIdentifierLineNum); + return new Charset($charsetString, $identifierLineNumber); } elseif (self::identifierIs($identifier, 'keyframes')) { - $result = new KeyFrame($iIdentifierLineNum); + $result = new KeyFrame($identifierLineNumber); $result->setVendorKeyFrame($identifier); $result->setAnimationName(\trim($parserState->consumeUntil('{', false, true))); CSSList::parseList($parserState, $result); @@ -187,52 +187,52 @@ private static function parseAtRule(ParserState $parserState) return $result; } elseif ($identifier === 'namespace') { $sPrefix = null; - $mUrl = Value::parsePrimitiveValue($parserState); + $url = Value::parsePrimitiveValue($parserState); if (!$parserState->comes(';')) { - $sPrefix = $mUrl; - $mUrl = Value::parsePrimitiveValue($parserState); + $sPrefix = $url; + $url = Value::parsePrimitiveValue($parserState); } $parserState->consumeUntil([';', ParserState::EOF], true, true); if ($sPrefix !== null && !\is_string($sPrefix)) { - throw new UnexpectedTokenException('Wrong namespace prefix', $sPrefix, 'custom', $iIdentifierLineNum); + throw new UnexpectedTokenException('Wrong namespace prefix', $sPrefix, 'custom', $identifierLineNumber); } - if (!($mUrl instanceof CSSString || $mUrl instanceof URL)) { + if (!($url instanceof CSSString || $url instanceof URL)) { throw new UnexpectedTokenException( 'Wrong namespace url of invalid type', - $mUrl, + $url, 'custom', - $iIdentifierLineNum + $identifierLineNumber ); } - return new CSSNamespace($mUrl, $sPrefix, $iIdentifierLineNum); + return new CSSNamespace($url, $sPrefix, $identifierLineNumber); } else { // Unknown other at rule (font-face or such) - $sArgs = \trim($parserState->consumeUntil('{', false, true)); - if (\substr_count($sArgs, '(') != \substr_count($sArgs, ')')) { + $arguments = \trim($parserState->consumeUntil('{', false, true)); + if (\substr_count($arguments, '(') != \substr_count($arguments, ')')) { if ($parserState->getSettings()->bLenientParsing) { return null; } else { throw new SourceException('Unmatched brace count in media query', $parserState->currentLine()); } } - $bUseRuleSet = true; - foreach (\explode('/', AtRule::BLOCK_RULES) as $sBlockRuleName) { - if (self::identifierIs($identifier, $sBlockRuleName)) { - $bUseRuleSet = false; + $useRuleSet = true; + foreach (\explode('/', AtRule::BLOCK_RULES) as $blockRuleName) { + if (self::identifierIs($identifier, $blockRuleName)) { + $useRuleSet = false; break; } } - if ($bUseRuleSet) { - $oAtRule = new AtRuleSet($identifier, $sArgs, $iIdentifierLineNum); - RuleSet::parseRuleSet($parserState, $oAtRule); + if ($useRuleSet) { + $atRule = new AtRuleSet($identifier, $arguments, $identifierLineNumber); + RuleSet::parseRuleSet($parserState, $atRule); } else { - $oAtRule = new AtRuleBlockList($identifier, $sArgs, $iIdentifierLineNum); - CSSList::parseList($parserState, $oAtRule); + $atRule = new AtRuleBlockList($identifier, $arguments, $identifierLineNumber); + CSSList::parseList($parserState, $atRule); if ($parserState->comes('}')) { $parserState->consume('}'); } } - return $oAtRule; + return $atRule; } } @@ -242,10 +242,10 @@ private static function parseAtRule(ParserState $parserState) * * @param string $identifier */ - private static function identifierIs($identifier, string $sMatch): bool + private static function identifierIs($identifier, string $match): bool { - return (\strcasecmp($identifier, $sMatch) === 0) - ?: \preg_match("/^(-\\w+-)?$sMatch$/i", $identifier) === 1; + return (\strcasecmp($identifier, $match) === 0) + ?: \preg_match("/^(-\\w+-)?$match$/i", $identifier) === 1; } /** @@ -279,13 +279,13 @@ public function append($item): void /** * Splices the list of contents. * - * @param int $iOffset - * @param int $iLength - * @param array $mReplacement + * @param int $offset + * @param int $length + * @param array $replacement */ - public function splice($iOffset, $iLength = null, $mReplacement = null): void + public function splice($offset, $length = null, $replacement = null): void { - \array_splice($this->contents, $iOffset, $iLength, $mReplacement); + \array_splice($this->contents, $offset, $length, $replacement); } /** @@ -360,36 +360,36 @@ public function setContents(array $contents): void /** * Removes a declaration block from the CSS list if it matches all given selectors. * - * @param DeclarationBlock|array|string $mSelector the selectors to match - * @param bool $bRemoveAll whether to stop at the first declaration block found or remove all blocks + * @param DeclarationBlock|array|string $selectors the selectors to match + * @param bool $removeAll whether to stop at the first declaration block found or remove all blocks */ - public function removeDeclarationBlockBySelector($mSelector, $bRemoveAll = false): void + public function removeDeclarationBlockBySelector($selectors, $removeAll = false): void { - if ($mSelector instanceof DeclarationBlock) { - $mSelector = $mSelector->getSelectors(); + if ($selectors instanceof DeclarationBlock) { + $selectors = $selectors->getSelectors(); } - if (!\is_array($mSelector)) { - $mSelector = \explode(',', $mSelector); + if (!\is_array($selectors)) { + $selectors = \explode(',', $selectors); } - foreach ($mSelector as $key => &$mSel) { - if (!($mSel instanceof Selector)) { - if (!Selector::isValid($mSel)) { + foreach ($selectors as $key => &$selector) { + if (!($selector instanceof Selector)) { + if (!Selector::isValid($selector)) { throw new UnexpectedTokenException( "Selector did not match '" . Selector::SELECTOR_VALIDATION_RX . "'.", - $mSel, + $selector, 'custom' ); } - $mSel = new Selector($mSel); + $selector = new Selector($selector); } } foreach ($this->contents as $key => $item) { if (!($item instanceof DeclarationBlock)) { continue; } - if ($item->getSelectors() == $mSelector) { + if ($item->getSelectors() == $selectors) { unset($this->contents[$key]); - if (!$bRemoveAll) { + if (!$removeAll) { return; } } @@ -406,34 +406,34 @@ public function __toString(): string */ protected function renderListContents(OutputFormat $oOutputFormat) { - $sResult = ''; - $bIsFirst = true; - $oNextLevel = $oOutputFormat; + $result = ''; + $isFirst = true; + $nextLevelFormat = $oOutputFormat; if (!$this->isRootList()) { - $oNextLevel = $oOutputFormat->nextLevel(); + $nextLevelFormat = $oOutputFormat->nextLevel(); } foreach ($this->contents as $listItem) { - $sRendered = $oOutputFormat->safely(static function () use ($oNextLevel, $listItem): string { - return $listItem->render($oNextLevel); + $renderedCss = $oOutputFormat->safely(static function () use ($nextLevelFormat, $listItem): string { + return $listItem->render($nextLevelFormat); }); - if ($sRendered === null) { + if ($renderedCss === null) { continue; } - if ($bIsFirst) { - $bIsFirst = false; - $sResult .= $oNextLevel->spaceBeforeBlocks(); + if ($isFirst) { + $isFirst = false; + $result .= $nextLevelFormat->spaceBeforeBlocks(); } else { - $sResult .= $oNextLevel->spaceBetweenBlocks(); + $result .= $nextLevelFormat->spaceBetweenBlocks(); } - $sResult .= $sRendered; + $result .= $renderedCss; } - if (!$bIsFirst) { + if (!$isFirst) { // Had some output - $sResult .= $oOutputFormat->spaceAfterBlocks(); + $result .= $oOutputFormat->spaceAfterBlocks(); } - return $sResult; + return $result; } /** From 19cccefae46e86ec4dcc850c0ade2e93604bc0aa Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 2 Feb 2025 19:29:33 +0100 Subject: [PATCH 152/555] [TASK] Add more tests for `OutputFormat` (#868) --- tests/Unit/OutputFormatTest.php | 178 ++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) diff --git a/tests/Unit/OutputFormatTest.php b/tests/Unit/OutputFormatTest.php index 84fd9f23..512df67f 100644 --- a/tests/Unit/OutputFormatTest.php +++ b/tests/Unit/OutputFormatTest.php @@ -6,6 +6,7 @@ use PHPUnit\Framework\TestCase; use Sabberworm\CSS\OutputFormat; +use Sabberworm\CSS\OutputFormatter; /** * @covers \Sabberworm\CSS\OutputFormat @@ -684,4 +685,181 @@ public function setIndentationLevelProvidesFluentInterface(): void { self::assertSame($this->subject, $this->subject->setIndentationLevel(4)); } + + /** + * @test + */ + public function indentWithTabsByDefaultSetsIndentationToOneTab(): void + { + $this->subject->indentWithTabs(); + + self::assertSame("\t", $this->subject->getIndentation()); + } + + /** + * @return array, 1: string}> + */ + public static function provideTabIndentation(): array + { + return [ + 'zero tabs' => [0, ''], + 'one tab' => [1, "\t"], + 'two tabs' => [2, "\t\t"], + 'three tabs' => [3, "\t\t\t"], + ]; + } + + /** + * @test + * @dataProvider provideTabIndentation + */ + public function indentWithTabsSetsIndentationToTheProvidedNumberOfTabs( + int $numberOfTabs, + string $expectedIndentation + ): void { + $this->subject->indentWithTabs($numberOfTabs); + + self::assertSame($expectedIndentation, $this->subject->getIndentation()); + } + + /** + * @test + */ + public function indentWithTabsProvidesFluentInterface(): void + { + self::assertSame($this->subject, $this->subject->indentWithTabs()); + } + + /** + * @test + */ + public function indentWithSpacesByDefaultSetsIndentationToTwoSpaces(): void + { + $this->subject->indentWithSpaces(); + + self::assertSame(' ', $this->subject->getIndentation()); + } + + /** + * @return array, 1: string}> + */ + public static function provideSpaceIndentation(): array + { + return [ + 'zero spaces' => [0, ''], + 'one space' => [1, ' '], + 'two spaces' => [2, ' '], + 'three spaces' => [3, ' '], + 'four spaces' => [4, ' '], + ]; + } + + /** + * @test + * @dataProvider provideSpaceIndentation + */ + public function indentWithSpacesSetsIndentationToTheProvidedNumberOfSpaces( + int $numberOfSpaces, + string $expectedIndentation + ): void { + $this->subject->indentWithSpaces($numberOfSpaces); + + self::assertSame($expectedIndentation, $this->subject->getIndentation()); + } + + /** + * @test + */ + public function indentWithSpacesProvidesFluentInterface(): void + { + self::assertSame($this->subject, $this->subject->indentWithSpaces()); + } + + /** + * @test + */ + public function nextLevelReturnsOutputFormatInstance(): void + { + self::assertInstanceOf(OutputFormat::class, $this->subject->nextLevel()); + } + + /** + * @test + */ + public function nextLevelReturnsDifferentInstance(): void + { + self::assertNotSame($this->subject, $this->subject->nextLevel()); + } + + /** + * @test + */ + public function nextLevelReturnsInstanceWithIndentationLevelIncreasedByOne(): void + { + $originalIndentationLevel = 2; + $this->subject->setIndentationLevel($originalIndentationLevel); + + self::assertSame($originalIndentationLevel + 1, $this->subject->nextLevel()->getIndentationLevel()); + } + + /** + * @test + */ + public function beLenientSetsIgnoreExceptionsToTrue(): void + { + $this->subject->setIgnoreExceptions(false); + + $this->subject->beLenient(); + + self::assertTrue($this->subject->getIgnoreExceptions()); + } + + /** + * @test + */ + public function getFormatterReturnsOutputFormatterInstance(): void + { + self::assertInstanceOf(OutputFormatter::class, $this->subject->getFormatter()); + } + + /** + * @test + */ + public function getFormatterCalledTwoTimesReturnsSameInstance(): void + { + $firstCallResult = $this->subject->getFormatter(); + $secondCallResult = $this->subject->getFormatter(); + + self::assertSame($firstCallResult, $secondCallResult); + } + + /** + * @test + */ + public function levelReturnsIndentationLevel(): void + { + $value = 4; + $this->subject->setIndentationLevel($value); + + self::assertSame($value, $this->subject->level()); + } + + /** + * @test + */ + public function createReturnsNewOutputFormatInstance(): void + { + self::assertInstanceOf(OutputFormat::class, OutputFormat::create()); + } + + /** + * @test + */ + public function createCalledTwoTimesReturnsDifferentInstances(): void + { + $firstCallResult = OutputFormat::create(); + $secondCallResult = OutputFormat::create(); + + self::assertNotSame($firstCallResult, $secondCallResult); + } } From cde73cd579a4169ff520941ef08f61907f11207c Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 2 Feb 2025 20:23:03 +0100 Subject: [PATCH 153/555] [TASK] Add type declarations for `OutputFormat::indentWith*` (#873) Also improve the naming of the parameters. --- CHANGELOG.md | 2 +- src/OutputFormat.php | 16 ++++++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13d94fe3..ed27efb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ Please also have a look at our ### Changed - Use more native type declarations and strict mode - (#641, #772, #774, #778, #804, #841) + (#641, #772, #774, #778, #804, #841, #873) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 3f126346..ce53bb1b 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -243,23 +243,19 @@ public function __call(string $sMethodName, array $aArguments) } /** - * @param int $iNumber - * - * @return self + * @return $this fluent interface */ - public function indentWithTabs($iNumber = 1) + public function indentWithTabs(int $numberOfTabs = 1): self { - return $this->setIndentation(\str_repeat("\t", $iNumber)); + return $this->setIndentation(\str_repeat("\t", $numberOfTabs)); } /** - * @param int $iNumber - * - * @return self + * @return $this fluent interface */ - public function indentWithSpaces($iNumber = 2) + public function indentWithSpaces(int $numberOfSpaces = 2): self { - return $this->setIndentation(\str_repeat(' ', $iNumber)); + return $this->setIndentation(\str_repeat(' ', $numberOfSpaces)); } /** From 7275925ffc6984cd71b703bdb48777a9d46cf3df Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 2 Feb 2025 21:37:52 +0100 Subject: [PATCH 154/555] [TASK] Remove the deprecated `OutputFormat::level()` (#874) This method is an inconsistently-named alias of `OutputFormat::getIndentationLevel()`. Fixes #869 --------- Co-authored-by: JakeQZ --- CHANGELOG.md | 1 + src/OutputFormat.php | 8 -------- src/OutputFormatter.php | 2 +- tests/Unit/OutputFormatTest.php | 11 ----------- 4 files changed, 2 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed27efb3..eb1dc6a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ Please also have a look at our ### Removed +- Remove `OutputFormat::level()` (#874) - Remove expansion of shorthand properties (#838) - Remove `Parser::setCharset/getCharset` (#808) - Remove `Rule::getValues()` (#582) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index ce53bb1b..2b9404d7 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -287,14 +287,6 @@ public function getFormatter() return $this->oFormatter; } - /** - * @return int - */ - public function level() - { - return $this->iIndentationLevel; - } - /** * Creates an instance of this class without any particular formatting settings. */ diff --git a/src/OutputFormatter.php b/src/OutputFormatter.php index 7ef86a3c..0dfb59dc 100644 --- a/src/OutputFormatter.php +++ b/src/OutputFormatter.php @@ -208,6 +208,6 @@ private function prepareSpace($sSpaceString): string */ private function indent(): string { - return \str_repeat($this->oFormat->sIndentation, $this->oFormat->level()); + return \str_repeat($this->oFormat->sIndentation, $this->oFormat->getIndentationLevel()); } } diff --git a/tests/Unit/OutputFormatTest.php b/tests/Unit/OutputFormatTest.php index 512df67f..2538bbe2 100644 --- a/tests/Unit/OutputFormatTest.php +++ b/tests/Unit/OutputFormatTest.php @@ -833,17 +833,6 @@ public function getFormatterCalledTwoTimesReturnsSameInstance(): void self::assertSame($firstCallResult, $secondCallResult); } - /** - * @test - */ - public function levelReturnsIndentationLevel(): void - { - $value = 4; - $this->subject->setIndentationLevel($value); - - self::assertSame($value, $this->subject->level()); - } - /** * @test */ From 1fad44a1b951dbd9640ea3131aa7e26410ac55a5 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 3 Feb 2025 02:52:19 +0100 Subject: [PATCH 155/555] [TASK] Add native type declarations to `OutputFormat` (#875) Also fix some type annotations. --- CHANGELOG.md | 2 +- src/OutputFormat.php | 16 ++++------------ 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb1dc6a8..8e499ea0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ Please also have a look at our ### Changed - Use more native type declarations and strict mode - (#641, #772, #774, #778, #804, #841, #873) + (#641, #772, #774, #778, #804, #841, #873, #875) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 2b9404d7..92608df8 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -170,11 +170,9 @@ class OutputFormat public function __construct() {} /** - * @param string $sName - * - * @return string|null + * @return string|int|bool|null */ - public function get($sName) + public function get(string $sName) { $aVarPrefixes = ['a', 's', 'm', 'b', 'f', 'o', 'c', 'i']; foreach ($aVarPrefixes as $sPrefix) { @@ -258,10 +256,7 @@ public function indentWithSpaces(int $numberOfSpaces = 2): self return $this->setIndentation(\str_repeat(' ', $numberOfSpaces)); } - /** - * @return OutputFormat - */ - public function nextLevel() + public function nextLevel(): self { if ($this->oNextLevelFormat === null) { $this->oNextLevelFormat = clone $this; @@ -276,10 +271,7 @@ public function beLenient(): void $this->bIgnoreExceptions = true; } - /** - * @return OutputFormatter - */ - public function getFormatter() + public function getFormatter(): OutputFormatter { if ($this->oFormatter === null) { $this->oFormatter = new OutputFormatter($this); From 7a12457fe0df12c55433bf9c3098b65b60bbfe24 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Thu, 6 Feb 2025 15:57:25 +0000 Subject: [PATCH 156/555] [CLEANUP] Add separate `OutputFormat` properties for arrays (#880) `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. Resolves #866, #876, #878. --- CHANGELOG.md | 3 ++ src/OutputFormat.php | 30 +++++++++++++++--- src/OutputFormatter.php | 8 +++-- tests/OutputFormatTest.php | 25 ++++++++++++++- tests/Unit/OutputFormatTest.php | 54 +++++++++++++++++++++++++++++++++ 5 files changed, 113 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e499ea0..a46c9d04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Please also have a look at our ### Added +- `OutputFormat` properties for space around specific list separators (#880) - Partial support for CSS Color Module Level 4: - `rgb` and `rgba`, and `hsl` and `hsla` are now aliases (#797} - Parse color functions that use the "modern" syntax (#800) @@ -26,6 +27,8 @@ Please also have a look at our ### Deprecated +- `OutputFormat` properties for space around list separators as an array (#880) + ### Removed - Remove `OutputFormat::level()` (#874) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 92608df8..1722b738 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -98,17 +98,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 */ @@ -311,7 +333,7 @@ public static function createPretty(): self $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 0dfb59dc..6740d822 100644 --- a/src/OutputFormatter.php +++ b/src/OutputFormatter.php @@ -91,7 +91,9 @@ public function spaceAfterSelectorSeparator(): string */ public function spaceBeforeListArgumentSeparator($sSeparator): string { - return $this->space('BeforeListArgumentSeparator', $sSeparator); + $spaceForSeparator = $this->oFormat->getSpaceBeforeListArgumentSeparators(); + + return $spaceForSeparator[$sSeparator] ?? $this->space('BeforeListArgumentSeparator', $sSeparator); } /** @@ -99,7 +101,9 @@ public function spaceBeforeListArgumentSeparator($sSeparator): string */ public function spaceAfterListArgumentSeparator($sSeparator): string { - return $this->space('AfterListArgumentSeparator', $sSeparator); + $spaceForSeparator = $this->oFormat->getSpaceAfterListArgumentSeparators(); + + return $spaceForSeparator[$sSeparator] ?? $this->space('AfterListArgumentSeparator', $sSeparator); } public function spaceBeforeOpeningBrace(): string diff --git a/tests/OutputFormatTest.php b/tests/OutputFormatTest.php index f308806e..d34b08fc 100644 --- a/tests/OutputFormatTest.php +++ b/tests/OutputFormatTest.php @@ -98,8 +98,11 @@ public function spaceAfterListArgumentSeparator(): void /** * @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(): void + public function spaceAfterListArgumentSeparatorComplexDeprecated(): void { self::assertSame( '.main, .test {font: italic normal bold 16px/1.2 "Helvetica", Verdana, sans-serif;background: white;}' @@ -113,6 +116,26 @@ public function spaceAfterListArgumentSeparatorComplex(): void ); } + /** + * @test + */ + public function spaceAfterListArgumentSeparatorComplex(): void + { + 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->document->render( + OutputFormat::create() + ->setSpaceAfterListArgumentSeparator(' ') + ->setSpaceAfterListArgumentSeparators([ + ',' => "\t", + '/' => '', + ' ' => '', + ]) + ) + ); + } + /** * @test */ diff --git a/tests/Unit/OutputFormatTest.php b/tests/Unit/OutputFormatTest.php index 2538bbe2..2e5abce9 100644 --- a/tests/Unit/OutputFormatTest.php +++ b/tests/Unit/OutputFormatTest.php @@ -441,6 +441,33 @@ public function setSpaceBeforeListArgumentSeparatorProvidesFluentInterface(): vo self::assertSame($this->subject, $this->subject->setSpaceBeforeListArgumentSeparator(' ')); } + /** + * @test + */ + public function getSpaceBeforeListArgumentSeparatorsInitiallyReturnsEmptyArray(): void + { + self::assertSame([], $this->subject->getSpaceBeforeListArgumentSeparators()); + } + + /** + * @test + */ + public function setSpaceBeforeListArgumentSeparatorsSetsSpaceBeforeListArgumentSeparators(): void + { + $value = ['/' => ' ']; + $this->subject->setSpaceBeforeListArgumentSeparators($value); + + self::assertSame($value, $this->subject->getSpaceBeforeListArgumentSeparators()); + } + + /** + * @test + */ + public function setSpaceBeforeListArgumentSeparatorsProvidesFluentInterface(): void + { + self::assertSame($this->subject, $this->subject->setSpaceBeforeListArgumentSeparators([])); + } + /** * @test */ @@ -468,6 +495,33 @@ public function setSpaceAfterListArgumentSeparatorProvidesFluentInterface(): voi self::assertSame($this->subject, $this->subject->setSpaceAfterListArgumentSeparator(' ')); } + /** + * @test + */ + public function getSpaceAfterListArgumentSeparatorsInitiallyReturnsEmptyArray(): void + { + self::assertSame([], $this->subject->getSpaceAfterListArgumentSeparators()); + } + + /** + * @test + */ + public function setSpaceAfterListArgumentSeparatorsSetsSpaceAfterListArgumentSeparators(): void + { + $value = [',' => ' ']; + $this->subject->setSpaceAfterListArgumentSeparators($value); + + self::assertSame($value, $this->subject->getSpaceAfterListArgumentSeparators()); + } + + /** + * @test + */ + public function setSpaceAfterListArgumentSeparatorsProvidesFluentInterface(): void + { + self::assertSame($this->subject, $this->subject->setSpaceAfterListArgumentSeparators([])); + } + /** * @test */ From 88b8729214062e3efd76b500b4f64ff06bec7648 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 6 Feb 2025 23:41:53 +0100 Subject: [PATCH 157/555] [FEATURE] Add dedicated property accessors for `OutputFormat` (#871) --- config/phpstan-baseline.neon | 7 +- src/OutputFormat.php | 482 +++++++++++++++++++++++++++++++- tests/Unit/OutputFormatTest.php | 22 +- 3 files changed, 473 insertions(+), 38 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index a7a2f211..846a8dfe 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -1,8 +1,3 @@ parameters: - ignoreErrors: - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:setIndentation\(\)\.$#' - identifier: method.notFound - count: 2 - path: ../src/OutputFormat.php + ignoreErrors: [] diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 1722b738..979f600a 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -4,12 +4,6 @@ namespace Sabberworm\CSS; -/** - * Class OutputFormat - * - * @method OutputFormat setSemicolonAfterLastRule(bool $bSemicolonAfterLastRule) Set whether semicolons are added after - * last rule. - */ class OutputFormat { /** @@ -251,17 +245,483 @@ public function set($aNames, $mValue) */ public function __call(string $sMethodName, array $aArguments) { - if (\strpos($sMethodName, 'set') === 0) { - return $this->set(\substr($sMethodName, 3), $aArguments[0]); - } elseif (\strpos($sMethodName, 'get') === 0) { - return $this->get(\substr($sMethodName, 3)); - } elseif (\method_exists(OutputFormatter::class, $sMethodName)) { + if (\method_exists(OutputFormatter::class, $sMethodName)) { return \call_user_func_array([$this->getFormatter(), $sMethodName], $aArguments); } else { throw new \Exception('Unknown OutputFormat method called: ' . $sMethodName); } } + /** + * @internal + */ + public function getStringQuotingType(): string + { + return $this->sStringQuotingType; + } + + /** + * @return $this fluent interface + */ + public function setStringQuotingType(string $quotingType): self + { + $this->sStringQuotingType = $quotingType; + + return $this; + } + + /** + * @internal + */ + public function getRGBHashNotation(): bool + { + return $this->bRGBHashNotation; + } + + /** + * @return $this fluent interface + */ + public function setRGBHashNotation(bool $rgbHashNotation): self + { + $this->bRGBHashNotation = $rgbHashNotation; + + return $this; + } + + /** + * @internal + */ + public function getSemicolonAfterLastRule(): bool + { + return $this->bSemicolonAfterLastRule; + } + + /** + * @return $this fluent interface + */ + public function setSemicolonAfterLastRule(bool $semicolonAfterLastRule): self + { + $this->bSemicolonAfterLastRule = $semicolonAfterLastRule; + + return $this; + } + + /** + * @internal + */ + public function getSpaceAfterRuleName(): string + { + return $this->sSpaceAfterRuleName; + } + + /** + * @return $this fluent interface + */ + public function setSpaceAfterRuleName(string $whitespace): self + { + $this->sSpaceAfterRuleName = $whitespace; + + return $this; + } + + /** + * @internal + */ + public function getSpaceBeforeRules(): string + { + return $this->sSpaceBeforeRules; + } + + /** + * @return $this fluent interface + */ + public function setSpaceBeforeRules(string $whitespace): self + { + $this->sSpaceBeforeRules = $whitespace; + + return $this; + } + + /** + * @internal + */ + public function getSpaceAfterRules(): string + { + return $this->sSpaceAfterRules; + } + + /** + * @return $this fluent interface + */ + public function setSpaceAfterRules(string $whitespace): self + { + $this->sSpaceAfterRules = $whitespace; + + return $this; + } + + /** + * @internal + */ + public function getSpaceBetweenRules(): string + { + return $this->sSpaceBetweenRules; + } + + /** + * @return $this fluent interface + */ + public function setSpaceBetweenRules(string $whitespace): self + { + $this->sSpaceBetweenRules = $whitespace; + + return $this; + } + + /** + * @internal + */ + public function getSpaceBeforeBlocks(): string + { + return $this->sSpaceBeforeBlocks; + } + + /** + * @return $this fluent interface + */ + public function setSpaceBeforeBlocks(string $whitespace): self + { + $this->sSpaceBeforeBlocks = $whitespace; + + return $this; + } + + /** + * @internal + */ + public function getSpaceAfterBlocks(): string + { + return $this->sSpaceAfterBlocks; + } + + /** + * @return $this fluent interface + */ + public function setSpaceAfterBlocks(string $whitespace): self + { + $this->sSpaceAfterBlocks = $whitespace; + + return $this; + } + + /** + * @internal + */ + public function getSpaceBetweenBlocks(): string + { + return $this->sSpaceBetweenBlocks; + } + + /** + * @return $this fluent interface + */ + public function setSpaceBetweenBlocks(string $whitespace): self + { + $this->sSpaceBetweenBlocks = $whitespace; + + return $this; + } + + /** + * @internal + */ + public function getBeforeAtRuleBlock(): string + { + return $this->sBeforeAtRuleBlock; + } + + /** + * @return $this fluent interface + */ + public function setBeforeAtRuleBlock(string $content): self + { + $this->sBeforeAtRuleBlock = $content; + + return $this; + } + + /** + * @internal + */ + public function getAfterAtRuleBlock(): string + { + return $this->sAfterAtRuleBlock; + } + + /** + * @return $this fluent interface + */ + public function setAfterAtRuleBlock(string $content): self + { + $this->sAfterAtRuleBlock = $content; + + return $this; + } + + /** + * @internal + */ + public function getSpaceBeforeSelectorSeparator(): string + { + return $this->sSpaceBeforeSelectorSeparator; + } + + /** + * @return $this fluent interface + */ + public function setSpaceBeforeSelectorSeparator(string $whitespace): self + { + $this->sSpaceBeforeSelectorSeparator = $whitespace; + + return $this; + } + + /** + * @internal + */ + public function getSpaceAfterSelectorSeparator(): string + { + return $this->sSpaceAfterSelectorSeparator; + } + + /** + * @return $this fluent interface + */ + public function setSpaceAfterSelectorSeparator(string $whitespace): self + { + $this->sSpaceAfterSelectorSeparator = $whitespace; + + return $this; + } + + /** + * @internal + */ + public function getSpaceBeforeListArgumentSeparator(): string + { + return $this->sSpaceBeforeListArgumentSeparator; + } + + /** + * @return $this fluent interface + */ + public function setSpaceBeforeListArgumentSeparator(string $whitespace): self + { + $this->sSpaceBeforeListArgumentSeparator = $whitespace; + + return $this; + } + + /** + * @return array + * + * @internal + */ + public function getSpaceBeforeListArgumentSeparators(): array + { + return $this->aSpaceBeforeListArgumentSeparators; + } + + /** + * @param array $separatorSpaces + * + * @return $this fluent interface + */ + public function setSpaceBeforeListArgumentSeparators(array $separatorSpaces): self + { + $this->aSpaceBeforeListArgumentSeparators = $separatorSpaces; + + return $this; + } + + /** + * @return string|array + * + * @internal + */ + public function getSpaceAfterListArgumentSeparator() + { + return $this->sSpaceAfterListArgumentSeparator; + } + + /** + * @param string|array $whitespace + * + * @return $this fluent interface + */ + public function setSpaceAfterListArgumentSeparator($whitespace): self + { + $this->sSpaceAfterListArgumentSeparator = $whitespace; + + return $this; + } + + /** + * @return array + * + * @internal + */ + public function getSpaceAfterListArgumentSeparators(): array + { + return $this->aSpaceAfterListArgumentSeparators; + } + + /** + * @param array $separatorSpaces + * + * @return $this fluent interface + */ + public function setSpaceAfterListArgumentSeparators(array $separatorSpaces): self + { + $this->aSpaceAfterListArgumentSeparators = $separatorSpaces; + + return $this; + } + + /** + * @internal + */ + public function getSpaceBeforeOpeningBrace(): string + { + return $this->sSpaceBeforeOpeningBrace; + } + + /** + * @return $this fluent interface + */ + public function setSpaceBeforeOpeningBrace(string $whitespace): self + { + $this->sSpaceBeforeOpeningBrace = $whitespace; + + return $this; + } + + /** + * @internal + */ + public function getBeforeDeclarationBlock(): string + { + return $this->sBeforeDeclarationBlock; + } + + /** + * @return $this fluent interface + */ + public function setBeforeDeclarationBlock(string $content): self + { + $this->sBeforeDeclarationBlock = $content; + + return $this; + } + + /** + * @internal + */ + public function getAfterDeclarationBlockSelectors(): string + { + return $this->sAfterDeclarationBlockSelectors; + } + + /** + * @return $this fluent interface + */ + public function setAfterDeclarationBlockSelectors(string $content): self + { + $this->sAfterDeclarationBlockSelectors = $content; + + return $this; + } + + /** + * @internal + */ + public function getAfterDeclarationBlock(): string + { + return $this->sAfterDeclarationBlock; + } + + /** + * @return $this fluent interface + */ + public function setAfterDeclarationBlock(string $content): self + { + $this->sAfterDeclarationBlock = $content; + + return $this; + } + + /** + * @internal + */ + public function getIndentation(): string + { + return $this->sIndentation; + } + + /** + * @return $this fluent interface + */ + public function setIndentation(string $indentation): self + { + $this->sIndentation = $indentation; + + return $this; + } + + /** + * @internal + */ + public function getIgnoreExceptions(): bool + { + return $this->bIgnoreExceptions; + } + + /** + * @return $this fluent interface + */ + public function setIgnoreExceptions(bool $ignoreExceptions): self + { + $this->bIgnoreExceptions = $ignoreExceptions; + + return $this; + } + + /** + * @internal + */ + public function getRenderComments(): bool + { + return $this->bRenderComments; + } + + /** + * @return $this fluent interface + */ + public function setRenderComments(bool $renderComments): self + { + $this->bRenderComments = $renderComments; + + return $this; + } + + /** + * @internal + */ + public function getIndentationLevel(): int + { + return $this->iIndentationLevel; + } + /** * @return $this fluent interface */ diff --git a/tests/Unit/OutputFormatTest.php b/tests/Unit/OutputFormatTest.php index 2e5abce9..79ff2712 100644 --- a/tests/Unit/OutputFormatTest.php +++ b/tests/Unit/OutputFormatTest.php @@ -721,25 +721,6 @@ public function getIndentationLevelInitiallyReturnsZero(): void self::assertSame(0, $this->subject->getIndentationLevel()); } - /** - * @test - */ - public function setIndentationLevelSetsIndentationLevel(): void - { - $value = 4; - $this->subject->setIndentationLevel($value); - - self::assertSame($value, $this->subject->getIndentationLevel()); - } - - /** - * @test - */ - public function setIndentationLevelProvidesFluentInterface(): void - { - self::assertSame($this->subject, $this->subject->setIndentationLevel(4)); - } - /** * @test */ @@ -850,8 +831,7 @@ public function nextLevelReturnsDifferentInstance(): void */ public function nextLevelReturnsInstanceWithIndentationLevelIncreasedByOne(): void { - $originalIndentationLevel = 2; - $this->subject->setIndentationLevel($originalIndentationLevel); + $originalIndentationLevel = $this->subject->getIndentationLevel(); self::assertSame($originalIndentationLevel + 1, $this->subject->nextLevel()->getIndentationLevel()); } From cb3ae4bc7d427f40e88d32a4c8c1239acddb09aa Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 7 Feb 2025 19:21:55 +0100 Subject: [PATCH 158/555] [TASK] Raise PHPStan to level 2 (#877) --- config/phpstan-baseline.neon | 247 ++++++++++++++++++++++++++++++++++- config/phpstan.neon | 2 +- 2 files changed, 247 insertions(+), 2 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 846a8dfe..5ca04904 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -1,3 +1,248 @@ parameters: - ignoreErrors: [] + ignoreErrors: + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:comments\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/CSSList/AtRuleBlockList.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceBeforeOpeningBrace\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/CSSList/AtRuleBlockList.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:safely\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/CSSList/CSSList.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceAfterBlocks\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/CSSList/CSSList.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceBeforeBlocks\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/CSSList/CSSList.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceBetweenBlocks\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/CSSList/CSSList.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:comments\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/CSSList/Document.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:comments\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/CSSList/KeyFrame.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceBeforeOpeningBrace\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/CSSList/KeyFrame.php + + - + message: '#^Default value of the parameter \#2 \$bIncludeEnd \(false\) of method Sabberworm\\CSS\\Parsing\\ParserState\:\:consumeUntil\(\) is incompatible with type string\.$#' + identifier: parameter.defaultValue + count: 1 + path: ../src/Parsing/ParserState.php + + - + message: '#^Default value of the parameter \#3 \$consumeEnd \(false\) of method Sabberworm\\CSS\\Parsing\\ParserState\:\:consumeUntil\(\) is incompatible with type string\.$#' + identifier: parameter.defaultValue + count: 1 + path: ../src/Parsing/ParserState.php + + - + message: '#^PHPDoc tag @return with type array\\|void is not subtype of native type array\.$#' + identifier: return.phpDocType + count: 1 + path: ../src/Parsing/ParserState.php + + - + message: '#^Cannot call method render\(\) on string\.$#' + identifier: method.nonObject + count: 1 + path: ../src/Property/CSSNamespace.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:comments\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/Property/Charset.php + + - + message: '#^PHPDoc tag @param references unknown parameter\: \$oCharset$#' + identifier: parameter.notFound + count: 1 + path: ../src/Property/Charset.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:comments\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/Property/Import.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:comments\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/Rule/Rule.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceAfterRuleName\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/Rule/Rule.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:comments\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/RuleSet/AtRuleSet.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceBeforeOpeningBrace\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/RuleSet/AtRuleSet.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:comments\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/RuleSet/DeclarationBlock.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:implode\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/RuleSet/DeclarationBlock.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceAfterSelectorSeparator\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/RuleSet/DeclarationBlock.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceBeforeOpeningBrace\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/RuleSet/DeclarationBlock.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceBeforeSelectorSeparator\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/RuleSet/DeclarationBlock.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:removeLastSemicolon\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/RuleSet/RuleSet.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:safely\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/RuleSet/RuleSet.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceAfterRules\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/RuleSet/RuleSet.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceBeforeRules\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/RuleSet/RuleSet.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceBetweenRules\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/RuleSet/RuleSet.php + + - + message: '#^PHPDoc tag @throws with type Sabberworm\\CSS\\Value\\SourceException\|Sabberworm\\CSS\\Value\\UnexpectedEOFException\|Sabberworm\\CSS\\Value\\UnexpectedTokenException is not subtype of Throwable$#' + identifier: throws.notThrowable + count: 3 + path: ../src/Value/CSSFunction.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:implode\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/Value/CalcRuleValueList.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:implode\(\)\.$#' + identifier: method.notFound + count: 2 + path: ../src/Value/Color.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceAfterListArgumentSeparator\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/Value/Color.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceBeforeListArgumentSeparator\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/Value/Color.php + + - + message: '#^Cannot call method getSize\(\) on Sabberworm\\CSS\\Value\\Value\|string\.$#' + identifier: method.nonObject + count: 3 + path: ../src/Value/Color.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:implode\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/Value/ValueList.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceAfterListArgumentSeparator\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/Value/ValueList.php + + - + message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceBeforeListArgumentSeparator\(\)\.$#' + identifier: method.notFound + count: 1 + path: ../src/Value/ValueList.php + + - + message: '#^Cannot call method getColor\(\) on Sabberworm\\CSS\\Value\\RuleValueList\|string\.$#' + identifier: method.nonObject + count: 7 + path: ../tests/ParserTest.php + + - + message: '#^Cannot call method getListSeparator\(\) on Sabberworm\\CSS\\Value\\Value\|string\.$#' + identifier: method.nonObject + count: 4 + path: ../tests/ParserTest.php diff --git a/config/phpstan.neon b/config/phpstan.neon index f8df3337..b6be40fd 100644 --- a/config/phpstan.neon +++ b/config/phpstan.neon @@ -6,7 +6,7 @@ parameters: # Don't be overly greedy on machines with more CPU's to be a good neighbor especially on CI maximumNumberOfProcesses: 5 - level: 1 + level: 2 paths: - %currentWorkingDirectory%/bin/ From d2d1db5cb4eac9f09ae5caf8b1a61dbf634f22a9 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 9 Feb 2025 14:13:22 +0100 Subject: [PATCH 159/555] [CLEANUP] Add more type assertions in the tests (#884) --- config/phpstan-baseline.neon | 11 ----------- tests/ParserTest.php | 13 +++++++++++++ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 5ca04904..d6dfeec7 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -234,15 +234,4 @@ parameters: count: 1 path: ../src/Value/ValueList.php - - - message: '#^Cannot call method getColor\(\) on Sabberworm\\CSS\\Value\\RuleValueList\|string\.$#' - identifier: method.nonObject - count: 7 - path: ../tests/ParserTest.php - - - - message: '#^Cannot call method getListSeparator\(\) on Sabberworm\\CSS\\Value\\Value\|string\.$#' - identifier: method.nonObject - count: 4 - path: ../tests/ParserTest.php diff --git a/tests/ParserTest.php b/tests/ParserTest.php index bdd1a1f4..15e663b7 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -24,6 +24,8 @@ use Sabberworm\CSS\Value\Color; use Sabberworm\CSS\Value\Size; use Sabberworm\CSS\Value\URL; +use Sabberworm\CSS\Value\Value; +use Sabberworm\CSS\Value\ValueList; /** * @covers \Sabberworm\CSS\CSSList\Document @@ -108,6 +110,7 @@ public function colorParsing(): void self::assertSame('red', $colorRuleValue); $colorRules = $ruleSet->getRules('background-'); $colorRuleValue = $colorRules[0]->getValue(); + self::assertInstanceOf(Color::class, $colorRuleValue); self::assertEquals([ 'r' => new Size(35.0, null, true, $colorRuleValue->getLineNo()), 'g' => new Size(35.0, null, true, $colorRuleValue->getLineNo()), @@ -115,12 +118,14 @@ public function colorParsing(): void ], $colorRuleValue->getColor()); $colorRules = $ruleSet->getRules('border-color'); $colorRuleValue = $colorRules[0]->getValue(); + self::assertInstanceOf(Color::class, $colorRuleValue); self::assertEquals([ 'r' => new Size(10.0, null, true, $colorRuleValue->getLineNo()), 'g' => new Size(100.0, null, true, $colorRuleValue->getLineNo()), 'b' => new Size(230.0, null, true, $colorRuleValue->getLineNo()), ], $colorRuleValue->getColor()); $colorRuleValue = $colorRules[1]->getValue(); + self::assertInstanceOf(Color::class, $colorRuleValue); self::assertEquals([ 'r' => new Size(10.0, null, true, $colorRuleValue->getLineNo()), 'g' => new Size(100.0, null, true, $colorRuleValue->getLineNo()), @@ -129,6 +134,7 @@ public function colorParsing(): void ], $colorRuleValue->getColor()); $colorRules = $ruleSet->getRules('outline-color'); $colorRuleValue = $colorRules[0]->getValue(); + self::assertInstanceOf(Color::class, $colorRuleValue); self::assertEquals([ 'r' => new Size(34.0, null, true, $colorRuleValue->getLineNo()), 'g' => new Size(34.0, null, true, $colorRuleValue->getLineNo()), @@ -137,12 +143,14 @@ public function colorParsing(): void } elseif ($selector === '#yours') { $colorRules = $ruleSet->getRules('background-color'); $colorRuleValue = $colorRules[0]->getValue(); + self::assertInstanceOf(Color::class, $colorRuleValue); self::assertEquals([ 'h' => new Size(220.0, null, true, $colorRuleValue->getLineNo()), 's' => new Size(10.0, '%', true, $colorRuleValue->getLineNo()), 'l' => new Size(220.0, '%', true, $colorRuleValue->getLineNo()), ], $colorRuleValue->getColor()); $colorRuleValue = $colorRules[1]->getValue(); + self::assertInstanceOf(Color::class, $colorRuleValue); self::assertEquals([ 'h' => new Size(220.0, null, true, $colorRuleValue->getLineNo()), 's' => new Size(10.0, '%', true, $colorRuleValue->getLineNo()), @@ -435,7 +443,9 @@ public function slashedValues(): void self::assertSame(' ', $fontRuleValue->getListSeparator()); $fontRuleValueComponents = $fontRuleValue->getListComponents(); $commaList = $fontRuleValueComponents[1]; + self::assertInstanceOf(ValueList::class, $commaList); $slashList = $fontRuleValueComponents[0]; + self::assertInstanceOf(ValueList::class, $slashList); self::assertSame(',', $commaList->getListSeparator()); self::assertSame('/', $slashList->getListSeparator()); $borderRadiusRules = $declarationBlock->getRules('border-radius'); @@ -444,7 +454,9 @@ public function slashedValues(): void self::assertSame('/', $slashList->getListSeparator()); $slashListComponents = $slashList->getListComponents(); $secondSlashListComponent = $slashListComponents[1]; + self::assertInstanceOf(ValueList::class, $secondSlashListComponent); $firstSlashListComponent = $slashListComponents[0]; + self::assertInstanceOf(ValueList::class, $firstSlashListComponent); self::assertSame(' ', $firstSlashListComponent->getListSeparator()); self::assertSame(' ', $secondSlashListComponent->getListSeparator()); } @@ -1019,6 +1031,7 @@ public function lineNumbersParsing(): void $rules = $secondDeclarationBlock->getRules(); // Choose the 2nd one $valueOfSecondRule = $rules[1]->getValue(); + self::assertInstanceOf(Color::class, $valueOfSecondRule); self::assertSame(27, $rules[1]->getLineNo()); $actualColorLineNumbers = []; From f7914f8b4c3363235d46a8393c3ef47d4ceb2964 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 9 Feb 2025 14:20:55 +0100 Subject: [PATCH 160/555] [TASK] Make all non-private properties `@internal` (#886) 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 --- CHANGELOG.md | 1 + 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, 95 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a46c9d04..36f1f1be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Please also have a look at our ### Changed +- Make all non-private properties `@internal` (#886) - Use more native type declarations and strict mode (#641, #772, #774, #778, #804, #841, #873, #875) - Add visibility to all class/interface constants (#469) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index f7be79fd..425ca5ba 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -35,16 +35,22 @@ abstract class CSSList implements Renderable, Commentable { /** * @var array + * + * @internal since 8.8.0 */ protected $comments; /** * @var array + * + * @internal since 8.8.0 */ protected $contents; /** * @var int + * + * @internal since 8.8.0 */ protected $lineNumber; diff --git a/src/Comment/Comment.php b/src/Comment/Comment.php index bd573eeb..acc0fc34 100644 --- a/src/Comment/Comment.php +++ b/src/Comment/Comment.php @@ -11,11 +11,15 @@ class Comment implements Renderable { /** * @var int + * + * @internal since 8.8.0 */ protected $lineNumber; /** * @var string + * + * @internal since 8.8.0 */ protected $commentText; diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 979f600a..f485d1c5 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -10,6 +10,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 = '"'; @@ -17,6 +19,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; @@ -26,6 +30,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; @@ -34,36 +40,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"; @@ -71,11 +93,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 = ''; @@ -83,11 +109,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 = ' '; @@ -98,6 +128,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 = ''; @@ -105,6 +137,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 = []; @@ -115,6 +149,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 = ''; @@ -122,11 +158,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 = ' '; @@ -134,16 +174,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 = ''; @@ -151,6 +197,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"; @@ -158,6 +206,8 @@ class OutputFormat * Output exceptions. * * @var bool + * + * @internal since 8.8.0, will be made private in 9.0.0 */ public $bIgnoreExceptions = false; @@ -165,6 +215,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 fcce5d08..7ef5e0d5 100644 --- a/src/Property/CSSNamespace.php +++ b/src/Property/CSSNamespace.php @@ -29,6 +29,8 @@ class CSSNamespace implements AtRule /** * @var array + * + * @internal since 8.8.0 */ protected $comments; diff --git a/src/Property/Charset.php b/src/Property/Charset.php index c58f990c..9e848392 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -25,11 +25,15 @@ class Charset implements AtRule /** * @var int + * + * @internal since 8.8.0 */ protected $lineNumber; /** * @var array + * + * @internal since 8.8.0 */ protected $comments; diff --git a/src/Property/Import.php b/src/Property/Import.php index 636ec58b..68c13f39 100644 --- a/src/Property/Import.php +++ b/src/Property/Import.php @@ -25,11 +25,15 @@ class Import implements AtRule /** * @var int + * + * @internal since 8.8.0 */ protected $lineNumber; /** * @var array + * + * @internal since 8.8.0 */ protected $comments; diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index 0fb9d494..d863b34c 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -48,11 +48,15 @@ class Rule implements Renderable, Commentable /** * @var int + * + * @internal since 8.8.0 */ protected $iColNo; /** * @var array + * + * @internal since 8.8.0 */ protected $comments; diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 458f0af5..5f9dc99d 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -31,11 +31,15 @@ abstract class RuleSet implements Renderable, Commentable /** * @var int + * + * @internal since 8.8.0 */ protected $lineNumber; /** * @var array + * + * @internal since 8.8.0 */ protected $comments; diff --git a/src/Settings.php b/src/Settings.php index 6dd4d3e4..c684a690 100644 --- a/src/Settings.php +++ b/src/Settings.php @@ -18,6 +18,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; @@ -25,6 +27,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'; @@ -32,6 +36,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 8e6372f3..37572615 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -15,6 +15,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 7ba2a7e2..3fa7abd3 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -18,6 +18,8 @@ abstract class Value implements Renderable { /** * @var int + * + * @internal since 8.8.0 */ protected $lineNumber; diff --git a/src/Value/ValueList.php b/src/Value/ValueList.php index 445bba26..683ada3d 100644 --- a/src/Value/ValueList.php +++ b/src/Value/ValueList.php @@ -16,11 +16,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 c6302483eead47c26126d5bdc8a35aa3b2b6f7f9 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 9 Feb 2025 15:25:53 +0100 Subject: [PATCH 161/555] [TASK] Only allow `string` for some `OutputFormat` properties (#885) `sBeforeAfterListArgumentSeparator` and `sSpaceAfterListArgumentSeparator` now can only hold strings, and no longer arrays (which was deprecated in V8.8.0). --- CHANGELOG.md | 1 + src/OutputFormat.php | 18 ++++-------------- tests/OutputFormatTest.php | 20 -------------------- 3 files changed, 5 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36f1f1be..72351773 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Please also have a look at our ### Changed +- Only allow `string` for some `OutputFormat` properties (#885) - Make all non-private properties `@internal` (#886) - Use more native type declarations and strict mode (#641, #772, #774, #778, #804, #841, #873, #875) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index f485d1c5..8bdfdb1d 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -124,10 +124,7 @@ class OutputFormat /** * This is what’s inserted before 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 $aSpaceBeforeListArgumentSeparators} instead. - * - * @var string|array + * @var string * * @internal since 8.8.0, will be made private in 9.0.0 */ @@ -145,10 +142,7 @@ class OutputFormat /** * 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 + * @var string * * @internal since 8.8.0, will be made private in 9.0.0 */ @@ -597,21 +591,17 @@ public function setSpaceBeforeListArgumentSeparators(array $separatorSpaces): se } /** - * @return string|array - * * @internal */ - public function getSpaceAfterListArgumentSeparator() + public function getSpaceAfterListArgumentSeparator(): string { return $this->sSpaceAfterListArgumentSeparator; } /** - * @param string|array $whitespace - * * @return $this fluent interface */ - public function setSpaceAfterListArgumentSeparator($whitespace): self + public function setSpaceAfterListArgumentSeparator(string $whitespace): self { $this->sSpaceAfterListArgumentSeparator = $whitespace; diff --git a/tests/OutputFormatTest.php b/tests/OutputFormatTest.php index d34b08fc..c9d7b068 100644 --- a/tests/OutputFormatTest.php +++ b/tests/OutputFormatTest.php @@ -96,26 +96,6 @@ public function spaceAfterListArgumentSeparator(): void ); } - /** - * @test - * - * @deprecated since version 8.8.0; will be removed in version 9.0. - * Use `setSpaceAfterListArgumentSeparators()` to set different spacing per separator. - */ - public function spaceAfterListArgumentSeparatorComplexDeprecated(): void - { - 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->document->render(OutputFormat::create()->setSpaceAfterListArgumentSeparator([ - 'default' => ' ', - ',' => "\t", - '/' => '', - ' ' => '', - ])) - ); - } - /** * @test */ From a9eaea4e69749516bbf07951226151ef00db4b59 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 9 Feb 2025 16:39:01 +0100 Subject: [PATCH 162/555] [TASK] Add more tests for `OutputFormat` (#890) --- tests/Unit/OutputFormatTest.php | 140 ++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/tests/Unit/OutputFormatTest.php b/tests/Unit/OutputFormatTest.php index 79ff2712..d3979c20 100644 --- a/tests/Unit/OutputFormatTest.php +++ b/tests/Unit/OutputFormatTest.php @@ -826,6 +826,17 @@ public function nextLevelReturnsDifferentInstance(): void self::assertNotSame($this->subject, $this->subject->nextLevel()); } + /** + * @test + */ + public function nextLevelReturnsCloneWithSameProperties(): void + { + $space = ' '; + $this->subject->setSpaceAfterRuleName($space); + + self::assertSame($space, $this->subject->nextLevel()->getSpaceAfterRuleName()); + } + /** * @test */ @@ -836,6 +847,16 @@ public function nextLevelReturnsInstanceWithIndentationLevelIncreasedByOne(): vo self::assertSame($originalIndentationLevel + 1, $this->subject->nextLevel()->getIndentationLevel()); } + /** + * @test + */ + public function nextLevelReturnsInstanceWithDifferentFormatterInstance(): void + { + $formatter = $this->subject->getFormatter(); + + self::assertNotSame($formatter, $this->subject->nextLevel()->getFormatter()); + } + /** * @test */ @@ -885,4 +906,123 @@ public function createCalledTwoTimesReturnsDifferentInstances(): void self::assertNotSame($firstCallResult, $secondCallResult); } + + /** + * @test + */ + public function createCompactReturnsNewOutputFormatInstance(): void + { + self::assertInstanceOf(OutputFormat::class, OutputFormat::createCompact()); + } + + /** + * @test + */ + public function createCompactCalledTwoTimesReturnsDifferentInstances(): void + { + $firstCallResult = OutputFormat::createCompact(); + $secondCallResult = OutputFormat::createCompact(); + + self::assertNotSame($firstCallResult, $secondCallResult); + } + + /** + * @test + */ + public function createCompactReturnsInstanceWithSpaceBeforeRulesSetToEmptyString(): void + { + $newInstance = OutputFormat::createCompact(); + + self::assertSame('', $newInstance->getSpaceBeforeRules()); + } + + /** + * @test + */ + public function createCompactReturnsInstanceWithSpaceBetweenRulesSetToEmptyString(): void + { + $newInstance = OutputFormat::createCompact(); + + self::assertSame('', $newInstance->getSpaceBetweenRules()); + } + + /** + * @test + */ + public function createCompactReturnsInstanceWithSpaceAfterRulesSetToEmptyString(): void + { + $newInstance = OutputFormat::createCompact(); + + self::assertSame('', $newInstance->getSpaceAfterRules()); + } + + /** + * @test + */ + public function createCompactReturnsInstanceWithSpaceBeforeBlocksSetToEmptyString(): void + { + $newInstance = OutputFormat::createCompact(); + + self::assertSame('', $newInstance->getSpaceBeforeBlocks()); + } + + /** + * @test + */ + public function createCompactReturnsInstanceWithSpaceBetweenBlocksSetToEmptyString(): void + { + $newInstance = OutputFormat::createCompact(); + + self::assertSame('', $newInstance->getSpaceBetweenBlocks()); + } + + /** + * @test + */ + public function createCompactReturnsInstanceWithSpaceAfterBlocksSetToEmptyString(): void + { + $newInstance = OutputFormat::createCompact(); + + self::assertSame('', $newInstance->getSpaceAfterBlocks()); + } + + /** + * @test + */ + public function createCompactReturnsInstanceWithSpaceAfterRuleNameSetToEmptyString(): void + { + $newInstance = OutputFormat::createCompact(); + + self::assertSame('', $newInstance->getSpaceAfterRuleName()); + } + + /** + * @test + */ + public function createCompactReturnsInstanceWithSpaceBeforeOpeningBraceSetToEmptyString(): void + { + $newInstance = OutputFormat::createCompact(); + + self::assertSame('', $newInstance->getSpaceBeforeOpeningBrace()); + } + + /** + * @test + */ + public function createCompactReturnsInstanceWithSpaceAfterSelectorSeparatorSetToEmptyString(): void + { + $newInstance = OutputFormat::createCompact(); + + self::assertSame('', $newInstance->getSpaceAfterSelectorSeparator()); + } + + /** + * @test + */ + public function createCompactReturnsInstanceWithRenderCommentsDisabled(): void + { + $newInstance = OutputFormat::createCompact(); + + self::assertFalse($newInstance->getRenderComments()); + } } From 6e432588201e1d08e044512cfe50d7fa89480d1f Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 9 Feb 2025 16:43:12 +0100 Subject: [PATCH 163/555] [TASK] Use native type declarations for `Renderable` (#891) --- CHANGELOG.md | 2 +- src/CSSList/AtRuleBlockList.php | 2 +- src/CSSList/CSSBlockList.php | 2 +- src/CSSList/CSSList.php | 8 ++++---- src/CSSList/Document.php | 2 +- src/CSSList/KeyFrame.php | 2 +- src/Comment/Comment.php | 8 ++++---- src/Parser.php | 2 +- src/Parsing/OutputException.php | 2 +- src/Parsing/ParserState.php | 2 +- src/Parsing/SourceException.php | 6 +++--- src/Parsing/UnexpectedTokenException.php | 2 +- src/Property/CSSNamespace.php | 6 +++--- src/Property/Charset.php | 8 ++++---- src/Property/Import.php | 8 ++++---- src/Renderable.php | 14 ++++---------- src/Rule/Rule.php | 6 +++--- src/RuleSet/AtRuleSet.php | 2 +- src/RuleSet/DeclarationBlock.php | 2 +- src/RuleSet/RuleSet.php | 8 ++++---- src/Value/CSSFunction.php | 2 +- src/Value/CSSString.php | 2 +- src/Value/CalcRuleValueList.php | 7 ++----- src/Value/Color.php | 2 +- src/Value/LineName.php | 2 +- src/Value/PrimitiveValue.php | 2 +- src/Value/RuleValueList.php | 2 +- src/Value/Size.php | 2 +- src/Value/URL.php | 2 +- src/Value/Value.php | 8 ++++---- src/Value/ValueList.php | 7 ++----- 31 files changed, 60 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72351773..38a03088 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ Please also have a look at our - Only allow `string` for some `OutputFormat` properties (#885) - Make all non-private properties `@internal` (#886) - Use more native type declarations and strict mode - (#641, #772, #774, #778, #804, #841, #873, #875) + (#641, #772, #774, #778, #804, #841, #873, #875, #891) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/src/CSSList/AtRuleBlockList.php b/src/CSSList/AtRuleBlockList.php index af0adf31..25ba773c 100644 --- a/src/CSSList/AtRuleBlockList.php +++ b/src/CSSList/AtRuleBlockList.php @@ -25,7 +25,7 @@ class AtRuleBlockList extends CSSBlockList implements AtRule /** * @param string $type * @param string $arguments - * @param int $lineNumber + * @param int<0, max> $lineNumber */ public function __construct($type, $arguments = '', $lineNumber = 0) { diff --git a/src/CSSList/CSSBlockList.php b/src/CSSList/CSSBlockList.php index e72850b1..3ccab5a1 100644 --- a/src/CSSList/CSSBlockList.php +++ b/src/CSSList/CSSBlockList.php @@ -21,7 +21,7 @@ abstract class CSSBlockList extends CSSList { /** - * @param int $lineNumber + * @param int<0, max> $lineNumber */ public function __construct($lineNumber = 0) { diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 425ca5ba..35791089 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -48,14 +48,14 @@ abstract class CSSList implements Renderable, Commentable protected $contents; /** - * @var int + * @var int<0, max> * * @internal since 8.8.0 */ protected $lineNumber; /** - * @param int $lineNumber + * @param int<0, max> $lineNumber */ public function __construct($lineNumber = 0) { @@ -255,9 +255,9 @@ private static function identifierIs($identifier, string $match): bool } /** - * @return int + * @return int<0, max> */ - public function getLineNo() + public function getLineNo(): int { return $this->lineNumber; } diff --git a/src/CSSList/Document.php b/src/CSSList/Document.php index 490a9752..51d87a4a 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -19,7 +19,7 @@ class Document extends CSSBlockList { /** - * @param int $lineNumber + * @param int<0, max> $lineNumber */ public function __construct($lineNumber = 0) { diff --git a/src/CSSList/KeyFrame.php b/src/CSSList/KeyFrame.php index 61e70380..8a6bea7d 100644 --- a/src/CSSList/KeyFrame.php +++ b/src/CSSList/KeyFrame.php @@ -20,7 +20,7 @@ class KeyFrame extends CSSList implements AtRule private $animationName; /** - * @param int $lineNumber + * @param int<0, max> $lineNumber */ public function __construct($lineNumber = 0) { diff --git a/src/Comment/Comment.php b/src/Comment/Comment.php index acc0fc34..7d6de3d7 100644 --- a/src/Comment/Comment.php +++ b/src/Comment/Comment.php @@ -10,7 +10,7 @@ class Comment implements Renderable { /** - * @var int + * @var int<0, max> * * @internal since 8.8.0 */ @@ -25,7 +25,7 @@ class Comment implements Renderable /** * @param string $commentText - * @param int $lineNumber + * @param int<0, max> $lineNumber */ public function __construct($commentText = '', $lineNumber = 0) { @@ -42,9 +42,9 @@ public function getComment() } /** - * @return int + * @return int<0, max> */ - public function getLineNo() + public function getLineNo(): int { return $this->lineNumber; } diff --git a/src/Parser.php b/src/Parser.php index 858c0d74..faaeb87d 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -20,7 +20,7 @@ class Parser /** * @param string $sText the complete CSS as text (i.e., usually the contents of a CSS file) - * @param int $lineNumber the line number (starting from 1, not from 0) + * @param int<0, max> $lineNumber the line number (starting from 1, not from 0) */ public function __construct($sText, ?Settings $oParserSettings = null, $lineNumber = 1) { diff --git a/src/Parsing/OutputException.php b/src/Parsing/OutputException.php index d52d7380..63b32207 100644 --- a/src/Parsing/OutputException.php +++ b/src/Parsing/OutputException.php @@ -11,7 +11,7 @@ final class OutputException extends SourceException { /** * @param string $sMessage - * @param int $lineNumber + * @param int<0, max> $lineNumber */ public function __construct($sMessage, $lineNumber = 0) { diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index c14674d2..cb28cdb0 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -58,7 +58,7 @@ class ParserState /** * @param string $sText the complete CSS as text (i.e., usually the contents of a CSS file) - * @param int $lineNumber + * @param int<0, max> $lineNumber */ public function __construct($sText, Settings $oParserSettings, $lineNumber = 1) { diff --git a/src/Parsing/SourceException.php b/src/Parsing/SourceException.php index 77adb478..1af7affc 100644 --- a/src/Parsing/SourceException.php +++ b/src/Parsing/SourceException.php @@ -13,7 +13,7 @@ class SourceException extends \Exception /** * @param string $sMessage - * @param int $lineNumber + * @param int<0, max> $lineNumber */ public function __construct($sMessage, $lineNumber = 0) { @@ -25,9 +25,9 @@ public function __construct($sMessage, $lineNumber = 0) } /** - * @return int + * @return int<0, max> */ - public function getLineNo() + public function getLineNo(): int { return $this->lineNumber; } diff --git a/src/Parsing/UnexpectedTokenException.php b/src/Parsing/UnexpectedTokenException.php index 3620ff01..441397f1 100644 --- a/src/Parsing/UnexpectedTokenException.php +++ b/src/Parsing/UnexpectedTokenException.php @@ -30,7 +30,7 @@ class UnexpectedTokenException extends SourceException * @param string $sExpected * @param string $sFound * @param string $sMatchType - * @param int $lineNumber + * @param int<0, max> $lineNumber */ public function __construct($sExpected, $sFound, $sMatchType = 'literal', $lineNumber = 0) { diff --git a/src/Property/CSSNamespace.php b/src/Property/CSSNamespace.php index 7ef5e0d5..066bede6 100644 --- a/src/Property/CSSNamespace.php +++ b/src/Property/CSSNamespace.php @@ -37,7 +37,7 @@ class CSSNamespace implements AtRule /** * @param string $mUrl * @param string|null $sPrefix - * @param int $lineNumber + * @param int<0, max> $lineNumber */ public function __construct($mUrl, $sPrefix = null, $lineNumber = 0) { @@ -48,9 +48,9 @@ public function __construct($mUrl, $sPrefix = null, $lineNumber = 0) } /** - * @return int + * @return int<0, max> */ - public function getLineNo() + public function getLineNo(): int { return $this->lineNumber; } diff --git a/src/Property/Charset.php b/src/Property/Charset.php index 9e848392..90dfeabf 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -24,7 +24,7 @@ class Charset implements AtRule private $oCharset; /** - * @var int + * @var int<0, max> * * @internal since 8.8.0 */ @@ -38,7 +38,7 @@ class Charset implements AtRule protected $comments; /** - * @param int $lineNumber + * @param int<0, max> $lineNumber */ public function __construct(CSSString $oCharset, $lineNumber = 0) { @@ -48,9 +48,9 @@ public function __construct(CSSString $oCharset, $lineNumber = 0) } /** - * @return int + * @return int<0, max> */ - public function getLineNo() + public function getLineNo(): int { return $this->lineNumber; } diff --git a/src/Property/Import.php b/src/Property/Import.php index 68c13f39..329b0337 100644 --- a/src/Property/Import.php +++ b/src/Property/Import.php @@ -24,7 +24,7 @@ class Import implements AtRule private $mediaQuery; /** - * @var int + * @var int<0, max> * * @internal since 8.8.0 */ @@ -39,7 +39,7 @@ class Import implements AtRule /** * @param string $mediaQuery - * @param int $lineNumber + * @param int<0, max> $lineNumber */ public function __construct(URL $location, $mediaQuery, $lineNumber = 0) { @@ -50,9 +50,9 @@ public function __construct(URL $location, $mediaQuery, $lineNumber = 0) } /** - * @return int + * @return int<0, max> */ - public function getLineNo() + public function getLineNo(): int { return $this->lineNumber; } diff --git a/src/Renderable.php b/src/Renderable.php index fc3b8e71..fb505ec0 100644 --- a/src/Renderable.php +++ b/src/Renderable.php @@ -6,18 +6,12 @@ interface Renderable { - /** - * @return string - */ - public function __toString(); + public function __toString(): string; - /** - * @return string - */ - public function render(OutputFormat $oOutputFormat); + public function render(OutputFormat $oOutputFormat): string; /** - * @return int + * @return int<0, max> */ - public function getLineNo(); + public function getLineNo(): int; } diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index d863b34c..ee15059b 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -62,7 +62,7 @@ class Rule implements Renderable, Commentable /** * @param string $sRule - * @param int $lineNumber + * @param int<0, max> $lineNumber * @param int $iColNo */ public function __construct($sRule, $lineNumber = 0, $iColNo = 0) @@ -141,9 +141,9 @@ private static function listDelimiterForRule($sRule): array } /** - * @return int + * @return int<0, max> */ - public function getLineNo() + public function getLineNo(): int { return $this->lineNumber; } diff --git a/src/RuleSet/AtRuleSet.php b/src/RuleSet/AtRuleSet.php index 4a9329a8..67b274be 100644 --- a/src/RuleSet/AtRuleSet.php +++ b/src/RuleSet/AtRuleSet.php @@ -28,7 +28,7 @@ class AtRuleSet extends RuleSet implements AtRule /** * @param string $sType * @param string $sArgs - * @param int $lineNumber + * @param int<0, max> $lineNumber */ public function __construct($sType, $sArgs = '', $lineNumber = 0) { diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 231476cf..2ca5de28 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -30,7 +30,7 @@ class DeclarationBlock extends RuleSet private $aSelectors; /** - * @param int $lineNumber + * @param int<0, max> $lineNumber */ public function __construct($lineNumber = 0) { diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 5f9dc99d..66a9043c 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -30,7 +30,7 @@ abstract class RuleSet implements Renderable, Commentable private $aRules; /** - * @var int + * @var int<0, max> * * @internal since 8.8.0 */ @@ -44,7 +44,7 @@ abstract class RuleSet implements Renderable, Commentable protected $comments; /** - * @param int $lineNumber + * @param int<0, max> $lineNumber */ public function __construct($lineNumber = 0) { @@ -94,9 +94,9 @@ public static function parseRuleSet(ParserState $parserState, RuleSet $ruleSet): } /** - * @return int + * @return int<0, max> */ - public function getLineNo() + public function getLineNo(): int { return $this->lineNumber; } diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index 37572615..44635f84 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -24,7 +24,7 @@ class CSSFunction extends ValueList * @param string $sName * @param RuleValueList|array $aArguments * @param string $sSeparator - * @param int $lineNumber + * @param int<0, max> $lineNumber */ public function __construct($sName, $aArguments, $sSeparator = ',', $lineNumber = 0) { diff --git a/src/Value/CSSString.php b/src/Value/CSSString.php index a1ca1c70..b0c92e4c 100644 --- a/src/Value/CSSString.php +++ b/src/Value/CSSString.php @@ -24,7 +24,7 @@ class CSSString extends PrimitiveValue /** * @param string $sString - * @param int $lineNumber + * @param int<0, max> $lineNumber */ public function __construct($sString, $lineNumber = 0) { diff --git a/src/Value/CalcRuleValueList.php b/src/Value/CalcRuleValueList.php index 508296d9..84b80ade 100644 --- a/src/Value/CalcRuleValueList.php +++ b/src/Value/CalcRuleValueList.php @@ -9,17 +9,14 @@ class CalcRuleValueList extends RuleValueList { /** - * @param int $lineNumber + * @param int<0, max> $lineNumber */ public function __construct($lineNumber = 0) { parent::__construct(',', $lineNumber); } - /** - * @return string - */ - public function render(OutputFormat $oOutputFormat) + public function render(OutputFormat $oOutputFormat): string { return $oOutputFormat->implode(' ', $this->aComponents); } diff --git a/src/Value/Color.php b/src/Value/Color.php index c4d6a4ee..48249598 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -17,7 +17,7 @@ class Color extends CSSFunction { /** * @param array $colorValues - * @param int $lineNumber + * @param int<0, max> $lineNumber */ public function __construct(array $colorValues, $lineNumber = 0) { diff --git a/src/Value/LineName.php b/src/Value/LineName.php index 18a53333..6caa8617 100644 --- a/src/Value/LineName.php +++ b/src/Value/LineName.php @@ -13,7 +13,7 @@ class LineName extends ValueList { /** * @param array $aComponents - * @param int $lineNumber + * @param int<0, max> $lineNumber */ public function __construct(array $aComponents = [], $lineNumber = 0) { diff --git a/src/Value/PrimitiveValue.php b/src/Value/PrimitiveValue.php index 4cccf35c..42728c73 100644 --- a/src/Value/PrimitiveValue.php +++ b/src/Value/PrimitiveValue.php @@ -7,7 +7,7 @@ abstract class PrimitiveValue extends Value { /** - * @param int $lineNumber + * @param int<0, max> $lineNumber */ public function __construct($lineNumber = 0) { diff --git a/src/Value/RuleValueList.php b/src/Value/RuleValueList.php index b68df0ea..33f98eb1 100644 --- a/src/Value/RuleValueList.php +++ b/src/Value/RuleValueList.php @@ -13,7 +13,7 @@ class RuleValueList extends ValueList { /** * @param string $sSeparator - * @param int $lineNumber + * @param int<0, max> $lineNumber */ public function __construct($sSeparator = ',', $lineNumber = 0) { diff --git a/src/Value/Size.php b/src/Value/Size.php index 82fdc261..48ae770b 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -71,7 +71,7 @@ class Size extends PrimitiveValue * @param float|int|string $fSize * @param string|null $sUnit * @param bool $bIsColorComponent - * @param int $lineNumber + * @param int<0, max> $lineNumber */ public function __construct($fSize, $sUnit = null, $bIsColorComponent = false, $lineNumber = 0) { diff --git a/src/Value/URL.php b/src/Value/URL.php index 018abefc..1608d12d 100644 --- a/src/Value/URL.php +++ b/src/Value/URL.php @@ -21,7 +21,7 @@ class URL extends PrimitiveValue private $oURL; /** - * @param int $lineNumber + * @param int<0, max> $lineNumber */ public function __construct(CSSString $oURL, $lineNumber = 0) { diff --git a/src/Value/Value.php b/src/Value/Value.php index 3fa7abd3..b5c20c99 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -17,14 +17,14 @@ abstract class Value implements Renderable { /** - * @var int + * @var int<0, max> * * @internal since 8.8.0 */ protected $lineNumber; /** - * @param int $lineNumber + * @param int<0, max> $lineNumber */ public function __construct($lineNumber = 0) { @@ -212,9 +212,9 @@ private static function parseUnicodeRangeValue(ParserState $parserState): string } /** - * @return int + * @return int<0, max> */ - public function getLineNo() + public function getLineNo(): int { return $this->lineNumber; } diff --git a/src/Value/ValueList.php b/src/Value/ValueList.php index 683ada3d..d18ad9ef 100644 --- a/src/Value/ValueList.php +++ b/src/Value/ValueList.php @@ -31,7 +31,7 @@ abstract class ValueList extends Value /** * @param array|Value|string $aComponents * @param string $sSeparator - * @param int $lineNumber + * @param int<0, max> $lineNumber */ public function __construct($aComponents = [], $sSeparator = ',', $lineNumber = 0) { @@ -88,10 +88,7 @@ public function __toString(): string return $this->render(new OutputFormat()); } - /** - * @return string - */ - public function render(OutputFormat $oOutputFormat) + public function render(OutputFormat $oOutputFormat): string { return $oOutputFormat->implode( $oOutputFormat->spaceBeforeListArgumentSeparator($this->sSeparator) . $this->sSeparator From 5e051971481c18e59acf436ee1a5ba05a5e4470a Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 9 Feb 2025 18:49:02 +0100 Subject: [PATCH 164/555] [BUGFIX] Fix return type annotation of `Value::isSize()` (#892) --- src/Value/Size.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Value/Size.php b/src/Value/Size.php index 48ae770b..ad38de1d 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -186,7 +186,8 @@ public function isColorComponent() /** * Returns whether the number stored in this Size really represents a size (as in a length of something on screen). * - * @return false if the unit an angle, a duration, a frequency or the number is a component in a Color object. + * Returns `false` if the unit is an angle, a duration, a frequency, or the number is a component in a `Color` + * object. */ public function isSize(): bool { From 5c440f3126b88bcaf6976632c506f5464538ec37 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 10 Feb 2025 10:19:57 +0100 Subject: [PATCH 165/555] [TASK] Add more tests for `OutputFormat` (#893) --- tests/Unit/OutputFormatTest.php | 143 +++++++++++++++++++++++++++++++- 1 file changed, 141 insertions(+), 2 deletions(-) diff --git a/tests/Unit/OutputFormatTest.php b/tests/Unit/OutputFormatTest.php index d3979c20..2b60b52c 100644 --- a/tests/Unit/OutputFormatTest.php +++ b/tests/Unit/OutputFormatTest.php @@ -891,7 +891,7 @@ public function getFormatterCalledTwoTimesReturnsSameInstance(): void /** * @test */ - public function createReturnsNewOutputFormatInstance(): void + public function createReturnsOutputFormatInstance(): void { self::assertInstanceOf(OutputFormat::class, OutputFormat::create()); } @@ -910,7 +910,7 @@ public function createCalledTwoTimesReturnsDifferentInstances(): void /** * @test */ - public function createCompactReturnsNewOutputFormatInstance(): void + public function createCompactReturnsOutputFormatInstance(): void { self::assertInstanceOf(OutputFormat::class, OutputFormat::createCompact()); } @@ -1016,6 +1016,16 @@ public function createCompactReturnsInstanceWithSpaceAfterSelectorSeparatorSetTo self::assertSame('', $newInstance->getSpaceAfterSelectorSeparator()); } + /** + * @test + */ + public function createCompactReturnsInstanceWithSpaceAfterListArgumentSeparatorsSetToEmptyArray(): void + { + $newInstance = OutputFormat::createCompact(); + + self::assertSame([], $newInstance->getSpaceAfterListArgumentSeparators()); + } + /** * @test */ @@ -1025,4 +1035,133 @@ public function createCompactReturnsInstanceWithRenderCommentsDisabled(): void self::assertFalse($newInstance->getRenderComments()); } + + /** + * @test + */ + public function createPrettyReturnsOutputFormatInstance(): void + { + self::assertInstanceOf(OutputFormat::class, OutputFormat::createPretty()); + } + + /** + * @test + */ + public function createPrettyCalledTwoTimesReturnsDifferentInstances(): void + { + $firstCallResult = OutputFormat::createPretty(); + $secondCallResult = OutputFormat::createPretty(); + + self::assertNotSame($firstCallResult, $secondCallResult); + } + + /** + * @test + */ + public function createPrettyReturnsInstanceWithSpaceBeforeRulesSetToNewline(): void + { + $newInstance = OutputFormat::createPretty(); + + self::assertSame("\n", $newInstance->getSpaceBeforeRules()); + } + + /** + * @test + */ + public function createPrettyReturnsInstanceWithSpaceBetweenRulesSetToNewline(): void + { + $newInstance = OutputFormat::createPretty(); + + self::assertSame("\n", $newInstance->getSpaceBetweenRules()); + } + + /** + * @test + */ + public function createPrettyReturnsInstanceWithSpaceAfterRulesSetToNewline(): void + { + $newInstance = OutputFormat::createPretty(); + + self::assertSame("\n", $newInstance->getSpaceAfterRules()); + } + + /** + * @test + */ + public function createPrettyReturnsInstanceWithSpaceBeforeBlocksSetToNewline(): void + { + $newInstance = OutputFormat::createPretty(); + + self::assertSame("\n", $newInstance->getSpaceBeforeBlocks()); + } + + /** + * @test + */ + public function createPrettyReturnsInstanceWithSpaceBetweenBlocksSetToTwoNewlines(): void + { + $newInstance = OutputFormat::createPretty(); + + self::assertSame("\n\n", $newInstance->getSpaceBetweenBlocks()); + } + + /** + * @test + */ + public function createPrettyReturnsInstanceWithSpaceAfterBlocksSetToNewline(): void + { + $newInstance = OutputFormat::createPretty(); + + self::assertSame("\n", $newInstance->getSpaceAfterBlocks()); + } + + /** + * @test + */ + public function createPrettyReturnsInstanceWithSpaceAfterRuleNameSetToSpace(): void + { + $newInstance = OutputFormat::createPretty(); + + self::assertSame(' ', $newInstance->getSpaceAfterRuleName()); + } + + /** + * @test + */ + public function createPrettyReturnsInstanceWithSpaceBeforeOpeningBraceSetToSpace(): void + { + $newInstance = OutputFormat::createPretty(); + + self::assertSame(' ', $newInstance->getSpaceBeforeOpeningBrace()); + } + + /** + * @test + */ + public function createPrettyReturnsInstanceWithSpaceAfterSelectorSeparatorSetToSpace(): void + { + $newInstance = OutputFormat::createPretty(); + + self::assertSame(' ', $newInstance->getSpaceAfterSelectorSeparator()); + } + + /** + * @test + */ + public function createPrettyReturnsInstanceWithSpaceAfterListArgumentSeparatorsSetToSpaceForCommaOnly(): void + { + $newInstance = OutputFormat::createPretty(); + + self::assertSame([',' => ' '], $newInstance->getSpaceAfterListArgumentSeparators()); + } + + /** + * @test + */ + public function createPrettyReturnsInstanceWithRenderCommentsEnabled(): void + { + $newInstance = OutputFormat::createPretty(); + + self::assertTrue($newInstance->getRenderComments()); + } } From ceec94e0636a2ffebc254f95e763771725837601 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 10 Feb 2025 11:40:46 +0100 Subject: [PATCH 166/555] [BUGFIX] Add missing imports for `@throws` annotations (#897) --- config/phpstan-baseline.neon | 6 ------ src/Value/CSSFunction.php | 3 +++ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index d6dfeec7..81ecb713 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -180,12 +180,6 @@ parameters: count: 1 path: ../src/RuleSet/RuleSet.php - - - message: '#^PHPDoc tag @throws with type Sabberworm\\CSS\\Value\\SourceException\|Sabberworm\\CSS\\Value\\UnexpectedEOFException\|Sabberworm\\CSS\\Value\\UnexpectedTokenException is not subtype of Throwable$#' - identifier: throws.notThrowable - count: 3 - path: ../src/Value/CSSFunction.php - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:implode\(\)\.$#' identifier: method.notFound diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index 44635f84..110c9907 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -6,6 +6,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 4322d97b04ab5e7e8c044afeb7b9d33adbb12adf Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 10 Feb 2025 11:43:40 +0100 Subject: [PATCH 167/555] [CLEANUP] Autoformat the code and organize the imports (#899) --- src/Value/Color.php | 12 ++++++------ src/Value/Value.php | 8 ++++---- tests/ParserTest.php | 1 - tests/Unit/Rule/RuleTest.php | 2 +- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/Value/Color.php b/src/Value/Color.php index 48249598..16e7dbd8 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -32,8 +32,8 @@ public static function parse(ParserState $parserState, bool $ignoreCase = false) { return $parserState->comes('#') - ? self::parseHexColor($parserState) - : self::parseColorFunction($parserState); + ? self::parseHexColor($parserState) + : self::parseColorFunction($parserState); } /** @@ -138,8 +138,8 @@ private static function parseColorFunction(ParserState $parserState): CSSFunctio // With a `var` argument, the function can have fewer arguments. // And as of CSS Color Module Level 4, the alpha argument is optional. $canCloseNow = - $containsVar || - ($mayHaveOptionalAlpha && $argumentIndex >= $expectedArgumentCount - 2); + $containsVar + || ($mayHaveOptionalAlpha && $argumentIndex >= $expectedArgumentCount - 2); if ($canCloseNow && $parserState->comes(')')) { break; } @@ -183,8 +183,8 @@ private static function parseColorFunction(ParserState $parserState): CSSFunctio return $containsVar - ? new CSSFunction($colorMode, \array_values($colorValues), ',', $parserState->currentLine()) - : new Color($colorValues, $parserState->currentLine()); + ? new CSSFunction($colorMode, \array_values($colorValues), ',', $parserState->currentLine()) + : new Color($colorValues, $parserState->currentLine()); } private static function mapRange(float $value, float $fromMin, float $fromMax, float $toMin, float $toMax): float diff --git a/src/Value/Value.php b/src/Value/Value.php index b5c20c99..4e27b668 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -46,10 +46,10 @@ public static function parseValue(ParserState $parserState, array $aListDelimite $parserState->consumeWhiteSpace(); //Build a list of delimiters and parsed values while ( - !($parserState->comes('}') || $parserState->comes(';') || $parserState->comes('!') - || $parserState->comes(')') - || $parserState->comes('\\') - || $parserState->isEnd()) + !($parserState->comes('}') || $parserState->comes(';') || $parserState->comes('!') + || $parserState->comes(')') + || $parserState->comes('\\') + || $parserState->isEnd()) ) { if (\count($aStack) > 0) { $bFoundDelimiter = false; diff --git a/tests/ParserTest.php b/tests/ParserTest.php index 15e663b7..15d152a4 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -24,7 +24,6 @@ use Sabberworm\CSS\Value\Color; use Sabberworm\CSS\Value\Size; use Sabberworm\CSS\Value\URL; -use Sabberworm\CSS\Value\Value; use Sabberworm\CSS\Value\ValueList; /** diff --git a/tests/Unit/Rule/RuleTest.php b/tests/Unit/Rule/RuleTest.php index 604ace1c..b803c685 100644 --- a/tests/Unit/Rule/RuleTest.php +++ b/tests/Unit/Rule/RuleTest.php @@ -6,8 +6,8 @@ use PHPUnit\Framework\TestCase; use Sabberworm\CSS\Parsing\ParserState; -use Sabberworm\CSS\Settings; use Sabberworm\CSS\Rule\Rule; +use Sabberworm\CSS\Settings; use Sabberworm\CSS\Value\RuleValueList; use Sabberworm\CSS\Value\Value; use Sabberworm\CSS\Value\ValueList; From bcbdf4bac21632b006611a80dc3cf9b6fb8c644a Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 11 Feb 2025 03:31:15 +0100 Subject: [PATCH 168/555] [TASK] Mark `OutputFormat::nextLevel()` as `@internal` (#901) This method is used internally for rendering the CSS, and it is not intended to be called from outside this library. --- CHANGELOG.md | 1 + src/OutputFormat.php | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38a03088..b6182da3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Please also have a look at our ### Changed +- Mark `OutputFormat::nextLevel()` as `@internal` (#901) - Only allow `string` for some `OutputFormat` properties (#885) - Make all non-private properties `@internal` (#886) - Use more native type declarations and strict mode diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 8bdfdb1d..ce1459d3 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -780,6 +780,9 @@ public function indentWithSpaces(int $numberOfSpaces = 2): self return $this->setIndentation(\str_repeat(' ', $numberOfSpaces)); } + /** + * @internal since V8.8.0 + */ public function nextLevel(): self { if ($this->oNextLevelFormat === null) { From 3614c0d03c1b360da5221e9b2450d2f510434197 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 11 Feb 2025 03:35:00 +0100 Subject: [PATCH 169/555] [BUGFIX] Fix a type annotation in a test (#903) --- tests/Unit/Value/ValueTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/Value/ValueTest.php b/tests/Unit/Value/ValueTest.php index d96485fa..380a2ebb 100644 --- a/tests/Unit/Value/ValueTest.php +++ b/tests/Unit/Value/ValueTest.php @@ -57,7 +57,7 @@ public function parsesArithmeticInFunctions(string $operator): void } /** - * @return array + * @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. */ From b3e83b8670f3e0f6ff9e48f77625365348c7a4c9 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 11 Feb 2025 08:18:35 +0100 Subject: [PATCH 170/555] [BUGFIX] Fix an incorrect type annotation in `OutputFormat` (#902) --- src/OutputFormat.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index ce1459d3..52033bf4 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -18,7 +18,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 571febbf2ebe20a40a63a6469d63f5c7191efc4e Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 11 Feb 2025 19:51:01 +0100 Subject: [PATCH 171/555] [TASK] Raise PHPStan to level 3 (#887) --- config/phpstan-baseline.neon | 37 +++++++++++++++++++++++++++++++++++- config/phpstan.neon | 2 +- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 81ecb713..eb3026f7 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -12,6 +12,18 @@ parameters: count: 1 path: ../src/CSSList/AtRuleBlockList.php + - + message: '#^Parameter &\$result by\-ref type of method Sabberworm\\CSS\\CSSList\\CSSBlockList\:\:allSelectors\(\) expects array\, array\ given\.$#' + identifier: parameterByRef.type + count: 2 + path: ../src/CSSList/CSSBlockList.php + + - + message: '#^Parameter &\$result by\-ref type of method Sabberworm\\CSS\\CSSList\\CSSBlockList\:\:allValues\(\) expects array\, array\ given\.$#' + identifier: parameterByRef.type + count: 1 + path: ../src/CSSList/CSSBlockList.php + - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:safely\(\)\.$#' identifier: method.notFound @@ -78,12 +90,24 @@ parameters: count: 1 path: ../src/Property/CSSNamespace.php + - + message: '#^Return type \(array\\) of method Sabberworm\\CSS\\Property\\CSSNamespace\:\:atRuleArgs\(\) should be compatible with return type \(string\|null\) of method Sabberworm\\CSS\\Property\\AtRule\:\:atRuleArgs\(\)$#' + identifier: method.childReturnType + count: 1 + path: ../src/Property/CSSNamespace.php + - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:comments\(\)\.$#' identifier: method.notFound count: 1 path: ../src/Property/Charset.php + - + message: '#^Method Sabberworm\\CSS\\Property\\Charset\:\:atRuleArgs\(\) should return string but returns Sabberworm\\CSS\\Value\\CSSString\.$#' + identifier: return.type + count: 1 + path: ../src/Property/Charset.php + - message: '#^PHPDoc tag @param references unknown parameter\: \$oCharset$#' identifier: parameter.notFound @@ -96,6 +120,12 @@ parameters: count: 1 path: ../src/Property/Import.php + - + message: '#^Return type \(array\\) of method Sabberworm\\CSS\\Property\\Import\:\:atRuleArgs\(\) should be compatible with return type \(string\|null\) of method Sabberworm\\CSS\\Property\\AtRule\:\:atRuleArgs\(\)$#' + identifier: method.childReturnType + count: 1 + path: ../src/Property/Import.php + - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:comments\(\)\.$#' identifier: method.notFound @@ -150,6 +180,12 @@ parameters: count: 1 path: ../src/RuleSet/DeclarationBlock.php + - + message: '#^Argument of an invalid type Sabberworm\\CSS\\Rule\\Rule supplied for foreach, only iterables are supported\.$#' + identifier: foreach.nonIterable + count: 2 + path: ../src/RuleSet/RuleSet.php + - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:removeLastSemicolon\(\)\.$#' identifier: method.notFound @@ -228,4 +264,3 @@ parameters: count: 1 path: ../src/Value/ValueList.php - diff --git a/config/phpstan.neon b/config/phpstan.neon index b6be40fd..910181dd 100644 --- a/config/phpstan.neon +++ b/config/phpstan.neon @@ -6,7 +6,7 @@ parameters: # Don't be overly greedy on machines with more CPU's to be a good neighbor especially on CI maximumNumberOfProcesses: 5 - level: 2 + level: 3 paths: - %currentWorkingDirectory%/bin/ From a5d22be7731573e0b11f2ceb612f9ff9029be025 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 11 Feb 2025 19:58:22 +0100 Subject: [PATCH 172/555] [BUGFIX] Fix the parameter name in a type annotation (#904) Co-authored-by: JakeQZ --- config/phpstan-baseline.neon | 6 ------ src/Property/Charset.php | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index eb3026f7..2551deed 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -108,12 +108,6 @@ parameters: count: 1 path: ../src/Property/Charset.php - - - message: '#^PHPDoc tag @param references unknown parameter\: \$oCharset$#' - identifier: parameter.notFound - count: 1 - path: ../src/Property/Charset.php - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:comments\(\)\.$#' identifier: method.notFound diff --git a/src/Property/Charset.php b/src/Property/Charset.php index 90dfeabf..94a9f983 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -56,7 +56,7 @@ public function getLineNo(): int } /** - * @param string|CSSString $oCharset + * @param string|CSSString $sCharset */ public function setCharset($sCharset): void { From 845b457ffdcbbd38315ff98c27705a268d3a80f1 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 13 Feb 2025 03:03:55 +0100 Subject: [PATCH 173/555] [CLEANUP] Avoid Hungarian notation for `outputFormat` (#910) Part of #756 --- src/CSSList/AtRuleBlockList.php | 12 ++++++------ src/CSSList/CSSList.php | 10 +++++----- src/CSSList/Document.php | 8 ++++---- src/CSSList/KeyFrame.php | 8 ++++---- src/Property/CSSNamespace.php | 4 ++-- src/Property/Charset.php | 4 ++-- src/Property/Import.php | 4 ++-- src/Renderable.php | 2 +- src/Rule/Rule.php | 6 +++--- src/RuleSet/AtRuleSet.php | 8 ++++---- src/RuleSet/DeclarationBlock.php | 18 +++++++++--------- src/RuleSet/RuleSet.php | 8 ++++---- src/Value/CSSFunction.php | 4 ++-- src/Value/CSSString.php | 4 ++-- src/Value/CalcRuleValueList.php | 4 ++-- src/Value/LineName.php | 2 +- src/Value/Size.php | 2 +- src/Value/URL.php | 4 ++-- src/Value/ValueList.php | 8 ++++---- 19 files changed, 60 insertions(+), 60 deletions(-) diff --git a/src/CSSList/AtRuleBlockList.php b/src/CSSList/AtRuleBlockList.php index 25ba773c..db472c29 100644 --- a/src/CSSList/AtRuleBlockList.php +++ b/src/CSSList/AtRuleBlockList.php @@ -55,18 +55,18 @@ public function __toString(): string return $this->render(new OutputFormat()); } - public function render(OutputFormat $oOutputFormat): string + public function render(OutputFormat $outputFormat): string { - $sResult = $oOutputFormat->comments($this); - $sResult .= $oOutputFormat->sBeforeAtRuleBlock; + $sResult = $outputFormat->comments($this); + $sResult .= $outputFormat->sBeforeAtRuleBlock; $sArgs = $this->sArgs; if ($sArgs) { $sArgs = ' ' . $sArgs; } - $sResult .= "@{$this->type}$sArgs{$oOutputFormat->spaceBeforeOpeningBrace()}{"; - $sResult .= $this->renderListContents($oOutputFormat); + $sResult .= "@{$this->type}$sArgs{$outputFormat->spaceBeforeOpeningBrace()}{"; + $sResult .= $this->renderListContents($outputFormat); $sResult .= '}'; - $sResult .= $oOutputFormat->sAfterAtRuleBlock; + $sResult .= $outputFormat->sAfterAtRuleBlock; return $sResult; } diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 35791089..a037ee9b 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -410,16 +410,16 @@ public function __toString(): string /** * @return string */ - protected function renderListContents(OutputFormat $oOutputFormat) + protected function renderListContents(OutputFormat $outputFormat) { $result = ''; $isFirst = true; - $nextLevelFormat = $oOutputFormat; + $nextLevelFormat = $outputFormat; if (!$this->isRootList()) { - $nextLevelFormat = $oOutputFormat->nextLevel(); + $nextLevelFormat = $outputFormat->nextLevel(); } foreach ($this->contents as $listItem) { - $renderedCss = $oOutputFormat->safely(static function () use ($nextLevelFormat, $listItem): string { + $renderedCss = $outputFormat->safely(static function () use ($nextLevelFormat, $listItem): string { return $listItem->render($nextLevelFormat); }); if ($renderedCss === null) { @@ -436,7 +436,7 @@ protected function renderListContents(OutputFormat $oOutputFormat) if (!$isFirst) { // Had some output - $result .= $oOutputFormat->spaceAfterBlocks(); + $result .= $outputFormat->spaceAfterBlocks(); } return $result; diff --git a/src/CSSList/Document.php b/src/CSSList/Document.php index 51d87a4a..6e3fbdba 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -114,12 +114,12 @@ public function getSelectorsBySpecificity($sSpecificitySearch = null): array /** * Overrides `render()` to make format argument optional. */ - public function render(?OutputFormat $oOutputFormat = null): string + public function render(?OutputFormat $outputFormat = null): string { - if ($oOutputFormat === null) { - $oOutputFormat = new OutputFormat(); + if ($outputFormat === null) { + $outputFormat = new OutputFormat(); } - return $oOutputFormat->comments($this) . $this->renderListContents($oOutputFormat); + return $outputFormat->comments($this) . $this->renderListContents($outputFormat); } public function isRootList(): bool diff --git a/src/CSSList/KeyFrame.php b/src/CSSList/KeyFrame.php index 8a6bea7d..04a7e760 100644 --- a/src/CSSList/KeyFrame.php +++ b/src/CSSList/KeyFrame.php @@ -66,11 +66,11 @@ public function __toString(): string return $this->render(new OutputFormat()); } - public function render(OutputFormat $oOutputFormat): string + public function render(OutputFormat $outputFormat): string { - $sResult = $oOutputFormat->comments($this); - $sResult .= "@{$this->vendorKeyFrame} {$this->animationName}{$oOutputFormat->spaceBeforeOpeningBrace()}{"; - $sResult .= $this->renderListContents($oOutputFormat); + $sResult = $outputFormat->comments($this); + $sResult .= "@{$this->vendorKeyFrame} {$this->animationName}{$outputFormat->spaceBeforeOpeningBrace()}{"; + $sResult .= $this->renderListContents($outputFormat); $sResult .= '}'; return $sResult; } diff --git a/src/Property/CSSNamespace.php b/src/Property/CSSNamespace.php index 066bede6..2563e13a 100644 --- a/src/Property/CSSNamespace.php +++ b/src/Property/CSSNamespace.php @@ -60,10 +60,10 @@ public function __toString(): string return $this->render(new OutputFormat()); } - public function render(OutputFormat $oOutputFormat): string + public function render(OutputFormat $outputFormat): string { return '@namespace ' . ($this->sPrefix === null ? '' : $this->sPrefix . ' ') - . $this->mUrl->render($oOutputFormat) . ';'; + . $this->mUrl->render($outputFormat) . ';'; } /** diff --git a/src/Property/Charset.php b/src/Property/Charset.php index 94a9f983..387f92d1 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -77,9 +77,9 @@ public function __toString(): string return $this->render(new OutputFormat()); } - public function render(OutputFormat $oOutputFormat): string + public function render(OutputFormat $outputFormat): string { - return "{$oOutputFormat->comments($this)}@charset {$this->oCharset->render($oOutputFormat)};"; + return "{$outputFormat->comments($this)}@charset {$this->oCharset->render($outputFormat)};"; } public function atRuleName(): string diff --git a/src/Property/Import.php b/src/Property/Import.php index 329b0337..e852eed6 100644 --- a/src/Property/Import.php +++ b/src/Property/Import.php @@ -78,9 +78,9 @@ public function __toString(): string return $this->render(new OutputFormat()); } - public function render(OutputFormat $oOutputFormat): string + public function render(OutputFormat $outputFormat): string { - return $oOutputFormat->comments($this) . '@import ' . $this->location->render($oOutputFormat) + return $outputFormat->comments($this) . '@import ' . $this->location->render($outputFormat) . ($this->mediaQuery === null ? '' : ' ' . $this->mediaQuery) . ';'; } diff --git a/src/Renderable.php b/src/Renderable.php index fb505ec0..3a833f9a 100644 --- a/src/Renderable.php +++ b/src/Renderable.php @@ -8,7 +8,7 @@ interface Renderable { public function __toString(): string; - public function render(OutputFormat $oOutputFormat): string; + public function render(OutputFormat $outputFormat): string; /** * @return int<0, max> diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index ee15059b..cb07d268 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -267,11 +267,11 @@ public function __toString(): string return $this->render(new OutputFormat()); } - public function render(OutputFormat $oOutputFormat): string + public function render(OutputFormat $outputFormat): string { - $sResult = "{$oOutputFormat->comments($this)}{$this->sRule}:{$oOutputFormat->spaceAfterRuleName()}"; + $sResult = "{$outputFormat->comments($this)}{$this->sRule}:{$outputFormat->spaceAfterRuleName()}"; if ($this->mValue instanceof Value) { // Can also be a ValueList - $sResult .= $this->mValue->render($oOutputFormat); + $sResult .= $this->mValue->render($outputFormat); } else { $sResult .= $this->mValue; } diff --git a/src/RuleSet/AtRuleSet.php b/src/RuleSet/AtRuleSet.php index 67b274be..a56fe7c4 100644 --- a/src/RuleSet/AtRuleSet.php +++ b/src/RuleSet/AtRuleSet.php @@ -58,15 +58,15 @@ public function __toString(): string return $this->render(new OutputFormat()); } - public function render(OutputFormat $oOutputFormat): string + public function render(OutputFormat $outputFormat): string { - $sResult = $oOutputFormat->comments($this); + $sResult = $outputFormat->comments($this); $sArgs = $this->sArgs; if ($sArgs) { $sArgs = ' ' . $sArgs; } - $sResult .= "@{$this->sType}$sArgs{$oOutputFormat->spaceBeforeOpeningBrace()}{"; - $sResult .= $this->renderRules($oOutputFormat); + $sResult .= "@{$this->sType}$sArgs{$outputFormat->spaceBeforeOpeningBrace()}{"; + $sResult .= $this->renderRules($outputFormat); $sResult .= '}'; return $sResult; } diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 2ca5de28..8235304d 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -159,23 +159,23 @@ public function __toString(): string /** * @throws OutputException */ - public function render(OutputFormat $oOutputFormat): string + public function render(OutputFormat $outputFormat): string { - $sResult = $oOutputFormat->comments($this); + $sResult = $outputFormat->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->lineNumber); } - $sResult .= $oOutputFormat->sBeforeDeclarationBlock; - $sResult .= $oOutputFormat->implode( - $oOutputFormat->spaceBeforeSelectorSeparator() . ',' . $oOutputFormat->spaceAfterSelectorSeparator(), + $sResult .= $outputFormat->sBeforeDeclarationBlock; + $sResult .= $outputFormat->implode( + $outputFormat->spaceBeforeSelectorSeparator() . ',' . $outputFormat->spaceAfterSelectorSeparator(), $this->aSelectors ); - $sResult .= $oOutputFormat->sAfterDeclarationBlockSelectors; - $sResult .= $oOutputFormat->spaceBeforeOpeningBrace() . '{'; - $sResult .= $this->renderRules($oOutputFormat); + $sResult .= $outputFormat->sAfterDeclarationBlockSelectors; + $sResult .= $outputFormat->spaceBeforeOpeningBrace() . '{'; + $sResult .= $this->renderRules($outputFormat); $sResult .= '}'; - $sResult .= $oOutputFormat->sAfterDeclarationBlock; + $sResult .= $outputFormat->sAfterDeclarationBlock; return $sResult; } } diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 66a9043c..cdb39c0c 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -263,11 +263,11 @@ public function __toString(): string /** * @return string */ - protected function renderRules(OutputFormat $oOutputFormat) + protected function renderRules(OutputFormat $outputFormat) { $sResult = ''; $bIsFirst = true; - $oNextLevel = $oOutputFormat->nextLevel(); + $oNextLevel = $outputFormat->nextLevel(); foreach ($this->aRules as $aRules) { foreach ($aRules as $rule) { $sRendered = $oNextLevel->safely(static function () use ($rule, $oNextLevel): string { @@ -288,10 +288,10 @@ protected function renderRules(OutputFormat $oOutputFormat) if (!$bIsFirst) { // Had some output - $sResult .= $oOutputFormat->spaceAfterRules(); + $sResult .= $outputFormat->spaceAfterRules(); } - return $oOutputFormat->removeLastSemicolon($sResult); + return $outputFormat->removeLastSemicolon($sResult); } /** diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index 110c9907..45f671bc 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -108,9 +108,9 @@ public function __toString(): string return $this->render(new OutputFormat()); } - public function render(OutputFormat $oOutputFormat): string + public function render(OutputFormat $outputFormat): string { - $aArguments = parent::render($oOutputFormat); + $aArguments = parent::render($outputFormat); return "{$this->sName}({$aArguments})"; } } diff --git a/src/Value/CSSString.php b/src/Value/CSSString.php index b0c92e4c..2435fa1c 100644 --- a/src/Value/CSSString.php +++ b/src/Value/CSSString.php @@ -93,10 +93,10 @@ public function __toString(): string return $this->render(new OutputFormat()); } - public function render(OutputFormat $oOutputFormat): string + public function render(OutputFormat $outputFormat): string { $sString = \addslashes($this->sString); $sString = \str_replace("\n", '\\A', $sString); - return $oOutputFormat->getStringQuotingType() . $sString . $oOutputFormat->getStringQuotingType(); + return $outputFormat->getStringQuotingType() . $sString . $outputFormat->getStringQuotingType(); } } diff --git a/src/Value/CalcRuleValueList.php b/src/Value/CalcRuleValueList.php index 84b80ade..da1fbd05 100644 --- a/src/Value/CalcRuleValueList.php +++ b/src/Value/CalcRuleValueList.php @@ -16,8 +16,8 @@ public function __construct($lineNumber = 0) parent::__construct(',', $lineNumber); } - public function render(OutputFormat $oOutputFormat): string + public function render(OutputFormat $outputFormat): string { - return $oOutputFormat->implode(' ', $this->aComponents); + return $outputFormat->implode(' ', $this->aComponents); } } diff --git a/src/Value/LineName.php b/src/Value/LineName.php index 6caa8617..bee05a40 100644 --- a/src/Value/LineName.php +++ b/src/Value/LineName.php @@ -52,7 +52,7 @@ public function __toString(): string return $this->render(new OutputFormat()); } - public function render(OutputFormat $oOutputFormat): string + public function render(OutputFormat $outputFormat): string { return '[' . parent::render(OutputFormat::createCompact()) . ']'; } diff --git a/src/Value/Size.php b/src/Value/Size.php index ad38de1d..a36f394b 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -213,7 +213,7 @@ public function __toString(): string return $this->render(new OutputFormat()); } - public function render(OutputFormat $oOutputFormat): string + public function render(OutputFormat $outputFormat): string { $l = \localeconv(); $sPoint = \preg_quote($l['decimal_point'], '/'); diff --git a/src/Value/URL.php b/src/Value/URL.php index 1608d12d..256d6c9e 100644 --- a/src/Value/URL.php +++ b/src/Value/URL.php @@ -79,8 +79,8 @@ public function __toString(): string return $this->render(new OutputFormat()); } - public function render(OutputFormat $oOutputFormat): string + public function render(OutputFormat $outputFormat): string { - return "url({$this->oURL->render($oOutputFormat)})"; + return "url({$this->oURL->render($outputFormat)})"; } } diff --git a/src/Value/ValueList.php b/src/Value/ValueList.php index d18ad9ef..c9739d1d 100644 --- a/src/Value/ValueList.php +++ b/src/Value/ValueList.php @@ -88,11 +88,11 @@ public function __toString(): string return $this->render(new OutputFormat()); } - public function render(OutputFormat $oOutputFormat): string + public function render(OutputFormat $outputFormat): string { - return $oOutputFormat->implode( - $oOutputFormat->spaceBeforeListArgumentSeparator($this->sSeparator) . $this->sSeparator - . $oOutputFormat->spaceAfterListArgumentSeparator($this->sSeparator), + return $outputFormat->implode( + $outputFormat->spaceBeforeListArgumentSeparator($this->sSeparator) . $this->sSeparator + . $outputFormat->spaceAfterListArgumentSeparator($this->sSeparator), $this->aComponents ); } From 62a2e6579f9e710936149b35e2be0da4769e2608 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 13 Feb 2025 03:05:28 +0100 Subject: [PATCH 174/555] [TASK] Add strict PHPStan rules (#909) Now we are using the same static analysis tools as our sister project. --- composer.json | 1 + config/phpstan-baseline.neon | 150 +++++++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+) diff --git a/composer.json b/composer.json index b147d6a7..65c984ca 100644 --- a/composer.json +++ b/composer.json @@ -31,6 +31,7 @@ "phpstan/extension-installer": "1.4.3", "phpstan/phpstan": "1.12.16 || 2.1.2", "phpstan/phpstan-phpunit": "1.4.2 || 2.0.4", + "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.3", "phpunit/phpunit": "8.5.41", "rector/rector": "1.2.10 || 2.0.7" }, diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 2551deed..17b94e84 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -12,6 +12,12 @@ parameters: count: 1 path: ../src/CSSList/AtRuleBlockList.php + - + message: '#^Only booleans are allowed in an if condition, string given\.$#' + identifier: if.condNotBoolean + count: 1 + path: ../src/CSSList/AtRuleBlockList.php + - message: '#^Parameter &\$result by\-ref type of method Sabberworm\\CSS\\CSSList\\CSSBlockList\:\:allSelectors\(\) expects array\, array\ given\.$#' identifier: parameterByRef.type @@ -48,6 +54,24 @@ parameters: count: 1 path: ../src/CSSList/CSSList.php + - + message: '#^Loose comparison via "\!\=" is not allowed\.$#' + identifier: notEqual.notAllowed + count: 1 + path: ../src/CSSList/CSSList.php + + - + message: '#^Loose comparison via "\=\=" is not allowed\.$#' + identifier: equal.notAllowed + count: 1 + path: ../src/CSSList/CSSList.php + + - + message: '#^Short ternary operator is not allowed\. Use null coalesce operator if applicable or consider using long ternary\.$#' + identifier: ternary.shortNotAllowed + count: 1 + path: ../src/CSSList/CSSList.php + - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:comments\(\)\.$#' identifier: method.notFound @@ -66,6 +90,12 @@ parameters: count: 1 path: ../src/CSSList/KeyFrame.php + - + message: '#^Variable property access on \$this\(Sabberworm\\CSS\\OutputFormat\)\.$#' + identifier: property.dynamicName + count: 4 + path: ../src/OutputFormat.php + - message: '#^Default value of the parameter \#2 \$bIncludeEnd \(false\) of method Sabberworm\\CSS\\Parsing\\ParserState\:\:consumeUntil\(\) is incompatible with type string\.$#' identifier: parameter.defaultValue @@ -78,6 +108,24 @@ parameters: count: 1 path: ../src/Parsing/ParserState.php + - + message: '#^Loose comparison via "\=\=" is not allowed\.$#' + identifier: equal.notAllowed + count: 1 + path: ../src/Parsing/ParserState.php + + - + message: '#^Only booleans are allowed in a negated boolean, string given\.$#' + identifier: booleanNot.exprNotBoolean + count: 1 + path: ../src/Parsing/ParserState.php + + - + message: '#^Only booleans are allowed in an if condition, string given\.$#' + identifier: if.condNotBoolean + count: 1 + path: ../src/Parsing/ParserState.php + - message: '#^PHPDoc tag @return with type array\\|void is not subtype of native type array\.$#' identifier: return.phpDocType @@ -90,6 +138,12 @@ parameters: count: 1 path: ../src/Property/CSSNamespace.php + - + message: '#^Only booleans are allowed in an if condition, string given\.$#' + identifier: if.condNotBoolean + count: 1 + path: ../src/Property/CSSNamespace.php + - message: '#^Return type \(array\\) of method Sabberworm\\CSS\\Property\\CSSNamespace\:\:atRuleArgs\(\) should be compatible with return type \(string\|null\) of method Sabberworm\\CSS\\Property\\AtRule\:\:atRuleArgs\(\)$#' identifier: method.childReturnType @@ -114,6 +168,12 @@ parameters: count: 1 path: ../src/Property/Import.php + - + message: '#^Only booleans are allowed in an if condition, string given\.$#' + identifier: if.condNotBoolean + count: 1 + path: ../src/Property/Import.php + - message: '#^Return type \(array\\) of method Sabberworm\\CSS\\Property\\Import\:\:atRuleArgs\(\) should be compatible with return type \(string\|null\) of method Sabberworm\\CSS\\Property\\AtRule\:\:atRuleArgs\(\)$#' identifier: method.childReturnType @@ -132,6 +192,18 @@ parameters: count: 1 path: ../src/Rule/Rule.php + - + message: '#^Construct empty\(\) is not allowed\. Use more strict comparison\.$#' + identifier: empty.notAllowed + count: 1 + path: ../src/Rule/Rule.php + + - + message: '#^Only booleans are allowed in an if condition, Sabberworm\\CSS\\Value\\RuleValueList\|string\|null given\.$#' + identifier: if.condNotBoolean + count: 1 + path: ../src/Rule/Rule.php + - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:comments\(\)\.$#' identifier: method.notFound @@ -144,6 +216,12 @@ parameters: count: 1 path: ../src/RuleSet/AtRuleSet.php + - + message: '#^Only booleans are allowed in an if condition, string given\.$#' + identifier: if.condNotBoolean + count: 1 + path: ../src/RuleSet/AtRuleSet.php + - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:comments\(\)\.$#' identifier: method.notFound @@ -174,6 +252,24 @@ parameters: count: 1 path: ../src/RuleSet/DeclarationBlock.php + - + message: '#^Foreach overwrites \$mSelector with its value variable\.$#' + identifier: foreach.valueOverwrite + count: 1 + path: ../src/RuleSet/DeclarationBlock.php + + - + message: '#^Loose comparison via "\!\=" is not allowed\.$#' + identifier: notEqual.notAllowed + count: 1 + path: ../src/RuleSet/DeclarationBlock.php + + - + message: '#^Loose comparison via "\=\=" is not allowed\.$#' + identifier: equal.notAllowed + count: 1 + path: ../src/RuleSet/DeclarationBlock.php + - message: '#^Argument of an invalid type Sabberworm\\CSS\\Rule\\Rule supplied for foreach, only iterables are supported\.$#' identifier: foreach.nonIterable @@ -210,6 +306,30 @@ parameters: count: 1 path: ../src/RuleSet/RuleSet.php + - + message: '#^Only booleans are allowed in a negated boolean, string\|null given\.$#' + identifier: booleanNot.exprNotBoolean + count: 2 + path: ../src/RuleSet/RuleSet.php + + - + message: '#^Parameter \#1 \$comments \(array\\) of method Sabberworm\\CSS\\RuleSet\\RuleSet\:\:addComments\(\) should be contravariant with parameter \$comments \(array\\) of method Sabberworm\\CSS\\Comment\\Commentable\:\:addComments\(\)$#' + identifier: method.childParameterType + count: 1 + path: ../src/RuleSet/RuleSet.php + + - + message: '#^Parameter \#1 \$comments \(array\\) of method Sabberworm\\CSS\\RuleSet\\RuleSet\:\:setComments\(\) should be contravariant with parameter \$comments \(array\\) of method Sabberworm\\CSS\\Comment\\Commentable\:\:setComments\(\)$#' + identifier: method.childParameterType + count: 1 + path: ../src/RuleSet/RuleSet.php + + - + message: '#^Loose comparison via "\!\=" is not allowed\.$#' + identifier: notEqual.notAllowed + count: 3 + path: ../src/Value/CalcFunction.php + - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:implode\(\)\.$#' identifier: method.notFound @@ -234,12 +354,30 @@ parameters: count: 1 path: ../src/Value/Color.php + - + message: '#^Call to method Sabberworm\\CSS\\Value\\Color\:\:hasNoneAsComponentValue\(\) with incorrect case\: HasNoneAsComponentValue$#' + identifier: method.nameCase + count: 1 + path: ../src/Value/Color.php + - message: '#^Cannot call method getSize\(\) on Sabberworm\\CSS\\Value\\Value\|string\.$#' identifier: method.nonObject count: 3 path: ../src/Value/Color.php + - + message: '#^Loose comparison via "\=\=" is not allowed\.$#' + identifier: equal.notAllowed + count: 3 + path: ../src/Value/Color.php + + - + message: '#^Loose comparison via "\!\=" is not allowed\.$#' + identifier: notEqual.notAllowed + count: 1 + path: ../src/Value/Size.php + - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:implode\(\)\.$#' identifier: method.notFound @@ -258,3 +396,15 @@ parameters: count: 1 path: ../src/Value/ValueList.php + - + message: '#^Dynamic call to static method Sabberworm\\CSS\\Tests\\ParserTest\:\:parsedStructureForFile\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 2 + path: ../tests/ParserTest.php + + - + message: '#^Loose comparison via "\=\=" is not allowed\.$#' + identifier: equal.notAllowed + count: 11 + path: ../tests/ParserTest.php + From 537f5e8376caadbed29768eeddd5faf36f1c66b3 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 13 Feb 2025 03:18:32 +0100 Subject: [PATCH 175/555] [TASK] Mark many parsing-related methods as `@internal` (#908) The only parsing method that is expected to be called from outside of this library is `Parser::parse()`. --- 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 b6182da3..ef216818 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Please also have a look at our ### Changed +- Mark parsing-related methods of most CSS elements as `@internal` (#907) - Mark `OutputFormat::nextLevel()` as `@internal` (#901) - Only allow `string` for some `OutputFormat` properties (#885) - Make all non-private properties `@internal` (#886) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index a037ee9b..7f21893b 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -67,6 +67,8 @@ public function __construct($lineNumber = 0) /** * @throws UnexpectedTokenException * @throws SourceException + * + * @internal since V8.8.0 */ public static function parseList(ParserState $parserState, CSSList $list): void { diff --git a/src/CSSList/Document.php b/src/CSSList/Document.php index 6e3fbdba..3022be38 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -28,6 +28,8 @@ public function __construct($lineNumber = 0) /** * @throws SourceException + * + * @internal since V8.8.0 */ public static function parse(ParserState $parserState): Document { diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index cb28cdb0..651c6d64 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -136,6 +136,8 @@ public function setPosition($iPosition): void * @return string * * @throws UnexpectedTokenException + * + * @internal since V8.8.0 */ public function parseIdentifier($bIgnoreCase = true) { @@ -167,6 +169,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 cb07d268..872e7162 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -79,6 +79,8 @@ public function __construct($sRule, $lineNumber = 0, $iColNo = 0) /** * @throws UnexpectedEOFException * @throws UnexpectedTokenException + * + * @internal since V8.8.0 */ public static function parse(ParserState $parserState): Rule { diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 8235304d..79e0dd0c 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -45,6 +45,8 @@ public function __construct($lineNumber = 0) * * @throws UnexpectedTokenException * @throws UnexpectedEOFException + * + * @internal since V8.8.0 */ public static function parse(ParserState $parserState, $list = null) { diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index cdb39c0c..528f57cf 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -56,6 +56,8 @@ public function __construct($lineNumber = 0) /** * @throws UnexpectedTokenException * @throws UnexpectedEOFException + * + * @internal since V8.8.0 */ public static function parseRuleSet(ParserState $parserState, RuleSet $ruleSet): void { diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index 45f671bc..458c832f 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -44,6 +44,8 @@ public function __construct($sName, $aArguments, $sSeparator = ',', $lineNumber * @throws SourceException * @throws UnexpectedEOFException * @throws UnexpectedTokenException + * + * @internal since V8.8.0 */ public static function parse(ParserState $parserState, bool $bIgnoreCase = false): CSSFunction { diff --git a/src/Value/CSSString.php b/src/Value/CSSString.php index 2435fa1c..6cc609ea 100644 --- a/src/Value/CSSString.php +++ b/src/Value/CSSString.php @@ -36,6 +36,8 @@ public function __construct($sString, $lineNumber = 0) * @throws SourceException * @throws UnexpectedEOFException * @throws UnexpectedTokenException + * + * @internal since V8.8.0 */ public static function parse(ParserState $parserState): CSSString { diff --git a/src/Value/CalcFunction.php b/src/Value/CalcFunction.php index 5d84a9a8..2cd9f663 100644 --- a/src/Value/CalcFunction.php +++ b/src/Value/CalcFunction.php @@ -23,6 +23,8 @@ class CalcFunction extends CSSFunction /** * @throws UnexpectedTokenException * @throws UnexpectedEOFException + * + * @internal since V8.8.0 */ public static function parse(ParserState $parserState, bool $bIgnoreCase = false): CSSFunction { diff --git a/src/Value/Color.php b/src/Value/Color.php index 16e7dbd8..76b81a04 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -27,6 +27,8 @@ public function __construct(array $colorValues, $lineNumber = 0) /** * @throws UnexpectedEOFException * @throws UnexpectedTokenException + * + * @internal since V8.8.0 */ public static function parse(ParserState $parserState, bool $ignoreCase = false): CSSFunction { diff --git a/src/Value/LineName.php b/src/Value/LineName.php index bee05a40..57259dea 100644 --- a/src/Value/LineName.php +++ b/src/Value/LineName.php @@ -23,6 +23,8 @@ public function __construct(array $aComponents = [], $lineNumber = 0) /** * @throws UnexpectedTokenException * @throws UnexpectedEOFException + * + * @internal since V8.8.0 */ public static function parse(ParserState $parserState): LineName { diff --git a/src/Value/Size.php b/src/Value/Size.php index a36f394b..5f5ab4d7 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -86,6 +86,8 @@ public function __construct($fSize, $sUnit = null, $bIsColorComponent = false, $ * * @throws UnexpectedEOFException * @throws UnexpectedTokenException + * + * @internal since V8.8.0 */ public static function parse(ParserState $parserState, $bIsColorComponent = false): Size { diff --git a/src/Value/URL.php b/src/Value/URL.php index 256d6c9e..5353f966 100644 --- a/src/Value/URL.php +++ b/src/Value/URL.php @@ -33,6 +33,8 @@ public function __construct(CSSString $oURL, $lineNumber = 0) * @throws SourceException * @throws UnexpectedEOFException * @throws UnexpectedTokenException + * + * @internal since V8.8.0 */ public static function parse(ParserState $parserState): URL { diff --git a/src/Value/Value.php b/src/Value/Value.php index 4e27b668..4410551a 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -38,6 +38,8 @@ public function __construct($lineNumber = 0) * * @throws UnexpectedTokenException * @throws UnexpectedEOFException + * + * @internal since V8.8.0 */ public static function parseValue(ParserState $parserState, array $aListDelimiters = []) { @@ -114,6 +116,8 @@ public static function parseValue(ParserState $parserState, array $aListDelimite * * @throws UnexpectedEOFException * @throws UnexpectedTokenException + * + * @internal since V8.8.0 */ public static function parseIdentifierOrFunction(ParserState $parserState, $bIgnoreCase = false) { @@ -144,6 +148,8 @@ public static function parseIdentifierOrFunction(ParserState $parserState, $bIgn * @throws UnexpectedEOFException * @throws UnexpectedTokenException * @throws SourceException + * + * @internal since V8.8.0 */ public static function parsePrimitiveValue(ParserState $parserState) { From 8821cfb00a6351a98a24f2b09724afa468a91d11 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 13 Feb 2025 23:07:00 +0100 Subject: [PATCH 176/555] [DOCS] Fix a PR ID in the changelog (#914) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef216818..de02deb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ Please also have a look at our ### Changed -- Mark parsing-related methods of most CSS elements as `@internal` (#907) +- Mark parsing-related methods of most CSS elements as `@internal` (#908) - Mark `OutputFormat::nextLevel()` as `@internal` (#901) - Only allow `string` for some `OutputFormat` properties (#885) - Make all non-private properties `@internal` (#886) From 60578476b5575c35360e7e9d820fdd4ae508fd43 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 14 Feb 2025 02:17:38 +0100 Subject: [PATCH 177/555] [TASK] Move some tests for `Comment` to functional tests (#912) Move the tests that rely on another class (`OutputFormat`) (or might in the future). Part of #757. --- tests/Functional/.gitkeep | 0 tests/Functional/Comment/CommentTest.php | 80 ++++++++++++++++++++++++ tests/Unit/Comment/CommentTest.php | 27 -------- 3 files changed, 80 insertions(+), 27 deletions(-) delete mode 100644 tests/Functional/.gitkeep create mode 100644 tests/Functional/Comment/CommentTest.php diff --git a/tests/Functional/.gitkeep b/tests/Functional/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/Functional/Comment/CommentTest.php b/tests/Functional/Comment/CommentTest.php new file mode 100644 index 00000000..100d2531 --- /dev/null +++ b/tests/Functional/Comment/CommentTest.php @@ -0,0 +1,80 @@ +setComment($comment); + + self::assertSame('/*' . $comment . '*/', (string) $subject); + } + + /** + * @test + */ + public function renderWithVirginOutputFormatRendersCommentEnclosedInCommentDelimiters(): void + { + $comment = 'There is no spoon.'; + $subject = new Comment(); + + $subject->setComment($comment); + + self::assertSame('/*' . $comment . '*/', $subject->render(new OutputFormat())); + } + + /** + * @test + */ + public function renderWithDefaultOutputFormatRendersCommentEnclosedInCommentDelimiters(): void + { + $comment = 'There is no spoon.'; + $subject = new Comment(); + + $subject->setComment($comment); + + self::assertSame('/*' . $comment . '*/', $subject->render(OutputFormat::create())); + } + + /** + * @test + */ + public function renderWithCompactOutputFormatRendersCommentEnclosedInCommentDelimiters(): void + { + $comment = 'There is no spoon.'; + $subject = new Comment(); + + $subject->setComment($comment); + + self::assertSame('/*' . $comment . '*/', $subject->render(OutputFormat::createCompact())); + } + + /** + * @test + */ + public function renderWithPrettyOutputFormatRendersCommentEnclosedInCommentDelimiters(): void + { + $comment = 'There is no spoon.'; + $subject = new Comment(); + + $subject->setComment($comment); + + self::assertSame('/*' . $comment . '*/', $subject->render(OutputFormat::createPretty())); + } +} diff --git a/tests/Unit/Comment/CommentTest.php b/tests/Unit/Comment/CommentTest.php index c9e07102..2bfe670c 100644 --- a/tests/Unit/Comment/CommentTest.php +++ b/tests/Unit/Comment/CommentTest.php @@ -6,7 +6,6 @@ use PHPUnit\Framework\TestCase; use Sabberworm\CSS\Comment\Comment; -use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Renderable; /** @@ -78,30 +77,4 @@ public function getLineNoInitiallyReturnsLineNumberPassedToConstructor(): void self::assertSame($lineNumber, $subject->getLineNo()); } - - /** - * @test - */ - public function toStringRendersCommentEnclosedInCommentDelimiters(): void - { - $comment = 'There is no spoon.'; - $subject = new Comment(); - - $subject->setComment($comment); - - self::assertSame('/*' . $comment . '*/', (string) $subject); - } - - /** - * @test - */ - public function renderRendersCommentEnclosedInCommentDelimiters(): void - { - $comment = 'There is no spoon.'; - $subject = new Comment(); - - $subject->setComment($comment); - - self::assertSame('/*' . $comment . '*/', $subject->render(new OutputFormat())); - } } From 7121bc3c3c2e86e1c2d10a2ef5e106c1858ce534 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 14 Feb 2025 03:02:12 +0100 Subject: [PATCH 178/555] [TASK] Add tests for the exceptions (#911) --- tests/Unit/Parsing/OutputExceptionTest.php | 76 ++++++++ tests/Unit/Parsing/SourceExceptionTest.php | 67 +++++++ .../Parsing/UnexpectedEOFExceptionTest.php | 177 ++++++++++++++++++ .../Parsing/UnexpectedTokenExceptionTest.php | 177 ++++++++++++++++++ 4 files changed, 497 insertions(+) create mode 100644 tests/Unit/Parsing/OutputExceptionTest.php create mode 100644 tests/Unit/Parsing/SourceExceptionTest.php create mode 100644 tests/Unit/Parsing/UnexpectedEOFExceptionTest.php create mode 100644 tests/Unit/Parsing/UnexpectedTokenExceptionTest.php diff --git a/tests/Unit/Parsing/OutputExceptionTest.php b/tests/Unit/Parsing/OutputExceptionTest.php new file mode 100644 index 00000000..d3409aa4 --- /dev/null +++ b/tests/Unit/Parsing/OutputExceptionTest.php @@ -0,0 +1,76 @@ +getMessage()); + } + + /** + * @test + */ + public function getLineNoByDefaultReturnsZero(): void + { + $exception = new OutputException('foo'); + + self::assertSame(0, $exception->getLineNo()); + } + + /** + * @test + */ + public function getLineNoReturnsLineNumberProvidedToConstructor(): void + { + $lineNumber = 17; + $exception = new OutputException('foo', $lineNumber); + + self::assertSame($lineNumber, $exception->getLineNo()); + } + + /** + * @test + */ + public function getMessageWithLineNumberProvidedIncludesLineNumber(): void + { + $lineNumber = 17; + $exception = new OutputException('foo', $lineNumber); + + self::assertStringContainsString(' [line no: ' . $lineNumber . ']', $exception->getMessage()); + } + + /** + * @test + */ + public function canBeThrown(): void + { + $this->expectException(OutputException::class); + + throw new OutputException('foo'); + } +} diff --git a/tests/Unit/Parsing/SourceExceptionTest.php b/tests/Unit/Parsing/SourceExceptionTest.php new file mode 100644 index 00000000..b497ff52 --- /dev/null +++ b/tests/Unit/Parsing/SourceExceptionTest.php @@ -0,0 +1,67 @@ +getMessage()); + } + + /** + * @test + */ + public function getLineNoByDefaultReturnsZero(): void + { + $exception = new SourceException('foo'); + + self::assertSame(0, $exception->getLineNo()); + } + + /** + * @test + */ + public function getLineNoReturnsLineNumberProvidedToConstructor(): void + { + $lineNumber = 17; + $exception = new SourceException('foo', $lineNumber); + + self::assertSame($lineNumber, $exception->getLineNo()); + } + + /** + * @test + */ + public function getMessageWithLineNumberProvidedIncludesLineNumber(): void + { + $lineNumber = 17; + $exception = new SourceException('foo', $lineNumber); + + self::assertStringContainsString(' [line no: ' . $lineNumber . ']', $exception->getMessage()); + } + + /** + * @test + */ + public function canBeThrown(): void + { + $this->expectException(SourceException::class); + + throw new SourceException('foo'); + } +} diff --git a/tests/Unit/Parsing/UnexpectedEOFExceptionTest.php b/tests/Unit/Parsing/UnexpectedEOFExceptionTest.php new file mode 100644 index 00000000..929609ef --- /dev/null +++ b/tests/Unit/Parsing/UnexpectedEOFExceptionTest.php @@ -0,0 +1,177 @@ +getLineNo()); + } + + /** + * @test + */ + public function getLineNoReturnsLineNumberProvidedToConstructor(): void + { + $lineNumber = 17; + $exception = new UnexpectedEOFException('expected', 'found', 'literal', $lineNumber); + + self::assertSame($lineNumber, $exception->getLineNo()); + } + + /** + * @test + */ + public function getMessageWithLineNumberProvidedIncludesLineNumber(): void + { + $lineNumber = 17; + $exception = new UnexpectedEOFException('expected', 'found', 'literal', $lineNumber); + + self::assertStringContainsString(' [line no: ' . $lineNumber . ']', $exception->getMessage()); + } + + /** + * @test + */ + public function canBeThrown(): void + { + $this->expectException(UnexpectedEOFException::class); + + throw new UnexpectedEOFException('expected', 'found'); + } + + /** + * @test + */ + public function messageByDefaultRefersToTokenNotFound(): void + { + $expected = 'tea'; + $found = 'coffee'; + + $exception = new UnexpectedEOFException($expected, $found); + + $expectedMessage = 'Token “' . $expected . '” (literal) not found. Got “' . $found . '”.'; + self::assertStringContainsString($expectedMessage, $exception->getMessage()); + } + + /** + * @test + */ + public function messageForInvalidMatchTypeRefersToTokenNotFound(): void + { + $expected = 'tea'; + $found = 'coffee'; + + $exception = new UnexpectedEOFException($expected, $found, 'coding'); + + $expectedMessage = 'Token “' . $expected . '” (coding) not found. Got “' . $found . '”.'; + self::assertStringContainsString($expectedMessage, $exception->getMessage()); + } + + /** + * @test + */ + public function messageForLiteralMatchTypeRefersToTokenNotFound(): void + { + $expected = 'tea'; + $found = 'coffee'; + + $exception = new UnexpectedEOFException($expected, $found, 'literal'); + + $expectedMessage = 'Token “' . $expected . '” (literal) not found. Got “' . $found . '”.'; + self::assertStringContainsString($expectedMessage, $exception->getMessage()); + } + + /** + * @test + */ + public function messageForSearchMatchTypeRefersToNoResults(): void + { + $expected = 'tea'; + $found = 'coffee'; + + $exception = new UnexpectedEOFException($expected, $found, 'search'); + + $expectedMessage = 'Search for “' . $expected . '” returned no results. Context: “' . $found . '”.'; + self::assertStringContainsString($expectedMessage, $exception->getMessage()); + } + + /** + * @test + */ + public function messageForCountMatchTypeRefersToNumberOfCharacters(): void + { + $expected = 'tea'; + $found = 'coffee'; + + $exception = new UnexpectedEOFException($expected, $found, 'count'); + + $expectedMessage = 'Next token was expected to have ' . $expected . ' chars. Context: “' . $found . '”.'; + self::assertStringContainsString($expectedMessage, $exception->getMessage()); + } + + /** + * @test + */ + public function messageForIdentifierMatchTypeRefersToIdentifier(): void + { + $expected = 'tea'; + $found = 'coffee'; + + $exception = new UnexpectedEOFException($expected, $found, 'identifier'); + + $expectedMessage = 'Identifier expected. Got “' . $found . '”'; + self::assertStringContainsString($expectedMessage, $exception->getMessage()); + } + + /** + * @test + */ + public function messageForCustomMatchTypeMentionsExpectedAndFound(): void + { + $expected = 'tea'; + $found = 'coffee'; + + $exception = new UnexpectedEOFException($expected, $found, 'custom'); + + $expectedMessage = $expected . ' ' . $found; + self::assertStringContainsString($expectedMessage, $exception->getMessage()); + } + + /** + * @test + */ + public function messageForCustomMatchTypeTrimsMessage(): void + { + $expected = 'tea'; + $found = 'coffee'; + + $exception = new UnexpectedEOFException(' ' . $expected, $found . ' ', 'custom'); + + $expectedMessage = $expected . ' ' . $found; + self::assertStringContainsString($expectedMessage, $exception->getMessage()); + } +} diff --git a/tests/Unit/Parsing/UnexpectedTokenExceptionTest.php b/tests/Unit/Parsing/UnexpectedTokenExceptionTest.php new file mode 100644 index 00000000..e5c7a64d --- /dev/null +++ b/tests/Unit/Parsing/UnexpectedTokenExceptionTest.php @@ -0,0 +1,177 @@ +getLineNo()); + } + + /** + * @test + */ + public function getLineNoReturnsLineNumberProvidedToConstructor(): void + { + $lineNumber = 17; + $exception = new UnexpectedTokenException('expected', 'found', 'literal', $lineNumber); + + self::assertSame($lineNumber, $exception->getLineNo()); + } + + /** + * @test + */ + public function getMessageWithLineNumberProvidedIncludesLineNumber(): void + { + $lineNumber = 17; + $exception = new UnexpectedTokenException('expected', 'found', 'literal', $lineNumber); + + self::assertStringContainsString(' [line no: ' . $lineNumber . ']', $exception->getMessage()); + } + + /** + * @test + */ + public function canBeThrown(): void + { + $this->expectException(UnexpectedTokenException::class); + + throw new UnexpectedTokenException('expected', 'found'); + } + + /** + * @test + */ + public function messageByDefaultRefersToTokenNotFound(): void + { + $expected = 'tea'; + $found = 'coffee'; + + $exception = new UnexpectedTokenException($expected, $found); + + $expectedMessage = 'Token “' . $expected . '” (literal) not found. Got “' . $found . '”.'; + self::assertStringContainsString($expectedMessage, $exception->getMessage()); + } + + /** + * @test + */ + public function messageForInvalidMatchTypeRefersToTokenNotFound(): void + { + $expected = 'tea'; + $found = 'coffee'; + + $exception = new UnexpectedTokenException($expected, $found, 'coding'); + + $expectedMessage = 'Token “' . $expected . '” (coding) not found. Got “' . $found . '”.'; + self::assertStringContainsString($expectedMessage, $exception->getMessage()); + } + + /** + * @test + */ + public function messageForLiteralMatchTypeRefersToTokenNotFound(): void + { + $expected = 'tea'; + $found = 'coffee'; + + $exception = new UnexpectedTokenException($expected, $found, 'literal'); + + $expectedMessage = 'Token “' . $expected . '” (literal) not found. Got “' . $found . '”.'; + self::assertStringContainsString($expectedMessage, $exception->getMessage()); + } + + /** + * @test + */ + public function messageForSearchMatchTypeRefersToNoResults(): void + { + $expected = 'tea'; + $found = 'coffee'; + + $exception = new UnexpectedTokenException($expected, $found, 'search'); + + $expectedMessage = 'Search for “' . $expected . '” returned no results. Context: “' . $found . '”.'; + self::assertStringContainsString($expectedMessage, $exception->getMessage()); + } + + /** + * @test + */ + public function messageForCountMatchTypeRefersToNumberOfCharacters(): void + { + $expected = 'tea'; + $found = 'coffee'; + + $exception = new UnexpectedTokenException($expected, $found, 'count'); + + $expectedMessage = 'Next token was expected to have ' . $expected . ' chars. Context: “' . $found . '”.'; + self::assertStringContainsString($expectedMessage, $exception->getMessage()); + } + + /** + * @test + */ + public function messageForIdentifierMatchTypeRefersToIdentifier(): void + { + $expected = 'tea'; + $found = 'coffee'; + + $exception = new UnexpectedTokenException($expected, $found, 'identifier'); + + $expectedMessage = 'Identifier expected. Got “' . $found . '”'; + self::assertStringContainsString($expectedMessage, $exception->getMessage()); + } + + /** + * @test + */ + public function messageForCustomMatchTypeMentionsExpectedAndFound(): void + { + $expected = 'tea'; + $found = 'coffee'; + + $exception = new UnexpectedTokenException($expected, $found, 'custom'); + + $expectedMessage = $expected . ' ' . $found; + self::assertStringContainsString($expectedMessage, $exception->getMessage()); + } + + /** + * @test + */ + public function messageForCustomMatchTypeTrimsMessage(): void + { + $expected = 'tea'; + $found = 'coffee'; + + $exception = new UnexpectedTokenException(' ' . $expected, $found . ' ', 'custom'); + + $expectedMessage = $expected . ' ' . $found; + self::assertStringContainsString($expectedMessage, $exception->getMessage()); + } +} From 7d75688fdb4111e0bcac0342ae831edc84c98e68 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 14 Feb 2025 03:30:54 +0100 Subject: [PATCH 179/555] [TASK] Make the tests more strict (#915) --- config/phpstan-baseline.neon | 13 ------------- tests/ParserTest.php | 35 ++++++++++++++++++----------------- 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 17b94e84..c10c9c9d 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -395,16 +395,3 @@ parameters: identifier: method.notFound count: 1 path: ../src/Value/ValueList.php - - - - message: '#^Dynamic call to static method Sabberworm\\CSS\\Tests\\ParserTest\:\:parsedStructureForFile\(\)\.$#' - identifier: staticMethod.dynamicCall - count: 2 - path: ../tests/ParserTest.php - - - - message: '#^Loose comparison via "\=\=" is not allowed\.$#' - identifier: equal.notAllowed - count: 11 - path: ../tests/ParserTest.php - diff --git a/tests/ParserTest.php b/tests/ParserTest.php index 15d152a4..9ff7b283 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -21,6 +21,7 @@ use Sabberworm\CSS\RuleSet\DeclarationBlock; use Sabberworm\CSS\RuleSet\RuleSet; use Sabberworm\CSS\Settings; +use Sabberworm\CSS\Value\CalcFunction; use Sabberworm\CSS\Value\Color; use Sabberworm\CSS\Value\Size; use Sabberworm\CSS\Value\URL; @@ -193,37 +194,37 @@ public function unicodeParsing(): void } $contentRules = $ruleSet->getRules('content'); $firstContentRuleAsString = $contentRules[0]->getValue()->__toString(); - if ($selector == '.test-1') { + if ($selector === '.test-1') { self::assertSame('" "', $firstContentRuleAsString); } - if ($selector == '.test-2') { + if ($selector === '.test-2') { self::assertSame('"é"', $firstContentRuleAsString); } - if ($selector == '.test-3') { + if ($selector === '.test-3') { self::assertSame('" "', $firstContentRuleAsString); } - if ($selector == '.test-4') { + if ($selector === '.test-4') { self::assertSame('"𝄞"', $firstContentRuleAsString); } - if ($selector == '.test-5') { + if ($selector === '.test-5') { self::assertSame('"水"', $firstContentRuleAsString); } - if ($selector == '.test-6') { + if ($selector === '.test-6') { self::assertSame('"¥"', $firstContentRuleAsString); } - if ($selector == '.test-7') { + if ($selector === '.test-7') { self::assertSame('"\\A"', $firstContentRuleAsString); } - if ($selector == '.test-8') { + if ($selector === '.test-8') { self::assertSame('"\\"\\""', $firstContentRuleAsString); } - if ($selector == '.test-9') { + if ($selector === '.test-9') { self::assertSame('"\\"\\\'"', $firstContentRuleAsString); } - if ($selector == '.test-10') { + if ($selector === '.test-10') { self::assertSame('"\\\'\\\\"', $firstContentRuleAsString); } - if ($selector == '.test-11') { + if ($selector === '.test-11') { self::assertSame('"test"', $firstContentRuleAsString); } } @@ -1158,8 +1159,8 @@ public function flatCommentExtractingTwoComments(): void $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()); + self::assertSame('Find Me!', $rule1Comments[0]->getComment()); + self::assertSame('Find Me Too!', $rule2Comments[0]->getComment()); } /** @@ -1211,7 +1212,7 @@ public function largeSizeValuesInFile(): void */ public function scientificNotationSizeValuesInFile(): void { - $document = $this->parsedStructureForFile( + $document = self::parsedStructureForFile( 'scientific-notation-numbers', Settings::create()->withMultibyteSupport(false) ); @@ -1233,12 +1234,12 @@ public function lonelyImport(): void public function escapedSpecialCaseTokens(): void { - $document = $this->parsedStructureForFile('escaped-tokens'); + $document = self::parsedStructureForFile('escaped-tokens'); $contents = $document->getContents(); $rules = $contents[0]->getRules(); $urlRule = $rules[0]; $calcRule = $rules[1]; - self::assertTrue(\is_a($urlRule->getValue(), '\\Sabberworm\\CSS\\Value\\URL')); - self::assertTrue(\is_a($calcRule->getValue(), '\\Sabberworm\\CSS\\Value\\CalcFunction')); + self::assertInstanceOf(URL::class, $urlRule->getValue()); + self::assertInstanceOf(CalcFunction::class, $calcRule->getValue()); } } From 7c5de87638f5f1c51523eaf1d70c31f8045458d2 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 14 Feb 2025 03:33:14 +0100 Subject: [PATCH 180/555] [CLEANUP] Avoid Hungarian notation for `element` (#918) Part of #756 --- src/CSSList/Document.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/CSSList/Document.php b/src/CSSList/Document.php index 3022be38..45bb8f76 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -68,7 +68,7 @@ public function getAllRuleSets(): array /** * Returns all `Value` objects found recursively in `Rule`s in the tree. * - * @param CSSList|RuleSet|string $mElement + * @param CSSList|RuleSet|string $element * 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. @@ -77,18 +77,18 @@ public function getAllRuleSets(): array * * @see RuleSet->getRules() */ - public function getAllValues($mElement = null, $bSearchInFunctionArguments = false): array + public function getAllValues($element = null, $bSearchInFunctionArguments = false): array { $sSearchString = null; - if ($mElement === null) { - $mElement = $this; - } elseif (\is_string($mElement)) { - $sSearchString = $mElement; - $mElement = $this; + if ($element === null) { + $element = $this; + } elseif (\is_string($element)) { + $sSearchString = $element; + $element = $this; } /** @var array $aResult */ $aResult = []; - $this->allValues($mElement, $aResult, $sSearchString, $bSearchInFunctionArguments); + $this->allValues($element, $aResult, $sSearchString, $bSearchInFunctionArguments); return $aResult; } From 915b0946049a4b7693a327645de855253c509a57 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 14 Feb 2025 03:34:03 +0100 Subject: [PATCH 181/555] [CLEANUP] Avoid Hungarian notation for `component` (#917) Part of #756 --- src/CSSList/CSSBlockList.php | 4 ++-- src/Value/ValueList.php | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/CSSList/CSSBlockList.php b/src/CSSList/CSSBlockList.php index 3ccab5a1..bb5ff83a 100644 --- a/src/CSSList/CSSBlockList.php +++ b/src/CSSList/CSSBlockList.php @@ -80,8 +80,8 @@ protected function allValues( $this->allValues($element->getValue(), $result, $searchString, $searchInFunctionArguments); } elseif ($element instanceof ValueList) { if ($searchInFunctionArguments || !($element instanceof CSSFunction)) { - foreach ($element->getListComponents() as $mComponent) { - $this->allValues($mComponent, $result, $searchString, $searchInFunctionArguments); + foreach ($element->getListComponents() as $component) { + $this->allValues($component, $result, $searchString, $searchInFunctionArguments); } } } else { diff --git a/src/Value/ValueList.php b/src/Value/ValueList.php index c9739d1d..28c30368 100644 --- a/src/Value/ValueList.php +++ b/src/Value/ValueList.php @@ -44,11 +44,11 @@ public function __construct($aComponents = [], $sSeparator = ',', $lineNumber = } /** - * @param Value|string $mComponent + * @param Value|string $component */ - public function addListComponent($mComponent): void + public function addListComponent($component): void { - $this->aComponents[] = $mComponent; + $this->aComponents[] = $component; } /** From d7be02165976f3f79733287ab6accc835d5150d6 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 14 Feb 2025 03:35:11 +0100 Subject: [PATCH 182/555] [CLEANUP] Avoid Hungarian notation for `arguments` (#916) Part of #756 --- src/CSSList/AtRuleBlockList.php | 14 +++++++------- src/RuleSet/AtRuleSet.php | 18 +++++++++--------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/CSSList/AtRuleBlockList.php b/src/CSSList/AtRuleBlockList.php index db472c29..2fe8a98d 100644 --- a/src/CSSList/AtRuleBlockList.php +++ b/src/CSSList/AtRuleBlockList.php @@ -20,7 +20,7 @@ class AtRuleBlockList extends CSSBlockList implements AtRule /** * @var string */ - private $sArgs; + private $arguments; /** * @param string $type @@ -31,7 +31,7 @@ public function __construct($type, $arguments = '', $lineNumber = 0) { parent::__construct($lineNumber); $this->type = $type; - $this->sArgs = $arguments; + $this->arguments = $arguments; } /** @@ -47,7 +47,7 @@ public function atRuleName() */ public function atRuleArgs() { - return $this->sArgs; + return $this->arguments; } public function __toString(): string @@ -59,11 +59,11 @@ public function render(OutputFormat $outputFormat): string { $sResult = $outputFormat->comments($this); $sResult .= $outputFormat->sBeforeAtRuleBlock; - $sArgs = $this->sArgs; - if ($sArgs) { - $sArgs = ' ' . $sArgs; + $arguments = $this->arguments; + if ($arguments) { + $arguments = ' ' . $arguments; } - $sResult .= "@{$this->type}$sArgs{$outputFormat->spaceBeforeOpeningBrace()}{"; + $sResult .= "@{$this->type}$arguments{$outputFormat->spaceBeforeOpeningBrace()}{"; $sResult .= $this->renderListContents($outputFormat); $sResult .= '}'; $sResult .= $outputFormat->sAfterAtRuleBlock; diff --git a/src/RuleSet/AtRuleSet.php b/src/RuleSet/AtRuleSet.php index a56fe7c4..0891d6bd 100644 --- a/src/RuleSet/AtRuleSet.php +++ b/src/RuleSet/AtRuleSet.php @@ -23,18 +23,18 @@ class AtRuleSet extends RuleSet implements AtRule /** * @var string */ - private $sArgs; + private $arguments; /** * @param string $sType - * @param string $sArgs + * @param string $arguments * @param int<0, max> $lineNumber */ - public function __construct($sType, $sArgs = '', $lineNumber = 0) + public function __construct($sType, $arguments = '', $lineNumber = 0) { parent::__construct($lineNumber); $this->sType = $sType; - $this->sArgs = $sArgs; + $this->arguments = $arguments; } /** @@ -50,7 +50,7 @@ public function atRuleName() */ public function atRuleArgs() { - return $this->sArgs; + return $this->arguments; } public function __toString(): string @@ -61,11 +61,11 @@ public function __toString(): string public function render(OutputFormat $outputFormat): string { $sResult = $outputFormat->comments($this); - $sArgs = $this->sArgs; - if ($sArgs) { - $sArgs = ' ' . $sArgs; + $arguments = $this->arguments; + if ($arguments) { + $arguments = ' ' . $arguments; } - $sResult .= "@{$this->sType}$sArgs{$outputFormat->spaceBeforeOpeningBrace()}{"; + $sResult .= "@{$this->sType}$arguments{$outputFormat->spaceBeforeOpeningBrace()}{"; $sResult .= $this->renderRules($outputFormat); $sResult .= '}'; return $sResult; From 2d1a7723c3088c44aff0d43bde4775fca445c9f9 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 14 Feb 2025 19:04:12 +0100 Subject: [PATCH 183/555] [TASK] Use native type declarations for the exception classes (#922) Also improve the type annotations. Also fix the callers to get the tests green with the native types. Part of #811 --- CHANGELOG.md | 2 +- src/Parsing/OutputException.php | 3 +-- src/Parsing/ParserState.php | 11 ++++++++--- src/Parsing/SourceException.php | 5 ++--- src/Parsing/UnexpectedTokenException.php | 10 +++------- 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de02deb3..721ad8d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ Please also have a look at our - Only allow `string` for some `OutputFormat` properties (#885) - Make all non-private properties `@internal` (#886) - Use more native type declarations and strict mode - (#641, #772, #774, #778, #804, #841, #873, #875, #891) + (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/src/Parsing/OutputException.php b/src/Parsing/OutputException.php index 63b32207..f715080d 100644 --- a/src/Parsing/OutputException.php +++ b/src/Parsing/OutputException.php @@ -10,10 +10,9 @@ final class OutputException extends SourceException { /** - * @param string $sMessage * @param int<0, max> $lineNumber */ - public function __construct($sMessage, $lineNumber = 0) + public function __construct(string $sMessage, int $lineNumber = 0) { parent::__construct($sMessage, $lineNumber); } diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 651c6d64..f4263a99 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -146,7 +146,7 @@ public function parseIdentifier($bIgnoreCase = true) } $sResult = $this->parseCharacter(true); if ($sResult === null) { - throw new UnexpectedTokenException($sResult, $this->peek(5), 'identifier', $this->lineNumber); + throw new UnexpectedTokenException('', $this->peek(5), 'identifier', $this->lineNumber); } $sCharacter = null; while (!$this->isEnd() && ($sCharacter = $this->parseCharacter(true)) !== null) { @@ -294,14 +294,19 @@ public function consume($mValue = 1): string $iLineCount = \substr_count($mValue, "\n"); $iLength = $this->strlen($mValue); if (!$this->streql($this->substr($this->iCurrentPosition, $iLength), $mValue)) { - throw new UnexpectedTokenException($mValue, $this->peek(\max($iLength, 5)), $this->lineNumber); + throw new UnexpectedTokenException( + $mValue, + $this->peek(\max($iLength, 5)), + 'literal', + $this->lineNumber + ); } $this->lineNumber += $iLineCount; $this->iCurrentPosition += $this->strlen($mValue); return $mValue; } else { if ($this->iCurrentPosition + $mValue > $this->iLength) { - throw new UnexpectedEOFException($mValue, $this->peek(5), 'count', $this->lineNumber); + throw new UnexpectedEOFException((string) $mValue, $this->peek(5), 'count', $this->lineNumber); } $sResult = $this->substr($this->iCurrentPosition, $mValue); $iLineCount = \substr_count($sResult, "\n"); diff --git a/src/Parsing/SourceException.php b/src/Parsing/SourceException.php index 1af7affc..31fc0a59 100644 --- a/src/Parsing/SourceException.php +++ b/src/Parsing/SourceException.php @@ -7,15 +7,14 @@ class SourceException extends \Exception { /** - * @var int + * @var int<0, max> */ private $lineNumber; /** - * @param string $sMessage * @param int<0, max> $lineNumber */ - public function __construct($sMessage, $lineNumber = 0) + public function __construct(string $sMessage, int $lineNumber = 0) { $this->lineNumber = $lineNumber; if ($lineNumber !== 0) { diff --git a/src/Parsing/UnexpectedTokenException.php b/src/Parsing/UnexpectedTokenException.php index 441397f1..95fd3d6d 100644 --- a/src/Parsing/UnexpectedTokenException.php +++ b/src/Parsing/UnexpectedTokenException.php @@ -20,19 +20,15 @@ class UnexpectedTokenException extends SourceException private $sFound; /** - * Possible values: literal, identifier, count, expression, search - * - * @var string + * @var 'literal'|'identifier'|'count'|'expression'|'search'|'custom' */ private $sMatchType; /** - * @param string $sExpected - * @param string $sFound - * @param string $sMatchType + * @param 'literal'|'identifier'|'count'|'expression'|'search'|'custom' $sMatchType * @param int<0, max> $lineNumber */ - public function __construct($sExpected, $sFound, $sMatchType = 'literal', $lineNumber = 0) + public function __construct(string $sExpected, string $sFound, string $sMatchType = 'literal', int $lineNumber = 0) { $this->sExpected = $sExpected; $this->sFound = $sFound; From fa5ff8aff69d9845069dab117496007fdcc33146 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 14 Feb 2025 19:08:37 +0100 Subject: [PATCH 184/555] [CLEANUP] Avoid Hungarian notation for `result` (#924) Part of #756 Co-authored-by: JakeQZ --- src/CSSList/AtRuleBlockList.php | 14 +++++++------- src/CSSList/Document.php | 32 ++++++++++++++++---------------- src/CSSList/KeyFrame.php | 10 +++++----- src/OutputFormatter.php | 18 +++++++++--------- src/Parsing/ParserState.php | 30 +++++++++++++++--------------- src/Property/CSSNamespace.php | 6 +++--- src/Property/Import.php | 6 +++--- src/Rule/Rule.php | 14 +++++++------- src/RuleSet/AtRuleSet.php | 10 +++++----- src/RuleSet/DeclarationBlock.php | 18 +++++++++--------- src/RuleSet/RuleSet.php | 30 +++++++++++++++--------------- src/Value/CSSString.php | 8 ++++---- src/Value/Value.php | 18 +++++++++--------- 13 files changed, 107 insertions(+), 107 deletions(-) diff --git a/src/CSSList/AtRuleBlockList.php b/src/CSSList/AtRuleBlockList.php index 2fe8a98d..3266b828 100644 --- a/src/CSSList/AtRuleBlockList.php +++ b/src/CSSList/AtRuleBlockList.php @@ -57,17 +57,17 @@ public function __toString(): string public function render(OutputFormat $outputFormat): string { - $sResult = $outputFormat->comments($this); - $sResult .= $outputFormat->sBeforeAtRuleBlock; + $result = $outputFormat->comments($this); + $result .= $outputFormat->sBeforeAtRuleBlock; $arguments = $this->arguments; if ($arguments) { $arguments = ' ' . $arguments; } - $sResult .= "@{$this->type}$arguments{$outputFormat->spaceBeforeOpeningBrace()}{"; - $sResult .= $this->renderListContents($outputFormat); - $sResult .= '}'; - $sResult .= $outputFormat->sAfterAtRuleBlock; - return $sResult; + $result .= "@{$this->type}$arguments{$outputFormat->spaceBeforeOpeningBrace()}{"; + $result .= $this->renderListContents($outputFormat); + $result .= '}'; + $result .= $outputFormat->sAfterAtRuleBlock; + return $result; } public function isRootList(): bool diff --git a/src/CSSList/Document.php b/src/CSSList/Document.php index 45bb8f76..5f073072 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -46,10 +46,10 @@ public static function parse(ParserState $parserState): Document */ public function getAllDeclarationBlocks(): array { - /** @var array $aResult */ - $aResult = []; - $this->allDeclarationBlocks($aResult); - return $aResult; + /** @var array $result */ + $result = []; + $this->allDeclarationBlocks($result); + return $result; } /** @@ -59,10 +59,10 @@ public function getAllDeclarationBlocks(): array */ public function getAllRuleSets(): array { - /** @var array $aResult */ - $aResult = []; - $this->allRuleSets($aResult); - return $aResult; + /** @var array $result */ + $result = []; + $this->allRuleSets($result); + return $result; } /** @@ -86,10 +86,10 @@ public function getAllValues($element = null, $bSearchInFunctionArguments = fals $sSearchString = $element; $element = $this; } - /** @var array $aResult */ - $aResult = []; - $this->allValues($element, $aResult, $sSearchString, $bSearchInFunctionArguments); - return $aResult; + /** @var array $result */ + $result = []; + $this->allValues($element, $result, $sSearchString, $bSearchInFunctionArguments); + return $result; } /** @@ -107,10 +107,10 @@ public function getAllValues($element = null, $bSearchInFunctionArguments = fals */ public function getSelectorsBySpecificity($sSpecificitySearch = null): array { - /** @var array $aResult */ - $aResult = []; - $this->allSelectors($aResult, $sSpecificitySearch); - return $aResult; + /** @var array $result */ + $result = []; + $this->allSelectors($result, $sSpecificitySearch); + return $result; } /** diff --git a/src/CSSList/KeyFrame.php b/src/CSSList/KeyFrame.php index 04a7e760..4ab558c6 100644 --- a/src/CSSList/KeyFrame.php +++ b/src/CSSList/KeyFrame.php @@ -68,11 +68,11 @@ public function __toString(): string public function render(OutputFormat $outputFormat): string { - $sResult = $outputFormat->comments($this); - $sResult .= "@{$this->vendorKeyFrame} {$this->animationName}{$outputFormat->spaceBeforeOpeningBrace()}{"; - $sResult .= $this->renderListContents($outputFormat); - $sResult .= '}'; - return $sResult; + $result = $outputFormat->comments($this); + $result .= "@{$this->vendorKeyFrame} {$this->animationName}{$outputFormat->spaceBeforeOpeningBrace()}{"; + $result .= $this->renderListContents($outputFormat); + $result .= '}'; + return $result; } public function isRootList(): bool diff --git a/src/OutputFormatter.php b/src/OutputFormatter.php index 6740d822..48db2e0b 100644 --- a/src/OutputFormatter.php +++ b/src/OutputFormatter.php @@ -141,7 +141,7 @@ public function safely($cCode) */ public function implode(string $sSeparator, array $aValues, $bIncreaseLevel = false): string { - $sResult = ''; + $result = ''; $oFormat = $this->oFormat; if ($bIncreaseLevel) { $oFormat = $oFormat->nextLevel(); @@ -151,15 +151,15 @@ public function implode(string $sSeparator, array $aValues, $bIncreaseLevel = fa if ($bIsFirst) { $bIsFirst = false; } else { - $sResult .= $sSeparator; + $result .= $sSeparator; } if ($mValue instanceof Renderable) { - $sResult .= $mValue->render($oFormat); + $result .= $mValue->render($oFormat); } else { - $sResult .= $mValue; + $result .= $mValue; } } - return $sResult; + return $result; } /** @@ -188,15 +188,15 @@ public function comments(Commentable $oCommentable): string return ''; } - $sResult = ''; + $result = ''; $comments = $oCommentable->getComments(); $iLastCommentIndex = \count($comments) - 1; foreach ($comments as $i => $oComment) { - $sResult .= $oComment->render($this->oFormat); - $sResult .= $i === $iLastCommentIndex ? $this->spaceAfterBlocks() : $this->spaceBetweenBlocks(); + $result .= $oComment->render($this->oFormat); + $result .= $i === $iLastCommentIndex ? $this->spaceAfterBlocks() : $this->spaceBetweenBlocks(); } - return $sResult; + return $result; } /** diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index f4263a99..4f98c0ed 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -144,22 +144,22 @@ public function parseIdentifier($bIgnoreCase = true) if ($this->isEnd()) { throw new UnexpectedEOFException('', '', 'identifier', $this->lineNumber); } - $sResult = $this->parseCharacter(true); - if ($sResult === null) { + $result = $this->parseCharacter(true); + if ($result === null) { throw new UnexpectedTokenException('', $this->peek(5), 'identifier', $this->lineNumber); } $sCharacter = null; while (!$this->isEnd() && ($sCharacter = $this->parseCharacter(true)) !== null) { if (\preg_match('/[a-zA-Z0-9\\x{00A0}-\\x{FFFF}_-]/Sux', $sCharacter)) { - $sResult .= $sCharacter; + $result .= $sCharacter; } else { - $sResult .= '\\' . $sCharacter; + $result .= '\\' . $sCharacter; } } if ($bIgnoreCase) { - $sResult = $this->strtolower($sResult); + $result = $this->strtolower($result); } - return $sResult; + return $result; } /** @@ -308,11 +308,11 @@ public function consume($mValue = 1): string if ($this->iCurrentPosition + $mValue > $this->iLength) { throw new UnexpectedEOFException((string) $mValue, $this->peek(5), 'count', $this->lineNumber); } - $sResult = $this->substr($this->iCurrentPosition, $mValue); - $iLineCount = \substr_count($sResult, "\n"); + $result = $this->substr($this->iCurrentPosition, $mValue); + $iLineCount = \substr_count($result, "\n"); $this->lineNumber += $iLineCount; $this->iCurrentPosition += $mValue; - return $sResult; + return $result; } } @@ -460,13 +460,13 @@ private function substr($iStart, $iLength): string if ($iStart + $iLength > $this->iLength) { $iLength = $this->iLength - $iStart; } - $sResult = ''; + $result = ''; while ($iLength > 0) { - $sResult .= $this->aText[$iStart]; + $result .= $this->aText[$iStart]; $iStart++; $iLength--; } - return $sResult; + return $result; } /** @@ -493,11 +493,11 @@ private function strsplit($sString) return \preg_split('//u', $sString, -1, PREG_SPLIT_NO_EMPTY); } else { $iLength = \mb_strlen($sString, $this->sCharset); - $aResult = []; + $result = []; for ($i = 0; $i < $iLength; ++$i) { - $aResult[] = \mb_substr($sString, $i, 1, $this->sCharset); + $result[] = \mb_substr($sString, $i, 1, $this->sCharset); } - return $aResult; + return $result; } } else { if ($sString === '') { diff --git a/src/Property/CSSNamespace.php b/src/Property/CSSNamespace.php index 2563e13a..a646bcdd 100644 --- a/src/Property/CSSNamespace.php +++ b/src/Property/CSSNamespace.php @@ -111,11 +111,11 @@ public function atRuleName(): string */ public function atRuleArgs(): array { - $aResult = [$this->mUrl]; + $result = [$this->mUrl]; if ($this->sPrefix) { - \array_unshift($aResult, $this->sPrefix); + \array_unshift($result, $this->sPrefix); } - return $aResult; + return $result; } /** diff --git a/src/Property/Import.php b/src/Property/Import.php index e852eed6..62dcb2b4 100644 --- a/src/Property/Import.php +++ b/src/Property/Import.php @@ -94,11 +94,11 @@ public function atRuleName(): string */ public function atRuleArgs(): array { - $aResult = [$this->location]; + $result = [$this->location]; if ($this->mediaQuery) { - \array_push($aResult, $this->mediaQuery); + \array_push($result, $this->mediaQuery); } - return $aResult; + return $result; } /** diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index 872e7162..014a5030 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -271,20 +271,20 @@ public function __toString(): string public function render(OutputFormat $outputFormat): string { - $sResult = "{$outputFormat->comments($this)}{$this->sRule}:{$outputFormat->spaceAfterRuleName()}"; + $result = "{$outputFormat->comments($this)}{$this->sRule}:{$outputFormat->spaceAfterRuleName()}"; if ($this->mValue instanceof Value) { // Can also be a ValueList - $sResult .= $this->mValue->render($outputFormat); + $result .= $this->mValue->render($outputFormat); } else { - $sResult .= $this->mValue; + $result .= $this->mValue; } if (!empty($this->aIeHack)) { - $sResult .= ' \\' . \implode('\\', $this->aIeHack); + $result .= ' \\' . \implode('\\', $this->aIeHack); } if ($this->bIsImportant) { - $sResult .= ' !important'; + $result .= ' !important'; } - $sResult .= ';'; - return $sResult; + $result .= ';'; + return $result; } /** diff --git a/src/RuleSet/AtRuleSet.php b/src/RuleSet/AtRuleSet.php index 0891d6bd..601198d6 100644 --- a/src/RuleSet/AtRuleSet.php +++ b/src/RuleSet/AtRuleSet.php @@ -60,14 +60,14 @@ public function __toString(): string public function render(OutputFormat $outputFormat): string { - $sResult = $outputFormat->comments($this); + $result = $outputFormat->comments($this); $arguments = $this->arguments; if ($arguments) { $arguments = ' ' . $arguments; } - $sResult .= "@{$this->sType}$arguments{$outputFormat->spaceBeforeOpeningBrace()}{"; - $sResult .= $this->renderRules($outputFormat); - $sResult .= '}'; - return $sResult; + $result .= "@{$this->sType}$arguments{$outputFormat->spaceBeforeOpeningBrace()}{"; + $result .= $this->renderRules($outputFormat); + $result .= '}'; + return $result; } } diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 79e0dd0c..11e1982d 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -163,21 +163,21 @@ public function __toString(): string */ public function render(OutputFormat $outputFormat): string { - $sResult = $outputFormat->comments($this); + $result = $outputFormat->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->lineNumber); } - $sResult .= $outputFormat->sBeforeDeclarationBlock; - $sResult .= $outputFormat->implode( + $result .= $outputFormat->sBeforeDeclarationBlock; + $result .= $outputFormat->implode( $outputFormat->spaceBeforeSelectorSeparator() . ',' . $outputFormat->spaceAfterSelectorSeparator(), $this->aSelectors ); - $sResult .= $outputFormat->sAfterDeclarationBlockSelectors; - $sResult .= $outputFormat->spaceBeforeOpeningBrace() . '{'; - $sResult .= $this->renderRules($outputFormat); - $sResult .= '}'; - $sResult .= $outputFormat->sAfterDeclarationBlock; - return $sResult; + $result .= $outputFormat->sAfterDeclarationBlockSelectors; + $result .= $outputFormat->spaceBeforeOpeningBrace() . '{'; + $result .= $this->renderRules($outputFormat); + $result .= '}'; + $result .= $outputFormat->sAfterDeclarationBlock; + return $result; } } diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 528f57cf..231f3365 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -153,8 +153,8 @@ public function getRules($mRule = null) if ($mRule instanceof Rule) { $mRule = $mRule->getRule(); } - /** @var array $aResult */ - $aResult = []; + /** @var array $result */ + $result = []; 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. @@ -165,16 +165,16 @@ public function getRules($mRule = null) && (\strpos($sName, $mRule) === 0 || $sName === \substr($mRule, 0, -1)) ) ) { - $aResult = \array_merge($aResult, $aRules); + $result = \array_merge($result, $aRules); } } - \usort($aResult, static function (Rule $first, Rule $second): int { + \usort($result, static function (Rule $first, Rule $second): int { if ($first->getLineNo() === $second->getLineNo()) { return $first->getColNo() - $second->getColNo(); } return $first->getLineNo() - $second->getLineNo(); }); - return $aResult; + return $result; } /** @@ -207,12 +207,12 @@ public function setRules(array $aRules): void */ public function getRulesAssoc($mRule = null) { - /** @var array $aResult */ - $aResult = []; + /** @var array $result */ + $result = []; foreach ($this->getRules($mRule) as $rule) { - $aResult[$rule->getRule()] = $rule; + $result[$rule->getRule()] = $rule; } - return $aResult; + return $result; } /** @@ -267,7 +267,7 @@ public function __toString(): string */ protected function renderRules(OutputFormat $outputFormat) { - $sResult = ''; + $result = ''; $bIsFirst = true; $oNextLevel = $outputFormat->nextLevel(); foreach ($this->aRules as $aRules) { @@ -280,20 +280,20 @@ protected function renderRules(OutputFormat $outputFormat) } if ($bIsFirst) { $bIsFirst = false; - $sResult .= $oNextLevel->spaceBeforeRules(); + $result .= $oNextLevel->spaceBeforeRules(); } else { - $sResult .= $oNextLevel->spaceBetweenRules(); + $result .= $oNextLevel->spaceBetweenRules(); } - $sResult .= $sRendered; + $result .= $sRendered; } } if (!$bIsFirst) { // Had some output - $sResult .= $outputFormat->spaceAfterRules(); + $result .= $outputFormat->spaceAfterRules(); } - return $outputFormat->removeLastSemicolon($sResult); + return $outputFormat->removeLastSemicolon($result); } /** diff --git a/src/Value/CSSString.php b/src/Value/CSSString.php index 6cc609ea..496b9468 100644 --- a/src/Value/CSSString.php +++ b/src/Value/CSSString.php @@ -51,12 +51,12 @@ public static function parse(ParserState $parserState): CSSString if ($sQuote !== null) { $parserState->consume($sQuote); } - $sResult = ''; + $result = ''; $sContent = null; if ($sQuote === null) { // Unquoted strings end in whitespace or with braces, brackets, parentheses while (\preg_match('/[\\s{}()<>\\[\\]]/isu', $parserState->peek()) !== 1) { - $sResult .= $parserState->parseCharacter(false); + $result .= $parserState->parseCharacter(false); } } else { while (!$parserState->comes($sQuote)) { @@ -67,11 +67,11 @@ public static function parse(ParserState $parserState): CSSString $parserState->currentLine() ); } - $sResult .= $sContent; + $result .= $sContent; } $parserState->consume($sQuote); } - return new CSSString($sResult, $parserState->currentLine()); + return new CSSString($result, $parserState->currentLine()); } /** diff --git a/src/Value/Value.php b/src/Value/Value.php index 4410551a..b6973438 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -122,24 +122,24 @@ public static function parseValue(ParserState $parserState, array $aListDelimite public static function parseIdentifierOrFunction(ParserState $parserState, $bIgnoreCase = false) { $oAnchor = $parserState->anchor(); - $mResult = $parserState->parseIdentifier($bIgnoreCase); + $result = $parserState->parseIdentifier($bIgnoreCase); if ($parserState->comes('(')) { $oAnchor->backtrack(); - if ($parserState->streql('url', $mResult)) { - $mResult = URL::parse($parserState); + if ($parserState->streql('url', $result)) { + $result = URL::parse($parserState); } elseif ( - $parserState->streql('calc', $mResult) - || $parserState->streql('-webkit-calc', $mResult) - || $parserState->streql('-moz-calc', $mResult) + $parserState->streql('calc', $result) + || $parserState->streql('-webkit-calc', $result) + || $parserState->streql('-moz-calc', $result) ) { - $mResult = CalcFunction::parse($parserState); + $result = CalcFunction::parse($parserState); } else { - $mResult = CSSFunction::parse($parserState, $bIgnoreCase); + $result = CSSFunction::parse($parserState, $bIgnoreCase); } } - return $mResult; + return $result; } /** From fa66e430095671297a096e0e70a321bedd6296bb Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 15 Feb 2025 02:52:29 +0100 Subject: [PATCH 185/555] [CLEANUP] Avoid Hungarian notation for `prefix` (#926) Part of #756 --- src/CSSList/CSSList.php | 10 +++++----- src/OutputFormat.php | 8 ++++---- src/Property/CSSNamespace.php | 22 +++++++++++----------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 7f21893b..5e7cf294 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -194,15 +194,15 @@ private static function parseAtRule(ParserState $parserState) } return $result; } elseif ($identifier === 'namespace') { - $sPrefix = null; + $prefix = null; $url = Value::parsePrimitiveValue($parserState); if (!$parserState->comes(';')) { - $sPrefix = $url; + $prefix = $url; $url = Value::parsePrimitiveValue($parserState); } $parserState->consumeUntil([';', ParserState::EOF], true, true); - if ($sPrefix !== null && !\is_string($sPrefix)) { - throw new UnexpectedTokenException('Wrong namespace prefix', $sPrefix, 'custom', $identifierLineNumber); + if ($prefix !== null && !\is_string($prefix)) { + throw new UnexpectedTokenException('Wrong namespace prefix', $prefix, 'custom', $identifierLineNumber); } if (!($url instanceof CSSString || $url instanceof URL)) { throw new UnexpectedTokenException( @@ -212,7 +212,7 @@ private static function parseAtRule(ParserState $parserState) $identifierLineNumber ); } - return new CSSNamespace($url, $sPrefix, $identifierLineNumber); + return new CSSNamespace($url, $prefix, $identifierLineNumber); } else { // Unknown other at rule (font-face or such) $arguments = \trim($parserState->consumeUntil('{', false, true)); diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 52033bf4..1068fa1f 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -237,8 +237,8 @@ public function __construct() {} public function get(string $sName) { $aVarPrefixes = ['a', 's', 'm', 'b', 'f', 'o', 'c', 'i']; - foreach ($aVarPrefixes as $sPrefix) { - $sFieldName = $sPrefix . \ucfirst($sName); + foreach ($aVarPrefixes as $prefix) { + $sFieldName = $prefix . \ucfirst($sName); if (isset($this->$sFieldName)) { return $this->$sFieldName; } @@ -265,10 +265,10 @@ public function set($aNames, $mValue) } elseif (!\is_array($aNames)) { $aNames = [$aNames]; } - foreach ($aVarPrefixes as $sPrefix) { + foreach ($aVarPrefixes as $prefix) { $bDidReplace = false; foreach ($aNames as $sName) { - $sFieldName = $sPrefix . \ucfirst($sName); + $sFieldName = $prefix . \ucfirst($sName); if (isset($this->$sFieldName)) { $this->$sFieldName = $mValue; $bDidReplace = true; diff --git a/src/Property/CSSNamespace.php b/src/Property/CSSNamespace.php index a646bcdd..eed63782 100644 --- a/src/Property/CSSNamespace.php +++ b/src/Property/CSSNamespace.php @@ -20,7 +20,7 @@ class CSSNamespace implements AtRule /** * @var string */ - private $sPrefix; + private $prefix; /** * @var int @@ -36,13 +36,13 @@ class CSSNamespace implements AtRule /** * @param string $mUrl - * @param string|null $sPrefix + * @param string|null $prefix * @param int<0, max> $lineNumber */ - public function __construct($mUrl, $sPrefix = null, $lineNumber = 0) + public function __construct($mUrl, $prefix = null, $lineNumber = 0) { $this->mUrl = $mUrl; - $this->sPrefix = $sPrefix; + $this->prefix = $prefix; $this->lineNumber = $lineNumber; $this->comments = []; } @@ -62,7 +62,7 @@ public function __toString(): string public function render(OutputFormat $outputFormat): string { - return '@namespace ' . ($this->sPrefix === null ? '' : $this->sPrefix . ' ') + return '@namespace ' . ($this->prefix === null ? '' : $this->prefix . ' ') . $this->mUrl->render($outputFormat) . ';'; } @@ -79,7 +79,7 @@ public function getUrl() */ public function getPrefix() { - return $this->sPrefix; + return $this->prefix; } /** @@ -91,11 +91,11 @@ public function setUrl($mUrl): void } /** - * @param string $sPrefix + * @param string $prefix */ - public function setPrefix($sPrefix): void + public function setPrefix($prefix): void { - $this->sPrefix = $sPrefix; + $this->prefix = $prefix; } /** @@ -112,8 +112,8 @@ public function atRuleName(): string public function atRuleArgs(): array { $result = [$this->mUrl]; - if ($this->sPrefix) { - \array_unshift($result, $this->sPrefix); + if ($this->prefix) { + \array_unshift($result, $this->prefix); } return $result; } From 8fcaa0944bb43560e40b4e385d5d01231a8c8c97 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 15 Feb 2025 02:55:22 +0100 Subject: [PATCH 186/555] [TASK] Move some `AtRulBlockList` tests to unit tests (#927) Part of #758 --- tests/CSSList/AtRuleBlockListTest.php | 33 ---------------- tests/Unit/CSSList/AtRuleBlockListTest.php | 46 ++++++++++++++++++++++ 2 files changed, 46 insertions(+), 33 deletions(-) create mode 100644 tests/Unit/CSSList/AtRuleBlockListTest.php diff --git a/tests/CSSList/AtRuleBlockListTest.php b/tests/CSSList/AtRuleBlockListTest.php index 73e6c38b..676b908a 100644 --- a/tests/CSSList/AtRuleBlockListTest.php +++ b/tests/CSSList/AtRuleBlockListTest.php @@ -5,10 +5,7 @@ namespace Sabberworm\CSS\Tests\CSSList; use PHPUnit\Framework\TestCase; -use Sabberworm\CSS\Comment\Commentable; -use Sabberworm\CSS\CSSList\AtRuleBlockList; use Sabberworm\CSS\Parser; -use Sabberworm\CSS\Renderable; use Sabberworm\CSS\Settings; /** @@ -51,36 +48,6 @@ public static function provideSyntacticallyCorrectAtRule(): array ]; } - /** - * @test - */ - public function implementsAtRule(): void - { - $subject = new AtRuleBlockList(''); - - self::assertInstanceOf(AtRuleBlockList::class, $subject); - } - - /** - * @test - */ - public function implementsRenderable(): void - { - $subject = new AtRuleBlockList(''); - - self::assertInstanceOf(Renderable::class, $subject); - } - - /** - * @test - */ - public function implementsCommentable(): void - { - $subject = new AtRuleBlockList(''); - - self::assertInstanceOf(Commentable::class, $subject); - } - /** * @test * diff --git a/tests/Unit/CSSList/AtRuleBlockListTest.php b/tests/Unit/CSSList/AtRuleBlockListTest.php new file mode 100644 index 00000000..b57e0525 --- /dev/null +++ b/tests/Unit/CSSList/AtRuleBlockListTest.php @@ -0,0 +1,46 @@ + Date: Sat, 15 Feb 2025 02:57:56 +0100 Subject: [PATCH 187/555] [TASK] Use native type declarations for `Comment` (#923) Part of #811 --- CHANGELOG.md | 2 +- src/Comment/Comment.php | 13 +++---------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 721ad8d7..90c6d35a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ Please also have a look at our - Only allow `string` for some `OutputFormat` properties (#885) - Make all non-private properties `@internal` (#886) - Use more native type declarations and strict mode - (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922) + (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/src/Comment/Comment.php b/src/Comment/Comment.php index 7d6de3d7..9d8b5ed3 100644 --- a/src/Comment/Comment.php +++ b/src/Comment/Comment.php @@ -24,19 +24,15 @@ class Comment implements Renderable protected $commentText; /** - * @param string $commentText * @param int<0, max> $lineNumber */ - public function __construct($commentText = '', $lineNumber = 0) + public function __construct(string $commentText = '', int $lineNumber = 0) { $this->commentText = $commentText; $this->lineNumber = $lineNumber; } - /** - * @return string - */ - public function getComment() + public function getComment(): string { return $this->commentText; } @@ -49,10 +45,7 @@ public function getLineNo(): int return $this->lineNumber; } - /** - * @param string $commentText - */ - public function setComment($commentText): void + public function setComment(string $commentText): void { $this->commentText = $commentText; } From 663992aab3231b530dde1b84c393a9aa853919c8 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 15 Feb 2025 12:00:15 +0100 Subject: [PATCH 188/555] [TASK] Use type-safe setters in `OutputFormat` factory methods (#930) Also add one more test for good measure. --- src/OutputFormat.php | 21 ++++++++++++++++----- tests/Unit/OutputFormatTest.php | 8 ++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 1068fa1f..98f394b0 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -820,12 +820,18 @@ public static function create(): self public static function createCompact(): self { $format = self::create(); - $format->set('Space*Rules', '') - ->set('Space*Blocks', '') + $format + ->setSpaceBeforeRules('') + ->setSpaceBetweenRules('') + ->setSpaceAfterRules('') + ->setSpaceBeforeBlocks('') + ->setSpaceBetweenBlocks('') + ->setSpaceAfterBlocks('') ->setSpaceAfterRuleName('') ->setSpaceBeforeOpeningBrace('') ->setSpaceAfterSelectorSeparator('') ->setRenderComments(false); + return $format; } @@ -835,11 +841,16 @@ public static function createCompact(): self public static function createPretty(): self { $format = self::create(); - $format->set('Space*Rules', "\n") - ->set('Space*Blocks', "\n") + $format + ->setSpaceBeforeRules("\n") + ->setSpaceBetweenRules("\n") + ->setSpaceAfterRules("\n") + ->setSpaceBeforeBlocks("\n") ->setSpaceBetweenBlocks("\n\n") - ->set('SpaceAfterListArgumentSeparators', [',' => ' ']) + ->setSpaceAfterBlocks("\n") + ->setSpaceAfterListArgumentSeparators([',' => ' ']) ->setRenderComments(true); + return $format; } } diff --git a/tests/Unit/OutputFormatTest.php b/tests/Unit/OutputFormatTest.php index 2b60b52c..8792892d 100644 --- a/tests/Unit/OutputFormatTest.php +++ b/tests/Unit/OutputFormatTest.php @@ -896,6 +896,14 @@ public function createReturnsOutputFormatInstance(): void self::assertInstanceOf(OutputFormat::class, OutputFormat::create()); } + /** + * @test + */ + public function createCreatesInstanceWithDefaultSettings(): void + { + self::assertEquals(new OutputFormat(), OutputFormat::create()); + } + /** * @test */ From 2ad6f009ac14d7aa0a19830b8e3ec6bb39679089 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 15 Feb 2025 12:06:52 +0100 Subject: [PATCH 189/555] [TASK] Move `DocumentTest` to unit tests (#931) Also fix some type annotation for the moved tests. Part of #758 --- tests/{ => Unit}/CSSList/DocumentTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename tests/{ => Unit}/CSSList/DocumentTest.php (95%) diff --git a/tests/CSSList/DocumentTest.php b/tests/Unit/CSSList/DocumentTest.php similarity index 95% rename from tests/CSSList/DocumentTest.php rename to tests/Unit/CSSList/DocumentTest.php index c7662e7b..58ef853d 100644 --- a/tests/CSSList/DocumentTest.php +++ b/tests/Unit/CSSList/DocumentTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Sabberworm\CSS\Tests\CSSList; +namespace Sabberworm\CSS\Tests\Unit\CSSList; use PHPUnit\Framework\TestCase; use Sabberworm\CSS\Comment\Commentable; @@ -50,7 +50,7 @@ public function getContentsInitiallyReturnsEmptyArray(): void } /** - * @return array>> + * @return array}> */ public static function contentsDataProvider(): array { @@ -64,7 +64,7 @@ public static function contentsDataProvider(): array /** * @test * - * @param array $contents + * @param list $contents * * @dataProvider contentsDataProvider */ From e1fa3b6783c68f1af7170fdad3288339fd9ee520 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 15 Feb 2025 12:33:17 +0100 Subject: [PATCH 190/555] [TASK] Use native types in `Settings` (#933) --- CHANGELOG.md | 2 +- src/Settings.php | 17 ++++------------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90c6d35a..44a3697a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ Please also have a look at our - Only allow `string` for some `OutputFormat` properties (#885) - Make all non-private properties `@internal` (#886) - Use more native type declarations and strict mode - (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923) + (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/src/Settings.php b/src/Settings.php index c684a690..1765aafa 100644 --- a/src/Settings.php +++ b/src/Settings.php @@ -46,10 +46,7 @@ private function __construct() $this->bMultibyteSupport = \extension_loaded('mbstring'); } - /** - * @return self new instance - */ - public static function create(): Settings + public static function create(): self { return new Settings(); } @@ -60,11 +57,9 @@ public static function create(): Settings * If `true` (`mbstring` extension must be enabled), will use (slower) `mb_strlen`, `mb_convert_case`, `mb_substr` * and `mb_strpos` functions. Otherwise, the normal (ASCII-Only) functions will be used. * - * @param bool $bMultibyteSupport - * * @return $this fluent interface */ - public function withMultibyteSupport($bMultibyteSupport = true): self + public function withMultibyteSupport(bool $bMultibyteSupport = true): self { $this->bMultibyteSupport = $bMultibyteSupport; return $this; @@ -73,11 +68,9 @@ public function withMultibyteSupport($bMultibyteSupport = true): self /** * Sets the charset to be used if the CSS does not contain an `@charset` declaration. * - * @param string $sDefaultCharset - * * @return $this fluent interface */ - public function withDefaultCharset($sDefaultCharset): self + public function withDefaultCharset(string $sDefaultCharset): self { $this->sDefaultCharset = $sDefaultCharset; return $this; @@ -86,11 +79,9 @@ public function withDefaultCharset($sDefaultCharset): self /** * Configures whether the parser should silently ignore invalid rules. * - * @param bool $usesLenientParsing - * * @return $this fluent interface */ - public function withLenientParsing($usesLenientParsing = true): self + public function withLenientParsing(bool $usesLenientParsing = true): self { $this->bLenientParsing = $usesLenientParsing; return $this; From 15f7a930f770f2958149732a310dd1b03363aa75 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 16 Feb 2025 03:04:42 +0100 Subject: [PATCH 191/555] [TASK] Drop redundant constructor code (#932) - use default values for properties instead of setting them in the constructor - drop constructors that are redundant to the constructor of the parent class --- src/CSSList/CSSBlockList.php | 8 -------- src/CSSList/CSSList.php | 6 ++---- src/CSSList/Document.php | 8 -------- src/CSSList/KeyFrame.php | 10 ---------- src/OutputFormat.php | 4 ++-- src/Parsing/ParserState.php | 3 +-- src/Property/CSSNamespace.php | 3 +-- src/Property/Charset.php | 3 +-- src/Property/Import.php | 3 +-- src/Rule/Rule.php | 10 +++------- src/RuleSet/DeclarationBlock.php | 11 +---------- src/RuleSet/RuleSet.php | 6 ++---- src/Value/PrimitiveValue.php | 11 +---------- 13 files changed, 15 insertions(+), 71 deletions(-) diff --git a/src/CSSList/CSSBlockList.php b/src/CSSList/CSSBlockList.php index bb5ff83a..76216279 100644 --- a/src/CSSList/CSSBlockList.php +++ b/src/CSSList/CSSBlockList.php @@ -20,14 +20,6 @@ */ abstract class CSSBlockList extends CSSList { - /** - * @param int<0, max> $lineNumber - */ - public function __construct($lineNumber = 0) - { - parent::__construct($lineNumber); - } - /** * @param array $result */ diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 5e7cf294..e7acc842 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -38,14 +38,14 @@ abstract class CSSList implements Renderable, Commentable * * @internal since 8.8.0 */ - protected $comments; + protected $comments = []; /** * @var array * * @internal since 8.8.0 */ - protected $contents; + protected $contents = []; /** * @var int<0, max> @@ -59,8 +59,6 @@ abstract class CSSList implements Renderable, Commentable */ public function __construct($lineNumber = 0) { - $this->comments = []; - $this->contents = []; $this->lineNumber = $lineNumber; } diff --git a/src/CSSList/Document.php b/src/CSSList/Document.php index 5f073072..87595caa 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -18,14 +18,6 @@ */ class Document extends CSSBlockList { - /** - * @param int<0, max> $lineNumber - */ - public function __construct($lineNumber = 0) - { - parent::__construct($lineNumber); - } - /** * @throws SourceException * diff --git a/src/CSSList/KeyFrame.php b/src/CSSList/KeyFrame.php index 4ab558c6..1fce204b 100644 --- a/src/CSSList/KeyFrame.php +++ b/src/CSSList/KeyFrame.php @@ -19,16 +19,6 @@ class KeyFrame extends CSSList implements AtRule */ private $animationName; - /** - * @param int<0, max> $lineNumber - */ - public function __construct($lineNumber = 0) - { - parent::__construct($lineNumber); - $this->vendorKeyFrame = null; - $this->animationName = null; - } - /** * @param string $vendorKeyFrame */ diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 98f394b0..7f3786be 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -217,12 +217,12 @@ class OutputFormat /** * @var OutputFormatter|null */ - private $oFormatter = null; + private $oFormatter; /** * @var OutputFormat|null */ - private $oNextLevelFormat = null; + private $oNextLevelFormat; /** * @var int diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 4f98c0ed..10187617 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -37,7 +37,7 @@ class ParserState /** * @var int */ - private $iCurrentPosition; + private $iCurrentPosition = 0; /** * will only be used if the CSS does not contain an `@charset` declaration @@ -64,7 +64,6 @@ public function __construct($sText, Settings $oParserSettings, $lineNumber = 1) { $this->oParserSettings = $oParserSettings; $this->sText = $sText; - $this->iCurrentPosition = 0; $this->lineNumber = $lineNumber; $this->setCharset($this->oParserSettings->sDefaultCharset); } diff --git a/src/Property/CSSNamespace.php b/src/Property/CSSNamespace.php index eed63782..81f24e34 100644 --- a/src/Property/CSSNamespace.php +++ b/src/Property/CSSNamespace.php @@ -32,7 +32,7 @@ class CSSNamespace implements AtRule * * @internal since 8.8.0 */ - protected $comments; + protected $comments = []; /** * @param string $mUrl @@ -44,7 +44,6 @@ public function __construct($mUrl, $prefix = null, $lineNumber = 0) $this->mUrl = $mUrl; $this->prefix = $prefix; $this->lineNumber = $lineNumber; - $this->comments = []; } /** diff --git a/src/Property/Charset.php b/src/Property/Charset.php index 387f92d1..9addf369 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -35,7 +35,7 @@ class Charset implements AtRule * * @internal since 8.8.0 */ - protected $comments; + protected $comments = []; /** * @param int<0, max> $lineNumber @@ -44,7 +44,6 @@ public function __construct(CSSString $oCharset, $lineNumber = 0) { $this->oCharset = $oCharset; $this->lineNumber = $lineNumber; - $this->comments = []; } /** diff --git a/src/Property/Import.php b/src/Property/Import.php index 62dcb2b4..bc97d949 100644 --- a/src/Property/Import.php +++ b/src/Property/Import.php @@ -35,7 +35,7 @@ class Import implements AtRule * * @internal since 8.8.0 */ - protected $comments; + protected $comments = []; /** * @param string $mediaQuery @@ -46,7 +46,6 @@ public function __construct(URL $location, $mediaQuery, $lineNumber = 0) $this->location = $location; $this->mediaQuery = $mediaQuery; $this->lineNumber = $lineNumber; - $this->comments = []; } /** diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index 014a5030..a2cbbb86 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -34,12 +34,12 @@ class Rule implements Renderable, Commentable /** * @var bool */ - private $bIsImportant; + private $bIsImportant = false; /** * @var array */ - private $aIeHack; + private $aIeHack = []; /** * @var int @@ -58,7 +58,7 @@ class Rule implements Renderable, Commentable * * @internal since 8.8.0 */ - protected $comments; + protected $comments = []; /** * @param string $sRule @@ -68,12 +68,8 @@ class Rule implements Renderable, Commentable public function __construct($sRule, $lineNumber = 0, $iColNo = 0) { $this->sRule = $sRule; - $this->mValue = null; - $this->bIsImportant = false; - $this->aIeHack = []; $this->lineNumber = $lineNumber; $this->iColNo = $iColNo; - $this->comments = []; } /** diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 11e1982d..a39eaaa8 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -27,16 +27,7 @@ class DeclarationBlock extends RuleSet /** * @var array */ - private $aSelectors; - - /** - * @param int<0, max> $lineNumber - */ - public function __construct($lineNumber = 0) - { - parent::__construct($lineNumber); - $this->aSelectors = []; - } + private $aSelectors = []; /** * @param CSSList|null $list diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 231f3365..bb40bd23 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -27,7 +27,7 @@ abstract class RuleSet implements Renderable, Commentable /** * @var array */ - private $aRules; + private $aRules = []; /** * @var int<0, max> @@ -41,16 +41,14 @@ abstract class RuleSet implements Renderable, Commentable * * @internal since 8.8.0 */ - protected $comments; + protected $comments = []; /** * @param int<0, max> $lineNumber */ public function __construct($lineNumber = 0) { - $this->aRules = []; $this->lineNumber = $lineNumber; - $this->comments = []; } /** diff --git a/src/Value/PrimitiveValue.php b/src/Value/PrimitiveValue.php index 42728c73..f7f94092 100644 --- a/src/Value/PrimitiveValue.php +++ b/src/Value/PrimitiveValue.php @@ -4,13 +4,4 @@ namespace Sabberworm\CSS\Value; -abstract class PrimitiveValue extends Value -{ - /** - * @param int<0, max> $lineNumber - */ - public function __construct($lineNumber = 0) - { - parent::__construct($lineNumber); - } -} +abstract class PrimitiveValue extends Value {} From 8f8fe75ac2a6ef28237bd7f655db14e5d27e2b20 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 16 Feb 2025 03:07:33 +0100 Subject: [PATCH 192/555] [CLEANUP] Avoid Hungarian notation in `Document` (#929) Part of #756 --- src/CSSList/Document.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/CSSList/Document.php b/src/CSSList/Document.php index 87595caa..c79ae9a2 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -63,24 +63,24 @@ public function getAllRuleSets(): array * @param CSSList|RuleSet|string $element * 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. + * @param bool $searchInFunctionArguments whether to also return Value objects used as Function arguments. * * @return array * * @see RuleSet->getRules() */ - public function getAllValues($element = null, $bSearchInFunctionArguments = false): array + public function getAllValues($element = null, $searchInFunctionArguments = false): array { - $sSearchString = null; + $searchString = null; if ($element === null) { $element = $this; } elseif (\is_string($element)) { - $sSearchString = $element; + $searchString = $element; $element = $this; } /** @var array $result */ $result = []; - $this->allValues($element, $result, $sSearchString, $bSearchInFunctionArguments); + $this->allValues($element, $result, $searchString, $searchInFunctionArguments); return $result; } @@ -90,18 +90,18 @@ public function getAllValues($element = null, $bSearchInFunctionArguments = fals * Note that this does not yield the full `DeclarationBlock` that the selector belongs to * (and, currently, there is no way to get to that). * - * @param string|null $sSpecificitySearch + * @param string|null $specificitySearch * An optional filter by specificity. * May contain a comparison operator and a number or just a number (defaults to "=="). * * @return array * @example `getSelectorsBySpecificity('>= 100')` */ - public function getSelectorsBySpecificity($sSpecificitySearch = null): array + public function getSelectorsBySpecificity($specificitySearch = null): array { /** @var array $result */ $result = []; - $this->allSelectors($result, $sSpecificitySearch); + $this->allSelectors($result, $specificitySearch); return $result; } From 6775b97add18c785daecb299a18d86211be38ba8 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 16 Feb 2025 03:11:35 +0100 Subject: [PATCH 193/555] [TASK] Drop redundant `OutputException` constructor (#925) --- src/Parsing/OutputException.php | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/Parsing/OutputException.php b/src/Parsing/OutputException.php index f715080d..0a20dc96 100644 --- a/src/Parsing/OutputException.php +++ b/src/Parsing/OutputException.php @@ -7,13 +7,4 @@ /** * Thrown if the CSS parser attempts to print something invalid. */ -final class OutputException extends SourceException -{ - /** - * @param int<0, max> $lineNumber - */ - public function __construct(string $sMessage, int $lineNumber = 0) - { - parent::__construct($sMessage, $lineNumber); - } -} +final class OutputException extends SourceException {} From 96828776b2a3881858ff9d978d511aa47ab953e1 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 16 Feb 2025 10:36:39 +0100 Subject: [PATCH 194/555] [TASK] Add some more unit tests for `AtRuleBlockList` (#934) Part of #757 --- tests/Unit/CSSList/AtRuleBlockListTest.php | 68 ++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/tests/Unit/CSSList/AtRuleBlockListTest.php b/tests/Unit/CSSList/AtRuleBlockListTest.php index b57e0525..30a7f305 100644 --- a/tests/Unit/CSSList/AtRuleBlockListTest.php +++ b/tests/Unit/CSSList/AtRuleBlockListTest.php @@ -11,6 +11,8 @@ /** * @covers \Sabberworm\CSS\CSSList\AtRuleBlockList + * @covers \Sabberworm\CSS\CSSList\CSSBlockList + * @covers \Sabberworm\CSS\CSSList\CSSList */ final class AtRuleBlockListTest extends TestCase { @@ -43,4 +45,70 @@ public function implementsCommentable(): void self::assertInstanceOf(Commentable::class, $subject); } + + /** + * @test + */ + public function atRuleNameReturnsTypeProvidedToConstructor(): void + { + $type = 'foo'; + + $subject = new AtRuleBlockList($type); + + self::assertSame($type, $subject->atRuleName()); + } + + /** + * @test + */ + public function getLineNoByDefaultReturnsZero(): void + { + $subject = new AtRuleBlockList(''); + + self::assertSame(0, $subject->getLineNo()); + } + + /** + * @test + */ + public function atRuleArgsByDefaultReturnsEmptyString(): void + { + $subject = new AtRuleBlockList(''); + + self::assertSame('', $subject->atRuleArgs()); + } + + /** + * @test + */ + public function atRuleArgsReturnsArgumentsProvidedToConstructor(): void + { + $arguments = 'bar'; + + $subject = new AtRuleBlockList('', $arguments); + + self::assertSame($arguments, $subject->atRuleArgs()); + } + + /** + * @test + */ + public function getLineNoReturnsLineNumberProvidedToConstructor(): void + { + $lineNumber = 42; + + $subject = new AtRuleBlockList('', '', $lineNumber); + + self::assertSame($lineNumber, $subject->getLineNo()); + } + + /** + * @test + */ + public function isRootListAlwaysReturnsFalse(): void + { + $subject = new AtRuleBlockList(''); + + self::assertFalse($subject->isRootList()); + } } From 3e9bc30fb64b22109fd76a326a7b92c2c14d1b31 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 16 Feb 2025 10:39:05 +0100 Subject: [PATCH 195/555] [CLEANUP] Avoid Hungarian notation for `position` (#935) Part of #756 --- src/Parsing/Anchor.php | 10 +++++----- src/Parsing/ParserState.php | 6 +++--- src/RuleSet/RuleSet.php | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Parsing/Anchor.php b/src/Parsing/Anchor.php index 039f783e..e7cf9244 100644 --- a/src/Parsing/Anchor.php +++ b/src/Parsing/Anchor.php @@ -12,7 +12,7 @@ class Anchor /** * @var int */ - private $iPosition; + private $position; /** * @var ParserState @@ -20,16 +20,16 @@ class Anchor private $parserState; /** - * @param int $iPosition + * @param int $position */ - public function __construct($iPosition, ParserState $parserState) + public function __construct($position, ParserState $parserState) { - $this->iPosition = $iPosition; + $this->position = $position; $this->parserState = $parserState; } public function backtrack(): void { - $this->parserState->setPosition($this->iPosition); + $this->parserState->setPosition($this->position); } } diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 10187617..181bd88e 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -122,11 +122,11 @@ public function anchor(): Anchor } /** - * @param int $iPosition + * @param int $position */ - public function setPosition($iPosition): void + public function setPosition($position): void { - $this->iCurrentPosition = $iPosition; + $this->iCurrentPosition = $position; } /** diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index bb40bd23..8e9cf1aa 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -108,12 +108,12 @@ public function addRule(Rule $rule, ?Rule $oSibling = null): void $this->aRules[$sRule] = []; } - $iPosition = \count($this->aRules[$sRule]); + $position = \count($this->aRules[$sRule]); if ($oSibling !== null) { $iSiblingPos = \array_search($oSibling, $this->aRules[$sRule], true); if ($iSiblingPos !== false) { - $iPosition = $iSiblingPos; + $position = $iSiblingPos; $rule->setPosition($oSibling->getLineNo(), $oSibling->getColNo() - 1); } } @@ -127,7 +127,7 @@ public function addRule(Rule $rule, ?Rule $oSibling = null): void } } - \array_splice($this->aRules[$sRule], $iPosition, 0, [$rule]); + \array_splice($this->aRules[$sRule], $position, 0, [$rule]); } /** From 1acd24d47a9b706bea3d131ac3178a4837a6e234 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 16 Feb 2025 11:40:41 +0100 Subject: [PATCH 196/555] [CLEANUP] Avoid Hungarian notation for `parserSettings` (#938) Part of #756 --- src/Parser.php | 8 ++++---- src/Parsing/ParserState.php | 22 +++++++++++----------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Parser.php b/src/Parser.php index faaeb87d..7f3a9229 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -22,12 +22,12 @@ class Parser * @param string $sText the complete CSS as text (i.e., usually the contents of a CSS file) * @param int<0, max> $lineNumber the line number (starting from 1, not from 0) */ - public function __construct($sText, ?Settings $oParserSettings = null, $lineNumber = 1) + public function __construct($sText, ?Settings $parserSettings = null, $lineNumber = 1) { - if ($oParserSettings === null) { - $oParserSettings = Settings::create(); + if ($parserSettings === null) { + $parserSettings = Settings::create(); } - $this->parserState = new ParserState($sText, $oParserSettings, $lineNumber); + $this->parserState = new ParserState($sText, $parserSettings, $lineNumber); } /** diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 181bd88e..46311289 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -22,7 +22,7 @@ class ParserState /** * @var Settings */ - private $oParserSettings; + private $parserSettings; /** * @var string @@ -60,12 +60,12 @@ class ParserState * @param string $sText the complete CSS as text (i.e., usually the contents of a CSS file) * @param int<0, max> $lineNumber */ - public function __construct($sText, Settings $oParserSettings, $lineNumber = 1) + public function __construct($sText, Settings $parserSettings, $lineNumber = 1) { - $this->oParserSettings = $oParserSettings; + $this->parserSettings = $parserSettings; $this->sText = $sText; $this->lineNumber = $lineNumber; - $this->setCharset($this->oParserSettings->sDefaultCharset); + $this->setCharset($this->parserSettings->sDefaultCharset); } /** @@ -113,7 +113,7 @@ public function currentColumn() */ public function getSettings() { - return $this->oParserSettings; + return $this->parserSettings; } public function anchor(): Anchor @@ -175,7 +175,7 @@ public function parseCharacter($bIsForIdentifier) { if ($this->peek() === '\\') { if ( - $bIsForIdentifier && $this->oParserSettings->bLenientParsing + $bIsForIdentifier && $this->parserSettings->bLenientParsing && ($this->comes('\\0') || $this->comes('\\9')) ) { // Non-strings can contain \0 or \9 which is an IE hack supported in lenient parsing. @@ -239,7 +239,7 @@ public function consumeWhiteSpace(): array while (\preg_match('/\\s/isSu', $this->peek()) === 1) { $this->consume(1); } - if ($this->oParserSettings->bLenientParsing) { + if ($this->parserSettings->bLenientParsing) { try { $oComment = $this->consumeComment(); } catch (UnexpectedEOFException $e) { @@ -440,7 +440,7 @@ public function backtrack($iAmount): void */ public function strlen($sString): int { - if ($this->oParserSettings->bMultibyteSupport) { + if ($this->parserSettings->bMultibyteSupport) { return \mb_strlen($sString, $this->sCharset); } else { return \strlen($sString); @@ -473,7 +473,7 @@ private function substr($iStart, $iLength): string */ private function strtolower($sString): string { - if ($this->oParserSettings->bMultibyteSupport) { + if ($this->parserSettings->bMultibyteSupport) { return \mb_strtolower($sString, $this->sCharset); } else { return \strtolower($sString); @@ -487,7 +487,7 @@ private function strtolower($sString): string */ private function strsplit($sString) { - if ($this->oParserSettings->bMultibyteSupport) { + if ($this->parserSettings->bMultibyteSupport) { if ($this->streql($this->sCharset, 'utf-8')) { return \preg_split('//u', $sString, -1, PREG_SPLIT_NO_EMPTY); } else { @@ -516,7 +516,7 @@ private function strsplit($sString) */ private function strpos($sString, $sNeedle, $iOffset) { - if ($this->oParserSettings->bMultibyteSupport) { + if ($this->parserSettings->bMultibyteSupport) { return \mb_strpos($sString, $sNeedle, $iOffset, $this->sCharset); } else { return \strpos($sString, $sNeedle, $iOffset); From 0094c4fcb37a79d159c3ff1f4132e31c3c134a2a Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 16 Feb 2025 12:14:01 +0100 Subject: [PATCH 197/555] [TASK] Add PHPStan rules from Rector Type Perfect (#939) This is an opiniated set of PHPStan rules to make code more explicit and intuitive to read. https://github.com/rectorphp/type-perfect The new rules show some parts where we could improve our code, which have now been added to the PHPStan baseline. --- composer.json | 3 +- config/phpstan-baseline.neon | 114 +++++++++++++++++++++++++++++++++++ config/phpstan.neon | 7 +++ 3 files changed, 123 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 65c984ca..f07d3222 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,8 @@ "phpstan/phpstan-phpunit": "1.4.2 || 2.0.4", "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.3", "phpunit/phpunit": "8.5.41", - "rector/rector": "1.2.10 || 2.0.7" + "rector/rector": "1.2.10 || 2.0.7", + "rector/type-perfect": "1.0.0 || 2.0.2" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index c10c9c9d..8b834124 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -78,6 +78,12 @@ parameters: count: 1 path: ../src/CSSList/Document.php + - + message: '#^Parameters should have "string\|null" types as the only types passed to this method$#' + identifier: typePerfect.narrowPublicClassMethodParamType + count: 1 + path: ../src/CSSList/Document.php + - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:comments\(\)\.$#' identifier: method.notFound @@ -90,6 +96,24 @@ parameters: count: 1 path: ../src/CSSList/KeyFrame.php + - + message: '#^Parameters should have "string" types as the only types passed to this method$#' + identifier: typePerfect.narrowPublicClassMethodParamType + count: 2 + path: ../src/CSSList/KeyFrame.php + + - + message: '#^Parameters should have "string\|string" types as the only types passed to this method$#' + identifier: typePerfect.narrowPublicClassMethodParamType + count: 1 + path: ../src/OutputFormat.php + + - + message: '#^Returning false in non return bool class method\. Use null with type\|null instead or add bool return type$#' + identifier: typePerfect.nullOverFalse + count: 1 + path: ../src/OutputFormat.php + - message: '#^Variable property access on \$this\(Sabberworm\\CSS\\OutputFormat\)\.$#' identifier: property.dynamicName @@ -132,6 +156,30 @@ parameters: count: 1 path: ../src/Parsing/ParserState.php + - + message: '#^Parameters should have "bool" types as the only types passed to this method$#' + identifier: typePerfect.narrowPublicClassMethodParamType + count: 2 + path: ../src/Parsing/ParserState.php + + - + message: '#^Parameters should have "int" types as the only types passed to this method$#' + identifier: typePerfect.narrowPublicClassMethodParamType + count: 2 + path: ../src/Parsing/ParserState.php + + - + message: '#^Parameters should have "string" types as the only types passed to this method$#' + identifier: typePerfect.narrowPublicClassMethodParamType + count: 1 + path: ../src/Parsing/ParserState.php + + - + message: '#^Parameters should have "string\|int\|null" types as the only types passed to this method$#' + identifier: typePerfect.narrowPublicClassMethodParamType + count: 1 + path: ../src/Parsing/ParserState.php + - message: '#^Cannot call method render\(\) on string\.$#' identifier: method.nonObject @@ -180,6 +228,12 @@ parameters: count: 1 path: ../src/Property/Import.php + - + message: '#^Parameters should have "string" types as the only types passed to this method$#' + identifier: typePerfect.narrowPublicClassMethodParamType + count: 1 + path: ../src/Property/Selector.php + - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:comments\(\)\.$#' identifier: method.notFound @@ -204,6 +258,24 @@ parameters: count: 1 path: ../src/Rule/Rule.php + - + message: '#^Parameters should have "bool" types as the only types passed to this method$#' + identifier: typePerfect.narrowPublicClassMethodParamType + count: 1 + path: ../src/Rule/Rule.php + + - + message: '#^Parameters should have "int\|int" types as the only types passed to this method$#' + identifier: typePerfect.narrowPublicClassMethodParamType + count: 1 + path: ../src/Rule/Rule.php + + - + message: '#^Parameters should have "string" types as the only types passed to this method$#' + identifier: typePerfect.narrowPublicClassMethodParamType + count: 1 + path: ../src/Rule/Rule.php + - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:comments\(\)\.$#' identifier: method.notFound @@ -270,6 +342,18 @@ parameters: count: 1 path: ../src/RuleSet/DeclarationBlock.php + - + message: '#^Parameters should have "string" types as the only types passed to this method$#' + identifier: typePerfect.narrowPublicClassMethodParamType + count: 1 + path: ../src/RuleSet/DeclarationBlock.php + + - + message: '#^Returning false in non return bool class method\. Use null with type\|null instead or add bool return type$#' + identifier: typePerfect.nullOverFalse + count: 1 + path: ../src/RuleSet/DeclarationBlock.php + - message: '#^Argument of an invalid type Sabberworm\\CSS\\Rule\\Rule supplied for foreach, only iterables are supported\.$#' identifier: foreach.nonIterable @@ -324,6 +408,24 @@ parameters: count: 1 path: ../src/RuleSet/RuleSet.php + - + message: '#^Parameters should have "Sabberworm\\CSS\\Rule\\Rule" types as the only types passed to this method$#' + identifier: typePerfect.narrowPublicClassMethodParamType + count: 1 + path: ../src/RuleSet/RuleSet.php + + - + message: '#^Parameters should have "string" types as the only types passed to this method$#' + identifier: typePerfect.narrowPublicClassMethodParamType + count: 1 + path: ../src/RuleSet/RuleSet.php + + - + message: '#^Use explicit methods over array access on object$#' + identifier: typePerfect.noArrayAccessOnObject + count: 1 + path: ../src/RuleSet/RuleSet.php + - message: '#^Loose comparison via "\!\=" is not allowed\.$#' identifier: notEqual.notAllowed @@ -372,12 +474,24 @@ parameters: count: 3 path: ../src/Value/Color.php + - + message: '#^Provide more specific return type "Sabberworm\\CSS\\Value\\Color" over abstract one$#' + identifier: typePerfect.narrowReturnObjectType + count: 1 + path: ../src/Value/Color.php + - message: '#^Loose comparison via "\!\=" is not allowed\.$#' identifier: notEqual.notAllowed count: 1 path: ../src/Value/Size.php + - + message: '#^Parameters should have "float" types as the only types passed to this method$#' + identifier: typePerfect.narrowPublicClassMethodParamType + count: 1 + path: ../src/Value/Size.php + - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:implode\(\)\.$#' identifier: method.notFound diff --git a/config/phpstan.neon b/config/phpstan.neon index 910181dd..06ce70f2 100644 --- a/config/phpstan.neon +++ b/config/phpstan.neon @@ -12,3 +12,10 @@ parameters: - %currentWorkingDirectory%/bin/ - %currentWorkingDirectory%/src/ - %currentWorkingDirectory%/tests/ + + type_perfect: + no_mixed_property: true + no_mixed_caller: true + null_over_false: true + narrow_param: true + narrow_return: true From 631c07e074f4cf1f7a751b247ba8a4985d2f1dd6 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 16 Feb 2025 12:32:22 +0100 Subject: [PATCH 198/555] [TASK] Drop the unused `ParserState::getCharset()` (#941) As this class is `@internal`, we do not need a deprecation process for this. --- src/Parsing/ParserState.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 46311289..9ffd65e0 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -82,16 +82,6 @@ public function setCharset($sCharset): void } } - /** - * Returns the charset that is used if the CSS does not contain an `@charset` declaration. - * - * @return string - */ - public function getCharset() - { - return $this->sCharset; - } - /** * @return int */ From 17dd3cbc90a55f309657d0832d2933e422feb67f Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 16 Feb 2025 20:07:42 +0100 Subject: [PATCH 199/555] [CLEANUP] Avoid Hungarian notation for `outputFormat(ter)` (#942) Part of #756 --- src/OutputFormat.php | 13 +++++++------ src/OutputFormatter.php | 28 ++++++++++++++-------------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 7f3786be..4498b34b 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -39,7 +39,8 @@ class OutputFormat * Spacing * 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");`) + * The triples (After, Before, Between) can be set using a wildcard + * (e.g. `$outputFormat->set('Space*Rules', "\n");`) * * @var string * @@ -217,7 +218,7 @@ class OutputFormat /** * @var OutputFormatter|null */ - private $oFormatter; + private $outputFormatter; /** * @var OutputFormat|null @@ -788,7 +789,7 @@ public function nextLevel(): self if ($this->oNextLevelFormat === null) { $this->oNextLevelFormat = clone $this; $this->oNextLevelFormat->iIndentationLevel++; - $this->oNextLevelFormat->oFormatter = null; + $this->oNextLevelFormat->outputFormatter = null; } return $this->oNextLevelFormat; } @@ -800,10 +801,10 @@ public function beLenient(): void public function getFormatter(): OutputFormatter { - if ($this->oFormatter === null) { - $this->oFormatter = new OutputFormatter($this); + if ($this->outputFormatter === null) { + $this->outputFormatter = new OutputFormatter($this); } - return $this->oFormatter; + return $this->outputFormatter; } /** diff --git a/src/OutputFormatter.php b/src/OutputFormatter.php index 48db2e0b..e7f2af64 100644 --- a/src/OutputFormatter.php +++ b/src/OutputFormatter.php @@ -12,11 +12,11 @@ class OutputFormatter /** * @var OutputFormat */ - private $oFormat; + private $outputFormat; - public function __construct(OutputFormat $oFormat) + public function __construct(OutputFormat $outputFormat) { - $this->oFormat = $oFormat; + $this->outputFormat = $outputFormat; } /** @@ -25,7 +25,7 @@ public function __construct(OutputFormat $oFormat) */ public function space($sName, $sType = null): string { - $sSpaceString = $this->oFormat->get("Space$sName"); + $sSpaceString = $this->outputFormat->get("Space$sName"); // If $sSpaceString is an array, we have multiple values configured // depending on the type of object the space applies to if (\is_array($sSpaceString)) { @@ -91,7 +91,7 @@ public function spaceAfterSelectorSeparator(): string */ public function spaceBeforeListArgumentSeparator($sSeparator): string { - $spaceForSeparator = $this->oFormat->getSpaceBeforeListArgumentSeparators(); + $spaceForSeparator = $this->outputFormat->getSpaceBeforeListArgumentSeparators(); return $spaceForSeparator[$sSeparator] ?? $this->space('BeforeListArgumentSeparator', $sSeparator); } @@ -101,7 +101,7 @@ public function spaceBeforeListArgumentSeparator($sSeparator): string */ public function spaceAfterListArgumentSeparator($sSeparator): string { - $spaceForSeparator = $this->oFormat->getSpaceAfterListArgumentSeparators(); + $spaceForSeparator = $this->outputFormat->getSpaceAfterListArgumentSeparators(); return $spaceForSeparator[$sSeparator] ?? $this->space('AfterListArgumentSeparator', $sSeparator); } @@ -120,7 +120,7 @@ public function spaceBeforeOpeningBrace(): string */ public function safely($cCode) { - if ($this->oFormat->get('IgnoreExceptions')) { + if ($this->outputFormat->get('IgnoreExceptions')) { // If output exceptions are ignored, run the code with exception guards try { return $cCode(); @@ -142,9 +142,9 @@ public function safely($cCode) public function implode(string $sSeparator, array $aValues, $bIncreaseLevel = false): string { $result = ''; - $oFormat = $this->oFormat; + $outputFormat = $this->outputFormat; if ($bIncreaseLevel) { - $oFormat = $oFormat->nextLevel(); + $outputFormat = $outputFormat->nextLevel(); } $bIsFirst = true; foreach ($aValues as $mValue) { @@ -154,7 +154,7 @@ public function implode(string $sSeparator, array $aValues, $bIncreaseLevel = fa $result .= $sSeparator; } if ($mValue instanceof Renderable) { - $result .= $mValue->render($oFormat); + $result .= $mValue->render($outputFormat); } else { $result .= $mValue; } @@ -169,7 +169,7 @@ public function implode(string $sSeparator, array $aValues, $bIncreaseLevel = fa */ public function removeLastSemicolon($sString) { - if ($this->oFormat->get('SemicolonAfterLastRule')) { + if ($this->outputFormat->get('SemicolonAfterLastRule')) { return $sString; } $sString = \explode(';', $sString); @@ -184,7 +184,7 @@ public function removeLastSemicolon($sString) public function comments(Commentable $oCommentable): string { - if (!$this->oFormat->bRenderComments) { + if (!$this->outputFormat->bRenderComments) { return ''; } @@ -193,7 +193,7 @@ public function comments(Commentable $oCommentable): string $iLastCommentIndex = \count($comments) - 1; foreach ($comments as $i => $oComment) { - $result .= $oComment->render($this->oFormat); + $result .= $oComment->render($this->outputFormat); $result .= $i === $iLastCommentIndex ? $this->spaceAfterBlocks() : $this->spaceBetweenBlocks(); } return $result; @@ -212,6 +212,6 @@ private function prepareSpace($sSpaceString): string */ private function indent(): string { - return \str_repeat($this->oFormat->sIndentation, $this->oFormat->getIndentationLevel()); + return \str_repeat($this->outputFormat->sIndentation, $this->outputFormat->getIndentationLevel()); } } From 8c770fbe16c7616cc5290c5170e782dc37a6586b Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 17 Feb 2025 19:13:07 +0100 Subject: [PATCH 200/555] [TASK] Narrow the some type annotations in `OutputFormatter` (#945) --- src/OutputFormatter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OutputFormatter.php b/src/OutputFormatter.php index e7f2af64..c414e9e9 100644 --- a/src/OutputFormatter.php +++ b/src/OutputFormatter.php @@ -87,7 +87,7 @@ public function spaceAfterSelectorSeparator(): string } /** - * @param string $sSeparator + * @param non-empty-string $sSeparator */ public function spaceBeforeListArgumentSeparator($sSeparator): string { @@ -97,7 +97,7 @@ public function spaceBeforeListArgumentSeparator($sSeparator): string } /** - * @param string $sSeparator + * @param non-empty-string $sSeparator */ public function spaceAfterListArgumentSeparator($sSeparator): string { From 1a385cdbf4e55f20fe31d55925d840607844662f Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 17 Feb 2025 19:33:37 +0100 Subject: [PATCH 201/555] [CLEANUP] Avoid Hungarian notation for `text`/`characters` (#940) --- src/Parser.php | 6 +++--- src/Parsing/ParserState.php | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Parser.php b/src/Parser.php index 7f3a9229..eae809a5 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -19,15 +19,15 @@ class Parser private $parserState; /** - * @param string $sText the complete CSS as text (i.e., usually the contents of a CSS file) + * @param string $text the complete CSS as text (i.e., usually the contents of a CSS file) * @param int<0, max> $lineNumber the line number (starting from 1, not from 0) */ - public function __construct($sText, ?Settings $parserSettings = null, $lineNumber = 1) + public function __construct($text, ?Settings $parserSettings = null, $lineNumber = 1) { if ($parserSettings === null) { $parserSettings = Settings::create(); } - $this->parserState = new ParserState($sText, $parserSettings, $lineNumber); + $this->parserState = new ParserState($text, $parserSettings, $lineNumber); } /** diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 9ffd65e0..261c3995 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -27,12 +27,12 @@ class ParserState /** * @var string */ - private $sText; + private $text; /** * @var array */ - private $aText; + private $characters; /** * @var int @@ -57,13 +57,13 @@ class ParserState private $lineNumber; /** - * @param string $sText the complete CSS as text (i.e., usually the contents of a CSS file) + * @param string $text the complete CSS as text (i.e., usually the contents of a CSS file) * @param int<0, max> $lineNumber */ - public function __construct($sText, Settings $parserSettings, $lineNumber = 1) + public function __construct($text, Settings $parserSettings, $lineNumber = 1) { $this->parserSettings = $parserSettings; - $this->sText = $sText; + $this->text = $text; $this->lineNumber = $lineNumber; $this->setCharset($this->parserSettings->sDefaultCharset); } @@ -76,9 +76,9 @@ public function __construct($sText, Settings $parserSettings, $lineNumber = 1) public function setCharset($sCharset): void { $this->sCharset = $sCharset; - $this->aText = $this->strsplit($this->sText); - if (\is_array($this->aText)) { - $this->iLength = \count($this->aText); + $this->characters = $this->strsplit($this->text); + if (\is_array($this->characters)) { + $this->iLength = \count($this->characters); } } @@ -451,7 +451,7 @@ private function substr($iStart, $iLength): string } $result = ''; while ($iLength > 0) { - $result .= $this->aText[$iStart]; + $result .= $this->characters[$iStart]; $iStart++; $iLength--; } From 8a3cc0c8607f1791c8f953f7ae47f49d2ad72d26 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 17 Feb 2025 19:57:26 +0100 Subject: [PATCH 202/555] [TASK] Add some tests for `OutputFormatter` (part 1) (#943) Note: The new PHPStan warnings will go away once we add native type declarations for `OutputFormatter`. Part of #757 Co-authored-by: JakeQZ --- config/phpstan-baseline.neon | 6 + tests/Unit/OutputFormatterTest.php | 300 +++++++++++++++++++++++++++++ 2 files changed, 306 insertions(+) create mode 100644 tests/Unit/OutputFormatterTest.php diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 8b834124..47af1164 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -120,6 +120,12 @@ parameters: count: 4 path: ../src/OutputFormat.php + - + message: '#^Parameters should have "string" types as the only types passed to this method$#' + identifier: typePerfect.narrowPublicClassMethodParamType + count: 2 + path: ../src/OutputFormatter.php + - message: '#^Default value of the parameter \#2 \$bIncludeEnd \(false\) of method Sabberworm\\CSS\\Parsing\\ParserState\:\:consumeUntil\(\) is incompatible with type string\.$#' identifier: parameter.defaultValue diff --git a/tests/Unit/OutputFormatterTest.php b/tests/Unit/OutputFormatterTest.php new file mode 100644 index 00000000..94c967bd --- /dev/null +++ b/tests/Unit/OutputFormatterTest.php @@ -0,0 +1,300 @@ +outputFormat = new OutputFormat(); + $this->subject = new OutputFormatter($this->outputFormat); + } + + /** + * @test + */ + public function spaceAfterRuleNameReturnsSpaceAfterRuleNameFromOutputFormat(): void + { + $space = ' '; + $this->outputFormat->setSpaceAfterRuleName($space); + + self::assertSame($space, $this->subject->spaceAfterRuleName()); + } + + /** + * @test + */ + public function spaceBeforeRulesReturnsSpaceBeforeRulesFromOutputFormat(): void + { + $space = ' '; + $this->outputFormat->setSpaceBeforeRules($space); + + self::assertSame($space, $this->subject->spaceBeforeRules()); + } + + /** + * @test + */ + public function spaceAfterRulesReturnsSpaceAfterRulesFromOutputFormat(): void + { + $space = ' '; + $this->outputFormat->setSpaceAfterRules($space); + + self::assertSame($space, $this->subject->spaceAfterRules()); + } + + /** + * @test + */ + public function spaceBetweenRulesReturnsSpaceBetweenRulesFromOutputFormat(): void + { + $space = ' '; + $this->outputFormat->setSpaceBetweenRules($space); + + self::assertSame($space, $this->subject->spaceBetweenRules()); + } + + /** + * @test + */ + public function spaceBeforeBlocksReturnsSpaceBeforeBlocksFromOutputFormat(): void + { + $space = ' '; + $this->outputFormat->setSpaceBeforeBlocks($space); + + self::assertSame($space, $this->subject->spaceBeforeBlocks()); + } + + /** + * @test + */ + public function spaceAfterBlocksReturnsSpaceAfterBlocksFromOutputFormat(): void + { + $space = ' '; + $this->outputFormat->setSpaceAfterBlocks($space); + + self::assertSame($space, $this->subject->spaceAfterBlocks()); + } + + /** + * @test + */ + public function spaceBetweenBlocksReturnsSpaceBetweenBlocksFromOutputFormat(): void + { + $space = ' '; + $this->outputFormat->setSpaceBetweenBlocks($space); + + self::assertSame($space, $this->subject->spaceBetweenBlocks()); + } + + /** + * @test + */ + public function spaceBeforeSelectorSeparatorReturnsSpaceBeforeSelectorSeparatorFromOutputFormat(): void + { + $space = ' '; + $this->outputFormat->setSpaceBeforeSelectorSeparator($space); + + self::assertSame($space, $this->subject->spaceBeforeSelectorSeparator()); + } + + /** + * @test + */ + public function spaceAfterSelectorSeparatorReturnsSpaceAfterSelectorSeparatorFromOutputFormat(): void + { + $space = ' '; + $this->outputFormat->setSpaceAfterSelectorSeparator($space); + + self::assertSame($space, $this->subject->spaceAfterSelectorSeparator()); + } + + /** + * @test + */ + public function spaceBeforeListArgumentSeparatorReturnsSpaceSetForSpecificSeparator(): void + { + $separator = ','; + $space = ' '; + $this->outputFormat->setSpaceBeforeListArgumentSeparators([$separator => $space]); + $defaultSpace = "\t\t\t\t"; + $this->outputFormat->setSpaceBeforeListArgumentSeparator($defaultSpace); + + self::assertSame($space, $this->subject->spaceBeforeListArgumentSeparator($separator)); + } + + /** + * @test + */ + public function spaceBeforeListArgumentSeparatorWithoutSpecificSettingReturnsDefaultSpace( + ): void { + $space = ' '; + $this->outputFormat->setSpaceBeforeListArgumentSeparators([',' => $space]); + $defaultSpace = "\t\t\t\t"; + $this->outputFormat->setSpaceBeforeListArgumentSeparator($defaultSpace); + + self::assertSame($defaultSpace, $this->subject->spaceBeforeListArgumentSeparator(';')); + } + + /** + * @test + */ + public function spaceAfterListArgumentSeparatorReturnsSpaceSetForSpecificSeparator(): void + { + $separator = ','; + $space = ' '; + $this->outputFormat->setSpaceAfterListArgumentSeparators([$separator => $space]); + $defaultSpace = "\t\t\t\t"; + $this->outputFormat->setSpaceAfterListArgumentSeparator($defaultSpace); + + self::assertSame($space, $this->subject->spaceAfterListArgumentSeparator($separator)); + } + + /** + * @test + */ + public function spaceAfterListArgumentSeparatorWithoutSpecificSettingReturnsDefaultSpace( + ): void { + $space = ' '; + $this->outputFormat->setSpaceAfterListArgumentSeparators([',' => $space]); + $defaultSpace = "\t\t\t\t"; + $this->outputFormat->setSpaceAfterListArgumentSeparator($defaultSpace); + + self::assertSame($defaultSpace, $this->subject->spaceAfterListArgumentSeparator(';')); + } + + /** + * @test + */ + public function spaceBeforeOpeningBraceReturnsSpaceBeforeOpeningBraceFromOutputFormat(): void + { + $space = ' '; + $this->outputFormat->setSpaceBeforeOpeningBrace($space); + + self::assertSame($space, $this->subject->spaceBeforeOpeningBrace()); + } + + /** + * @test + */ + public function implodeForEmptyValuesReturnsEmptyString(): void + { + $values = []; + + $result = $this->subject->implode(', ', $values); + + self::assertSame('', $result); + } + + /** + * @test + */ + public function implodeWithOneStringValueReturnsStringValue(): void + { + $value = 'tea'; + $values = [$value]; + + $result = $this->subject->implode(', ', $values); + + self::assertSame($value, $result); + } + + /** + * @test + */ + public function implodeWithMultipleStringValuesReturnsValuesSeparatedBySeparator(): void + { + $value1 = 'tea'; + $value2 = 'coffee'; + $values = [$value1, $value2]; + $separator = ', '; + + $result = $this->subject->implode($separator, $values); + + self::assertSame($value1 . $separator . $value2, $result); + } + + /** + * @test + */ + public function implodeWithOneRenderableReturnsRenderedRenderable(): void + { + $renderable = $this->createMock(Renderable::class); + $renderedRenderable = 'tea'; + $renderable->method('render')->with($this->outputFormat)->willReturn($renderedRenderable); + $values = [$renderable]; + + $result = $this->subject->implode(', ', $values); + + self::assertSame($renderedRenderable, $result); + } + + /** + * @test + */ + public function implodeWithMultipleRenderablesReturnsRenderedRenderablesSeparatedBySeparator(): void + { + $renderable1 = $this->createMock(Renderable::class); + $renderedRenderable1 = 'tea'; + $renderable1->method('render')->with($this->outputFormat)->willReturn($renderedRenderable1); + $renderable2 = $this->createMock(Renderable::class); + $renderedRenderable2 = 'coffee'; + $renderable2->method('render')->with($this->outputFormat)->willReturn($renderedRenderable2); + $values = [$renderable1, $renderable2]; + $separator = ', '; + + $result = $this->subject->implode($separator, $values); + + self::assertSame($renderedRenderable1 . $separator . $renderedRenderable2, $result); + } + + /** + * @test + */ + public function implodeWithIncreaseLevelFalseUsesDefaultIndentationLevelForRendering(): void + { + $renderable = $this->createMock(Renderable::class); + $renderedRenderable = 'tea'; + $renderable->method('render')->with($this->outputFormat)->willReturn($renderedRenderable); + $values = [$renderable]; + + $result = $this->subject->implode(', ', $values, false); + + self::assertSame($renderedRenderable, $result); + } + + /** + * @test + */ + public function implodeWithIncreaseLevelTrueIncreasesIndentationLevelForRendering(): void + { + $renderable = $this->createMock(Renderable::class); + $renderedRenderable = 'tea'; + $renderable->method('render')->with($this->outputFormat->nextLevel())->willReturn($renderedRenderable); + $values = [$renderable]; + + $result = $this->subject->implode(', ', $values, true); + + self::assertSame($renderedRenderable, $result); + } +} From 45262028ea922f03df42a8992929605d0ae59e3e Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 17 Feb 2025 23:22:53 +0100 Subject: [PATCH 203/555] [CLEANUP] Avoid Hungarian notation for `currentPosition` (#947) Part of #756 --- src/Parsing/ParserState.php | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 261c3995..525150e2 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -37,7 +37,7 @@ class ParserState /** * @var int */ - private $iCurrentPosition = 0; + private $currentPosition = 0; /** * will only be used if the CSS does not contain an `@charset` declaration @@ -95,7 +95,7 @@ public function currentLine() */ public function currentColumn() { - return $this->iCurrentPosition; + return $this->currentPosition; } /** @@ -108,7 +108,7 @@ public function getSettings() public function anchor(): Anchor { - return new Anchor($this->iCurrentPosition, $this); + return new Anchor($this->currentPosition, $this); } /** @@ -116,7 +116,7 @@ public function anchor(): Anchor */ public function setPosition($position): void { - $this->iCurrentPosition = $position; + $this->currentPosition = $position; } /** @@ -233,7 +233,7 @@ public function consumeWhiteSpace(): array try { $oComment = $this->consumeComment(); } catch (UnexpectedEOFException $e) { - $this->iCurrentPosition = $this->iLength; + $this->currentPosition = $this->iLength; return $comments; } } else { @@ -264,7 +264,7 @@ public function comes($sString, $bCaseInsensitive = false): bool */ public function peek($iLength = 1, $iOffset = 0): string { - $iOffset += $this->iCurrentPosition; + $iOffset += $this->currentPosition; if ($iOffset >= $this->iLength) { return ''; } @@ -282,7 +282,7 @@ public function consume($mValue = 1): string if (\is_string($mValue)) { $iLineCount = \substr_count($mValue, "\n"); $iLength = $this->strlen($mValue); - if (!$this->streql($this->substr($this->iCurrentPosition, $iLength), $mValue)) { + if (!$this->streql($this->substr($this->currentPosition, $iLength), $mValue)) { throw new UnexpectedTokenException( $mValue, $this->peek(\max($iLength, 5)), @@ -291,16 +291,16 @@ public function consume($mValue = 1): string ); } $this->lineNumber += $iLineCount; - $this->iCurrentPosition += $this->strlen($mValue); + $this->currentPosition += $this->strlen($mValue); return $mValue; } else { - if ($this->iCurrentPosition + $mValue > $this->iLength) { + if ($this->currentPosition + $mValue > $this->iLength) { throw new UnexpectedEOFException((string) $mValue, $this->peek(5), 'count', $this->lineNumber); } - $result = $this->substr($this->iCurrentPosition, $mValue); + $result = $this->substr($this->currentPosition, $mValue); $iLineCount = \substr_count($result, "\n"); $this->lineNumber += $iLineCount; - $this->iCurrentPosition += $mValue; + $this->currentPosition += $mValue; return $result; } } @@ -351,7 +351,7 @@ public function consumeComment() public function isEnd(): bool { - return $this->iCurrentPosition >= $this->iLength; + return $this->currentPosition >= $this->iLength; } /** @@ -367,7 +367,7 @@ public function consumeUntil($aEnd, $bIncludeEnd = false, $consumeEnd = false, a { $aEnd = \is_array($aEnd) ? $aEnd : [$aEnd]; $out = ''; - $start = $this->iCurrentPosition; + $start = $this->currentPosition; while (!$this->isEnd()) { $char = $this->consume(1); @@ -375,7 +375,7 @@ public function consumeUntil($aEnd, $bIncludeEnd = false, $consumeEnd = false, a if ($bIncludeEnd) { $out .= $char; } elseif (!$consumeEnd) { - $this->iCurrentPosition -= $this->strlen($char); + $this->currentPosition -= $this->strlen($char); } return $out; } @@ -389,7 +389,7 @@ public function consumeUntil($aEnd, $bIncludeEnd = false, $consumeEnd = false, a return $out; } - $this->iCurrentPosition = $start; + $this->currentPosition = $start; throw new UnexpectedEOFException( 'One of ("' . \implode('","', $aEnd) . '")', $this->peek(5), @@ -400,7 +400,7 @@ public function consumeUntil($aEnd, $bIncludeEnd = false, $consumeEnd = false, a private function inputLeft(): string { - return $this->substr($this->iCurrentPosition, -1); + return $this->substr($this->currentPosition, -1); } /** @@ -422,7 +422,7 @@ public function streql($sString1, $sString2, $bCaseInsensitive = true): bool */ public function backtrack($iAmount): void { - $this->iCurrentPosition -= $iAmount; + $this->currentPosition -= $iAmount; } /** From 0cf4a7666c63db5ebb8edb9f163bb4131e0cfd95 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 17 Feb 2025 23:54:58 +0100 Subject: [PATCH 204/555] [CLEANUP] Avoid Hungarian notation for `charset` (#948) Part of #756 --- src/Parsing/ParserState.php | 22 +++++++++++----------- src/Property/Charset.php | 8 ++++---- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 525150e2..bfed3285 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -44,7 +44,7 @@ class ParserState * * @var string */ - private $sCharset; + private $charset; /** * @var int @@ -71,11 +71,11 @@ public function __construct($text, Settings $parserSettings, $lineNumber = 1) /** * Sets the charset to be used if the CSS does not contain an `@charset` declaration. * - * @param string $sCharset + * @param string $charset */ - public function setCharset($sCharset): void + public function setCharset($charset): void { - $this->sCharset = $sCharset; + $this->charset = $charset; $this->characters = $this->strsplit($this->text); if (\is_array($this->characters)) { $this->iLength = \count($this->characters); @@ -195,7 +195,7 @@ public function parseCharacter($bIsForIdentifier) $sUtf32 .= \chr($iUnicode & 0xff); $iUnicode = $iUnicode >> 8; } - return \iconv('utf-32le', $this->sCharset, $sUtf32); + return \iconv('utf-32le', $this->charset, $sUtf32); } if ($bIsForIdentifier) { $peek = \ord($this->peek()); @@ -431,7 +431,7 @@ public function backtrack($iAmount): void public function strlen($sString): int { if ($this->parserSettings->bMultibyteSupport) { - return \mb_strlen($sString, $this->sCharset); + return \mb_strlen($sString, $this->charset); } else { return \strlen($sString); } @@ -464,7 +464,7 @@ private function substr($iStart, $iLength): string private function strtolower($sString): string { if ($this->parserSettings->bMultibyteSupport) { - return \mb_strtolower($sString, $this->sCharset); + return \mb_strtolower($sString, $this->charset); } else { return \strtolower($sString); } @@ -478,13 +478,13 @@ private function strtolower($sString): string private function strsplit($sString) { if ($this->parserSettings->bMultibyteSupport) { - if ($this->streql($this->sCharset, 'utf-8')) { + if ($this->streql($this->charset, 'utf-8')) { return \preg_split('//u', $sString, -1, PREG_SPLIT_NO_EMPTY); } else { - $iLength = \mb_strlen($sString, $this->sCharset); + $iLength = \mb_strlen($sString, $this->charset); $result = []; for ($i = 0; $i < $iLength; ++$i) { - $result[] = \mb_substr($sString, $i, 1, $this->sCharset); + $result[] = \mb_substr($sString, $i, 1, $this->charset); } return $result; } @@ -507,7 +507,7 @@ private function strsplit($sString) private function strpos($sString, $sNeedle, $iOffset) { if ($this->parserSettings->bMultibyteSupport) { - return \mb_strpos($sString, $sNeedle, $iOffset, $this->sCharset); + return \mb_strpos($sString, $sNeedle, $iOffset, $this->charset); } else { return \strpos($sString, $sNeedle, $iOffset); } diff --git a/src/Property/Charset.php b/src/Property/Charset.php index 9addf369..20522739 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -55,12 +55,12 @@ public function getLineNo(): int } /** - * @param string|CSSString $sCharset + * @param string|CSSString $charset */ - public function setCharset($sCharset): void + public function setCharset($charset): void { - $sCharset = $sCharset instanceof CSSString ? $sCharset : new CSSString($sCharset); - $this->oCharset = $sCharset; + $charset = $charset instanceof CSSString ? $charset : new CSSString($charset); + $this->oCharset = $charset; } /** From 00799253cd958c3c27128c4b85a3a56ba0a54ac6 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 18 Feb 2025 17:27:44 +0100 Subject: [PATCH 205/555] [CLEANUP] Avoid Hungarian notation for `length` (#950) Part of #756 --- src/Parsing/ParserState.php | 44 ++++++++++++++++++------------------- src/Value/Size.php | 6 ++--- src/Value/Value.php | 8 +++---- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index bfed3285..bf348ed6 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -49,7 +49,7 @@ class ParserState /** * @var int */ - private $iLength; + private $length; /** * @var int @@ -78,7 +78,7 @@ public function setCharset($charset): void $this->charset = $charset; $this->characters = $this->strsplit($this->text); if (\is_array($this->characters)) { - $this->iLength = \count($this->characters); + $this->length = \count($this->characters); } } @@ -233,7 +233,7 @@ public function consumeWhiteSpace(): array try { $oComment = $this->consumeComment(); } catch (UnexpectedEOFException $e) { - $this->currentPosition = $this->iLength; + $this->currentPosition = $this->length; return $comments; } } else { @@ -259,16 +259,16 @@ public function comes($sString, $bCaseInsensitive = false): bool } /** - * @param int $iLength + * @param int $length * @param int $iOffset */ - public function peek($iLength = 1, $iOffset = 0): string + public function peek($length = 1, $iOffset = 0): string { $iOffset += $this->currentPosition; - if ($iOffset >= $this->iLength) { + if ($iOffset >= $this->length) { return ''; } - return $this->substr($iOffset, $iLength); + return $this->substr($iOffset, $length); } /** @@ -281,11 +281,11 @@ public function consume($mValue = 1): string { if (\is_string($mValue)) { $iLineCount = \substr_count($mValue, "\n"); - $iLength = $this->strlen($mValue); - if (!$this->streql($this->substr($this->currentPosition, $iLength), $mValue)) { + $length = $this->strlen($mValue); + if (!$this->streql($this->substr($this->currentPosition, $length), $mValue)) { throw new UnexpectedTokenException( $mValue, - $this->peek(\max($iLength, 5)), + $this->peek(\max($length, 5)), 'literal', $this->lineNumber ); @@ -294,7 +294,7 @@ public function consume($mValue = 1): string $this->currentPosition += $this->strlen($mValue); return $mValue; } else { - if ($this->currentPosition + $mValue > $this->iLength) { + if ($this->currentPosition + $mValue > $this->length) { throw new UnexpectedEOFException((string) $mValue, $this->peek(5), 'count', $this->lineNumber); } $result = $this->substr($this->currentPosition, $mValue); @@ -351,7 +351,7 @@ public function consumeComment() public function isEnd(): bool { - return $this->currentPosition >= $this->iLength; + return $this->currentPosition >= $this->length; } /** @@ -439,21 +439,21 @@ public function strlen($sString): int /** * @param int $iStart - * @param int $iLength + * @param int $length */ - private function substr($iStart, $iLength): string + private function substr($iStart, $length): string { - if ($iLength < 0) { - $iLength = $this->iLength - $iStart + $iLength; + if ($length < 0) { + $length = $this->length - $iStart + $length; } - if ($iStart + $iLength > $this->iLength) { - $iLength = $this->iLength - $iStart; + if ($iStart + $length > $this->length) { + $length = $this->length - $iStart; } $result = ''; - while ($iLength > 0) { + while ($length > 0) { $result .= $this->characters[$iStart]; $iStart++; - $iLength--; + $length--; } return $result; } @@ -481,9 +481,9 @@ private function strsplit($sString) if ($this->streql($this->charset, 'utf-8')) { return \preg_split('//u', $sString, -1, PREG_SPLIT_NO_EMPTY); } else { - $iLength = \mb_strlen($sString, $this->charset); + $length = \mb_strlen($sString, $this->charset); $result = []; - for ($i = 0; $i < $iLength; ++$i) { + for ($i = 0; $i < $length; ++$i) { $result[] = \mb_substr($sString, $i, 1, $this->charset); } return $result; diff --git a/src/Value/Size.php b/src/Value/Size.php index 5f5ab4d7..748e2849 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -112,11 +112,11 @@ public static function parse(ParserState $parserState, $bIsColorComponent = fals $sUnit = null; $aSizeUnits = self::getSizeUnits(); - foreach ($aSizeUnits as $iLength => &$aValues) { - $sKey = \strtolower($parserState->peek($iLength)); + foreach ($aSizeUnits as $length => &$aValues) { + $sKey = \strtolower($parserState->peek($length)); if (\array_key_exists($sKey, $aValues)) { if (($sUnit = $aValues[$sKey]) !== null) { - $parserState->consume($iLength); + $parserState->consume($length); break; } } diff --git a/src/Value/Value.php b/src/Value/Value.php index b6973438..88b0255f 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -83,18 +83,18 @@ public static function parseValue(ParserState $parserState, array $aListDelimite $aNewStack[] = $aStack[$iStartPosition]; continue; } - $iLength = 2; //Number of elements to be joined - for ($i = $iStartPosition + 3; $i < $iStackLength; $i += 2, ++$iLength) { + $length = 2; //Number of elements to be joined + for ($i = $iStartPosition + 3; $i < $iStackLength; $i += 2, ++$length) { if ($sDelimiter !== $aStack[$i]) { break; } } $list = new RuleValueList($sDelimiter, $parserState->currentLine()); - for ($i = $iStartPosition; $i - $iStartPosition < $iLength * 2; $i += 2) { + for ($i = $iStartPosition; $i - $iStartPosition < $length * 2; $i += 2) { $list->addListComponent($aStack[$i]); } $aNewStack[] = $list; - $iStartPosition += $iLength * 2 - 2; + $iStartPosition += $length * 2 - 2; } $aStack = $aNewStack; } From bb72f6eb915c1686106731ec72cf6caf43776ba3 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 18 Feb 2025 17:36:12 +0100 Subject: [PATCH 206/555] [CLEANUP] Drop redundant `@internal` annotations (#951) If a whole class is `@internal`, there is no need to mark the individual methods, properties or constants as `@internal`. --- src/Parsing/ParserState.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index bf348ed6..01658820 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -14,8 +14,6 @@ class ParserState { /** * @var null - * - * @internal since 8.5.2 */ public const EOF = null; @@ -125,8 +123,6 @@ public function setPosition($position): void * @return string * * @throws UnexpectedTokenException - * - * @internal since V8.8.0 */ public function parseIdentifier($bIgnoreCase = true) { @@ -158,8 +154,6 @@ public function parseIdentifier($bIgnoreCase = true) * * @throws UnexpectedEOFException * @throws UnexpectedTokenException - * - * @internal since V8.8.0 */ public function parseCharacter($bIsForIdentifier) { From d71ff28357e01ab5902d65e86b6b57c135e9cdec Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 18 Feb 2025 17:47:56 +0100 Subject: [PATCH 207/555] [TASK] Use more real-life data in a unit test (#952) This makes the test easier to read. Also, this prepares for when an `AtRuleBlockList` might not allow an empty type anymore. --- tests/Unit/CSSList/AtRuleBlockListTest.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/Unit/CSSList/AtRuleBlockListTest.php b/tests/Unit/CSSList/AtRuleBlockListTest.php index 30a7f305..782d1e3c 100644 --- a/tests/Unit/CSSList/AtRuleBlockListTest.php +++ b/tests/Unit/CSSList/AtRuleBlockListTest.php @@ -21,7 +21,7 @@ final class AtRuleBlockListTest extends TestCase */ public function implementsAtRule(): void { - $subject = new AtRuleBlockList(''); + $subject = new AtRuleBlockList('supports'); self::assertInstanceOf(AtRuleBlockList::class, $subject); } @@ -31,7 +31,7 @@ public function implementsAtRule(): void */ public function implementsRenderable(): void { - $subject = new AtRuleBlockList(''); + $subject = new AtRuleBlockList('supports'); self::assertInstanceOf(Renderable::class, $subject); } @@ -41,7 +41,7 @@ public function implementsRenderable(): void */ public function implementsCommentable(): void { - $subject = new AtRuleBlockList(''); + $subject = new AtRuleBlockList('supports'); self::assertInstanceOf(Commentable::class, $subject); } @@ -51,7 +51,7 @@ public function implementsCommentable(): void */ public function atRuleNameReturnsTypeProvidedToConstructor(): void { - $type = 'foo'; + $type = 'keyframes'; $subject = new AtRuleBlockList($type); @@ -63,7 +63,7 @@ public function atRuleNameReturnsTypeProvidedToConstructor(): void */ public function getLineNoByDefaultReturnsZero(): void { - $subject = new AtRuleBlockList(''); + $subject = new AtRuleBlockList('supports'); self::assertSame(0, $subject->getLineNo()); } @@ -73,7 +73,7 @@ public function getLineNoByDefaultReturnsZero(): void */ public function atRuleArgsByDefaultReturnsEmptyString(): void { - $subject = new AtRuleBlockList(''); + $subject = new AtRuleBlockList('supports'); self::assertSame('', $subject->atRuleArgs()); } @@ -107,7 +107,7 @@ public function getLineNoReturnsLineNumberProvidedToConstructor(): void */ public function isRootListAlwaysReturnsFalse(): void { - $subject = new AtRuleBlockList(''); + $subject = new AtRuleBlockList('supports'); self::assertFalse($subject->isRootList()); } From cff67f01d289f647ce3047045ae7f0a1bc7c3a1c Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 20 Feb 2025 02:29:30 +0100 Subject: [PATCH 208/555] [CLEANUP] Avoid Hungarian notation for `ignoreCase` (#955) Part of #756 --- src/Parsing/ParserState.php | 6 +++--- src/Value/CSSFunction.php | 8 ++++---- src/Value/CalcFunction.php | 2 +- src/Value/Value.php | 8 ++++---- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 01658820..4c680842 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -118,13 +118,13 @@ public function setPosition($position): void } /** - * @param bool $bIgnoreCase + * @param bool $ignoreCase * * @return string * * @throws UnexpectedTokenException */ - public function parseIdentifier($bIgnoreCase = true) + public function parseIdentifier($ignoreCase = true) { if ($this->isEnd()) { throw new UnexpectedEOFException('', '', 'identifier', $this->lineNumber); @@ -141,7 +141,7 @@ public function parseIdentifier($bIgnoreCase = true) $result .= '\\' . $sCharacter; } } - if ($bIgnoreCase) { + if ($ignoreCase) { $result = $this->strtolower($result); } return $result; diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index 458c832f..fa435549 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -47,9 +47,9 @@ public function __construct($sName, $aArguments, $sSeparator = ',', $lineNumber * * @internal since V8.8.0 */ - public static function parse(ParserState $parserState, bool $bIgnoreCase = false): CSSFunction + public static function parse(ParserState $parserState, bool $ignoreCase = false): CSSFunction { - $sName = self::parseName($parserState, $bIgnoreCase); + $sName = self::parseName($parserState, $ignoreCase); $parserState->consume('('); $mArguments = self::parseArguments($parserState); @@ -64,9 +64,9 @@ public static function parse(ParserState $parserState, bool $bIgnoreCase = false * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ - private static function parseName(ParserState $parserState, bool $bIgnoreCase = false): string + private static function parseName(ParserState $parserState, bool $ignoreCase = false): string { - return $parserState->parseIdentifier($bIgnoreCase); + return $parserState->parseIdentifier($ignoreCase); } /** diff --git a/src/Value/CalcFunction.php b/src/Value/CalcFunction.php index 2cd9f663..0958803b 100644 --- a/src/Value/CalcFunction.php +++ b/src/Value/CalcFunction.php @@ -26,7 +26,7 @@ class CalcFunction extends CSSFunction * * @internal since V8.8.0 */ - public static function parse(ParserState $parserState, bool $bIgnoreCase = false): CSSFunction + public static function parse(ParserState $parserState, bool $ignoreCase = false): CSSFunction { $aOperators = ['+', '-', '*', '/']; $sFunction = $parserState->parseIdentifier(); diff --git a/src/Value/Value.php b/src/Value/Value.php index 88b0255f..e2082d5e 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -110,7 +110,7 @@ public static function parseValue(ParserState $parserState, array $aListDelimite } /** - * @param bool $bIgnoreCase + * @param bool $ignoreCase * * @return CSSFunction|string * @@ -119,10 +119,10 @@ public static function parseValue(ParserState $parserState, array $aListDelimite * * @internal since V8.8.0 */ - public static function parseIdentifierOrFunction(ParserState $parserState, $bIgnoreCase = false) + public static function parseIdentifierOrFunction(ParserState $parserState, $ignoreCase = false) { $oAnchor = $parserState->anchor(); - $result = $parserState->parseIdentifier($bIgnoreCase); + $result = $parserState->parseIdentifier($ignoreCase); if ($parserState->comes('(')) { $oAnchor->backtrack(); @@ -135,7 +135,7 @@ public static function parseIdentifierOrFunction(ParserState $parserState, $bIgn ) { $result = CalcFunction::parse($parserState); } else { - $result = CSSFunction::parse($parserState, $bIgnoreCase); + $result = CSSFunction::parse($parserState, $ignoreCase); } } From 2be104444b6b66fb1515a1f1bb1b996d0dc67ee8 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Thu, 20 Feb 2025 08:16:26 +0000 Subject: [PATCH 209/555] [TASK] Add formal type parameter to `ParserState::setCharset()` (#958) --- config/phpstan-baseline.neon | 6 ------ src/Parsing/ParserState.php | 4 +--- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 47af1164..f783902b 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -174,12 +174,6 @@ parameters: count: 2 path: ../src/Parsing/ParserState.php - - - message: '#^Parameters should have "string" types as the only types passed to this method$#' - identifier: typePerfect.narrowPublicClassMethodParamType - count: 1 - path: ../src/Parsing/ParserState.php - - message: '#^Parameters should have "string\|int\|null" types as the only types passed to this method$#' identifier: typePerfect.narrowPublicClassMethodParamType diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 4c680842..44cceb29 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -68,10 +68,8 @@ public function __construct($text, Settings $parserSettings, $lineNumber = 1) /** * Sets the charset to be used if the CSS does not contain an `@charset` declaration. - * - * @param string $charset */ - public function setCharset($charset): void + public function setCharset(string $charset): void { $this->charset = $charset; $this->characters = $this->strsplit($this->text); From dc6014b9a6e52a35a0c6fb02e5ad28a331c9d147 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 20 Feb 2025 10:42:55 +0100 Subject: [PATCH 210/555] [TASK] Add some tests for `OutputFormatter` (part 2) (#949) Also autoformat the test class. The new PHPStan warning will go away once we switch the tested class to native type declarations. Part of #757 --- config/phpstan-baseline.neon | 2 +- tests/Unit/OutputFormatterTest.php | 330 ++++++++++++++++++++++++++++- 2 files changed, 327 insertions(+), 5 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index f783902b..4fdf7a84 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -123,7 +123,7 @@ parameters: - message: '#^Parameters should have "string" types as the only types passed to this method$#' identifier: typePerfect.narrowPublicClassMethodParamType - count: 2 + count: 3 path: ../src/OutputFormatter.php - diff --git a/tests/Unit/OutputFormatterTest.php b/tests/Unit/OutputFormatterTest.php index 94c967bd..2caf30e4 100644 --- a/tests/Unit/OutputFormatterTest.php +++ b/tests/Unit/OutputFormatterTest.php @@ -5,6 +5,8 @@ namespace Sabberworm\CSS\Tests\Unit; use PHPUnit\Framework\TestCase; +use Sabberworm\CSS\Comment\Comment; +use Sabberworm\CSS\Comment\Commentable; use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\OutputFormatter; use Sabberworm\CSS\Renderable; @@ -146,8 +148,8 @@ public function spaceBeforeListArgumentSeparatorReturnsSpaceSetForSpecificSepara /** * @test */ - public function spaceBeforeListArgumentSeparatorWithoutSpecificSettingReturnsDefaultSpace( - ): void { + public function spaceBeforeListArgumentSeparatorWithoutSpecificSettingReturnsDefaultSpace(): void + { $space = ' '; $this->outputFormat->setSpaceBeforeListArgumentSeparators([',' => $space]); $defaultSpace = "\t\t\t\t"; @@ -173,8 +175,8 @@ public function spaceAfterListArgumentSeparatorReturnsSpaceSetForSpecificSeparat /** * @test */ - public function spaceAfterListArgumentSeparatorWithoutSpecificSettingReturnsDefaultSpace( - ): void { + public function spaceAfterListArgumentSeparatorWithoutSpecificSettingReturnsDefaultSpace(): void + { $space = ' '; $this->outputFormat->setSpaceAfterListArgumentSeparators([',' => $space]); $defaultSpace = "\t\t\t\t"; @@ -297,4 +299,324 @@ public function implodeWithIncreaseLevelTrueIncreasesIndentationLevelForRenderin self::assertSame($renderedRenderable, $result); } + + /** + * @return array + */ + public function provideUnchangedStringForRemoveLastSemicolon(): array + { + return [ + 'empty string' => [''], + 'string without semicolon' => ['earl-grey: hot'], + 'string with trailing semicolon' => ['Earl Grey: hot;'], + 'string with semicolon in the middle' => ['Earl Grey: hot; Coffee: Americano'], + 'string with semicolons in the middle and trailing' => ['Earl Grey: hot; Coffee: Americano;'], + ]; + } + + /** + * @test + * @dataProvider provideUnchangedStringForRemoveLastSemicolon + */ + public function removeLastSemicolonWithSemicolonAfterLastRuleEnabledReturnsUnchangedArgument(string $string): void + { + $this->outputFormat->setSemicolonAfterLastRule(true); + + $result = $this->subject->removeLastSemicolon($string); + + self::assertSame($string, $result); + } + + /** + * @return array + */ + public function provideChangedStringForRemoveLastSemicolon(): array + { + return [ + 'empty string' => ['', ''], + 'non-empty string without semicolon' => ['Earl Grey: hot', 'Earl Grey: hot'], + 'just 1 semicolon' => [';', ''], + 'just 2 semicolons' => [';;', ';'], + 'string with trailing semicolon' => ['Earl Grey: hot;', 'Earl Grey: hot'], + 'string with semicolon in the middle' => [ + 'Earl Grey: hot; Coffee: Americano', + 'Earl Grey: hot Coffee: Americano', + ], + 'string with semicolon in the middle and trailing' => [ + 'Earl Grey: hot; Coffee: Americano;', + 'Earl Grey: hot; Coffee: Americano', + ], + 'string with 2 semicolons in the middle' => ['tea; coffee; Club-Mate', 'tea; coffee Club-Mate'], + 'string with 2 semicolons in the middle surrounded by spaces' => [ + 'Earl Grey: hot ; Coffee: Americano ; Club-Mate: cold', + 'Earl Grey: hot ; Coffee: Americano Club-Mate: cold', + ], + 'string with 2 adjacent semicolons in the middle' => [ + 'Earl Grey: hot;; Coffee: Americano', + 'Earl Grey: hot; Coffee: Americano', + ], + 'string with 3 adjacent semicolons in the middle' => [ + 'Earl Grey: hot;;; Coffee: Americano', + 'Earl Grey: hot;; Coffee: Americano', + ], + ]; + } + + /** + * @test + * @dataProvider provideChangedStringForRemoveLastSemicolon + */ + public function removeLastSemicolonWithSemicolonAfterLastRuleDisabledRemovesLastSemicolon( + string $input, + string $expected + ): void { + $this->outputFormat->setSemicolonAfterLastRule(false); + + $result = $this->subject->removeLastSemicolon($input); + + self::assertSame($expected, $result); + } + + /** + * @test + */ + public function commentsWithEmptyCommentableAndRenderCommentsDisabledDoesNotReturnSpaceBetweenBlocks(): void + { + $this->outputFormat->setRenderComments(false); + $spaceBetweenBlocks = ' between-space '; + $this->outputFormat->setSpaceBetweenBlocks($spaceBetweenBlocks); + + $commentable = $this->createMock(Commentable::class); + $commentable->method('getComments')->willReturn([]); + + $result = $this->subject->comments($commentable); + + self::assertStringNotContainsString($spaceBetweenBlocks, $result); + } + + /** + * @test + */ + public function commentsWithEmptyCommentableAndRenderCommentsDisabledDoesNotReturnSpaceAfterBlocks(): void + { + $this->outputFormat->setRenderComments(false); + $spaceAfterBlocks = ' after-space '; + $this->outputFormat->setSpaceAfterBlocks($spaceAfterBlocks); + + $commentable = $this->createMock(Commentable::class); + $commentable->method('getComments')->willReturn([]); + + $result = $this->subject->comments($commentable); + + self::assertStringNotContainsString($spaceAfterBlocks, $result); + } + + /** + * @test + */ + public function commentsWithEmptyCommentableAndRenderCommentsDisabledReturnsEmptyString(): void + { + $this->outputFormat->setRenderComments(false); + + $commentable = $this->createMock(Commentable::class); + $commentable->method('getComments')->willReturn([]); + + $result = $this->subject->comments($commentable); + + self::assertSame('', $result); + } + + /** + * @test + */ + public function commentsWithEmptyCommentableAndRenderCommentsEnabledDoesNotReturnSpaceBetweenBlocks(): void + { + $this->outputFormat->setRenderComments(true); + $spaceBetweenBlocks = ' between-space '; + $this->outputFormat->setSpaceBetweenBlocks($spaceBetweenBlocks); + + $commentable = $this->createMock(Commentable::class); + $commentable->method('getComments')->willReturn([]); + + $result = $this->subject->comments($commentable); + + self::assertStringNotContainsString($spaceBetweenBlocks, $result); + } + + /** + * @test + */ + public function commentsWithEmptyCommentableAndRenderCommentsEnabledDoesNotReturnSpaceAfterBlocks(): void + { + $this->outputFormat->setRenderComments(true); + $spaceAfterBlocks = ' after-space '; + $this->outputFormat->setSpaceAfterBlocks($spaceAfterBlocks); + + $commentable = $this->createMock(Commentable::class); + $commentable->method('getComments')->willReturn([]); + + $result = $this->subject->comments($commentable); + + self::assertStringNotContainsString($spaceAfterBlocks, $result); + } + + /** + * @test + */ + public function commentsWithEmptyCommentableAndRenderCommentsEnabledReturnsEmptyString(): void + { + $this->outputFormat->setRenderComments(true); + + $commentable = $this->createMock(Commentable::class); + $commentable->method('getComments')->willReturn([]); + + $result = $this->subject->comments($commentable); + + self::assertSame('', $result); + } + + /** + * @test + */ + public function commentsWithCommentableWithOneCommentAndRenderCommentsDisabledReturnsEmptyString(): void + { + $this->outputFormat->setRenderComments(false); + + $commentText = 'I am a teapot.'; + $comment = new Comment($commentText); + $commentable = $this->createMock(Commentable::class); + $commentable->method('getComments')->willReturn([$comment]); + + $result = $this->subject->comments($commentable); + + self::assertSame('', $result); + } + + /** + * @test + */ + public function commentsWithCommentableWithOneCommentRendersComment(): void + { + $this->outputFormat->setRenderComments(true); + + $commentText = 'I am a teapot.'; + $comment = new Comment($commentText); + $commentable = $this->createMock(Commentable::class); + $commentable->method('getComments')->willReturn([$comment]); + + $result = $this->subject->comments($commentable); + + self::assertStringContainsString('/*' . $commentText . '*/', $result); + } + + /** + * @test + */ + public function commentsWithCommentableWithOneCommentPutsSpaceAfterBlocksAfterRenderedComment(): void + { + $this->outputFormat->setRenderComments(true); + $afterSpace = ' after-space '; + $this->outputFormat->setSpaceAfterBlocks($afterSpace); + + $commentText = 'I am a teapot.'; + $comment = new Comment($commentText); + $commentable = $this->createMock(Commentable::class); + $commentable->method('getComments')->willReturn([$comment]); + + $result = $this->subject->comments($commentable); + + self::assertSame('/*' . $commentText . '*/' . $afterSpace, $result); + } + + /** + * @test + */ + public function commentsWithCommentableWithTwoCommentsPutsSpaceAfterBlocksAfterLastRenderedComment(): void + { + $this->outputFormat->setRenderComments(true); + $afterSpace = ' after-space '; + $this->outputFormat->setSpaceAfterBlocks($afterSpace); + + $commentText1 = 'I am a teapot.'; + $comment1 = new Comment($commentText1); + $commentText2 = 'But I am not.'; + $comment2 = new Comment($commentText2); + $commentable = $this->createMock(Commentable::class); + $commentable->method('getComments')->willReturn([$comment1, $comment2]); + + $result = $this->subject->comments($commentable); + + self::assertStringContainsString('/*' . $commentText2 . '*/' . $afterSpace, $result); + } + + /** + * @test + */ + public function commentsWithCommentableWithTwoCommentsSeparatesCommentsBySpaceBetweenBlocks(): void + { + $this->outputFormat->setRenderComments(true); + $betweenSpace = ' between-space '; + $this->outputFormat->setSpaceBetweenBlocks($betweenSpace); + + $commentText1 = 'I am a teapot.'; + $comment1 = new Comment($commentText1); + $commentText2 = 'But I am not.'; + $comment2 = new Comment($commentText2); + $commentable = $this->createMock(Commentable::class); + $commentable->method('getComments')->willReturn([$comment1, $comment2]); + + $result = $this->subject->comments($commentable); + + $expected = '/*' . $commentText1 . '*/' . $betweenSpace . '/*' . $commentText2 . '*/'; + self::assertStringContainsString($expected, $result); + } + + /** + * @test + */ + public function commentsWithCommentableWithMoreThanTwoCommentsPutsSpaceAfterBlocksAfterLastRenderedComment(): void + { + $this->outputFormat->setRenderComments(true); + $afterSpace = ' after-space '; + $this->outputFormat->setSpaceAfterBlocks($afterSpace); + + $commentText1 = 'I am a teapot.'; + $comment1 = new Comment($commentText1); + $commentText2 = 'But I am not.'; + $comment2 = new Comment($commentText2); + $commentText3 = 'So what am I then?'; + $comment3 = new Comment($commentText3); + $commentable = $this->createMock(Commentable::class); + $commentable->method('getComments')->willReturn([$comment1, $comment2, $comment3]); + + $result = $this->subject->comments($commentable); + + self::assertStringContainsString('/*' . $commentText3 . '*/' . $afterSpace, $result); + } + + /** + * @test + */ + public function commentsWithCommentableWithMoreThanTwoCommentsSeparatesCommentsBySpaceBetweenBlocks(): void + { + $this->outputFormat->setRenderComments(true); + $betweenSpace = ' between-space '; + $this->outputFormat->setSpaceBetweenBlocks($betweenSpace); + + $commentText1 = 'I am a teapot.'; + $comment1 = new Comment($commentText1); + $commentText2 = 'But I am not.'; + $comment2 = new Comment($commentText2); + $commentText3 = 'So what am I then?'; + $comment3 = new Comment($commentText3); + $commentable = $this->createMock(Commentable::class); + $commentable->method('getComments')->willReturn([$comment1, $comment2, $comment3]); + + $result = $this->subject->comments($commentable); + + $expected = '/*' . $commentText1 . '*/' + . $betweenSpace . '/*' . $commentText2 . '*/' + . $betweenSpace . '/*' . $commentText3 . '*/'; + self::assertStringContainsString($expected, $result); + } } From d6088b2c93a6c9c96ec92cf2c8cc8d91bfba9ea3 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Thu, 20 Feb 2025 09:55:18 +0000 Subject: [PATCH 211/555] [CLEANUP] Ensure `ParserState::strsplit()` always returns array (#962) ... by throwing an exception on failure. --- src/Parsing/ParserState.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 44cceb29..5663e221 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -68,14 +68,14 @@ public function __construct($text, Settings $parserSettings, $lineNumber = 1) /** * Sets the charset to be used if the CSS does not contain an `@charset` declaration. + * + * @throws SourceException if the charset is UTF-8 and the content has invalid byte sequences */ public function setCharset(string $charset): void { $this->charset = $charset; $this->characters = $this->strsplit($this->text); - if (\is_array($this->characters)) { - $this->length = \count($this->characters); - } + $this->length = \count($this->characters); } /** @@ -466,12 +466,18 @@ private function strtolower($sString): string * @param string $sString * * @return array + * + * @throws SourceException if the charset is UTF-8 and the string contains invalid byte sequences */ private function strsplit($sString) { if ($this->parserSettings->bMultibyteSupport) { if ($this->streql($this->charset, 'utf-8')) { - return \preg_split('//u', $sString, -1, PREG_SPLIT_NO_EMPTY); + $result = \preg_split('//u', $sString, -1, PREG_SPLIT_NO_EMPTY); + if (!\is_array($result)) { + throw new SourceException('`preg_split` failed with error ' . \preg_last_error()); + } + return $result; } else { $length = \mb_strlen($sString, $this->charset); $result = []; From c32f7aeac4e01d411b017c1244b87e509978c94b Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 20 Feb 2025 11:05:07 +0100 Subject: [PATCH 212/555] [CLEANUP] Avoid Hungarian notation for `character` (#959) Part of #756 --- src/Parsing/ParserState.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 5663e221..f0b6ca62 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -131,12 +131,12 @@ public function parseIdentifier($ignoreCase = true) if ($result === null) { throw new UnexpectedTokenException('', $this->peek(5), 'identifier', $this->lineNumber); } - $sCharacter = null; - while (!$this->isEnd() && ($sCharacter = $this->parseCharacter(true)) !== null) { - if (\preg_match('/[a-zA-Z0-9\\x{00A0}-\\x{FFFF}_-]/Sux', $sCharacter)) { - $result .= $sCharacter; + $character = null; + while (!$this->isEnd() && ($character = $this->parseCharacter(true)) !== null) { + if (\preg_match('/[a-zA-Z0-9\\x{00A0}-\\x{FFFF}_-]/Sux', $character)) { + $result .= $character; } else { - $result .= '\\' . $sCharacter; + $result .= '\\' . $character; } } if ($ignoreCase) { From 04b034540fc538886fd515e9dc0704ad59060dd5 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 20 Feb 2025 11:08:03 +0100 Subject: [PATCH 213/555] [CLEANUP] Avoid Hungarian notation for `offset` (#960) Part of #756 --- src/Parsing/ParserState.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index f0b6ca62..06b93e21 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -252,15 +252,15 @@ public function comes($sString, $bCaseInsensitive = false): bool /** * @param int $length - * @param int $iOffset + * @param int $offset */ - public function peek($length = 1, $iOffset = 0): string + public function peek($length = 1, $offset = 0): string { - $iOffset += $this->currentPosition; - if ($iOffset >= $this->length) { + $offset += $this->currentPosition; + if ($offset >= $this->length) { return ''; } - return $this->substr($iOffset, $length); + return $this->substr($offset, $length); } /** @@ -498,16 +498,16 @@ private function strsplit($sString) /** * @param string $sString * @param string $sNeedle - * @param int $iOffset + * @param int $offset * * @return int|false */ - private function strpos($sString, $sNeedle, $iOffset) + private function strpos($sString, $sNeedle, $offset) { if ($this->parserSettings->bMultibyteSupport) { - return \mb_strpos($sString, $sNeedle, $iOffset, $this->charset); + return \mb_strpos($sString, $sNeedle, $offset, $this->charset); } else { - return \strpos($sString, $sNeedle, $iOffset); + return \strpos($sString, $sNeedle, $offset); } } } From 3a05e1fefa4b09a2f813f31d367f7bd2abbab28d Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 20 Feb 2025 11:09:05 +0100 Subject: [PATCH 214/555] [CLEANUP] Avoid Hungarian notation for `isForIdentifier` (#961) Part of #756 --- src/Parsing/ParserState.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 06b93e21..c64f9262 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -146,18 +146,18 @@ public function parseIdentifier($ignoreCase = true) } /** - * @param bool $bIsForIdentifier + * @param bool $isForIdentifier * * @return string|null * * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ - public function parseCharacter($bIsForIdentifier) + public function parseCharacter($isForIdentifier) { if ($this->peek() === '\\') { if ( - $bIsForIdentifier && $this->parserSettings->bLenientParsing + $isForIdentifier && $this->parserSettings->bLenientParsing && ($this->comes('\\0') || $this->comes('\\9')) ) { // Non-strings can contain \0 or \9 which is an IE hack supported in lenient parsing. @@ -189,7 +189,7 @@ public function parseCharacter($bIsForIdentifier) } return \iconv('utf-32le', $this->charset, $sUtf32); } - if ($bIsForIdentifier) { + if ($isForIdentifier) { $peek = \ord($this->peek()); // Ranges: a-z A-Z 0-9 - _ if ( From d7d9128f0d110926b4f8f626d7f75c18c485b68e Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 20 Feb 2025 11:18:11 +0100 Subject: [PATCH 215/555] [CLEANUP] Use a per-test subject for `DocumentTest` (#963) --- tests/Unit/CSSList/DocumentTest.php | 56 ++++++++++++++--------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/tests/Unit/CSSList/DocumentTest.php b/tests/Unit/CSSList/DocumentTest.php index 58ef853d..494b4a49 100644 --- a/tests/Unit/CSSList/DocumentTest.php +++ b/tests/Unit/CSSList/DocumentTest.php @@ -15,22 +15,12 @@ */ final class DocumentTest extends TestCase { - /** - * @var Document - */ - private $subject; - - protected function setUp(): void - { - $this->subject = new Document(); - } - /** * @test */ public function implementsRenderable(): void { - self::assertInstanceOf(Renderable::class, $this->subject); + self::assertInstanceOf(Renderable::class, new Document()); } /** @@ -38,7 +28,7 @@ public function implementsRenderable(): void */ public function implementsCommentable(): void { - self::assertInstanceOf(Commentable::class, $this->subject); + self::assertInstanceOf(Commentable::class, new Document()); } /** @@ -46,7 +36,9 @@ public function implementsCommentable(): void */ public function getContentsInitiallyReturnsEmptyArray(): void { - self::assertSame([], $this->subject->getContents()); + $subject = new Document(); + + self::assertSame([], $subject->getContents()); } /** @@ -70,9 +62,11 @@ public static function contentsDataProvider(): array */ public function setContentsSetsContents(array $contents): void { - $this->subject->setContents($contents); + $subject = new Document(); + + $subject->setContents($contents); - self::assertSame($contents, $this->subject->getContents()); + self::assertSame($contents, $subject->getContents()); } /** @@ -80,12 +74,14 @@ public function setContentsSetsContents(array $contents): void */ public function setContentsReplacesContentsSetInPreviousCall(): void { + $subject = new Document(); + $contents2 = [new DeclarationBlock()]; - $this->subject->setContents([new DeclarationBlock()]); - $this->subject->setContents($contents2); + $subject->setContents([new DeclarationBlock()]); + $subject->setContents($contents2); - self::assertSame($contents2, $this->subject->getContents()); + self::assertSame($contents2, $subject->getContents()); } /** @@ -93,6 +89,8 @@ public function setContentsReplacesContentsSetInPreviousCall(): void */ public function insertContentBeforeInsertsContentBeforeSibling(): void { + $subject = new Document(); + $bogusOne = new DeclarationBlock(); $bogusOne->setSelectors('.bogus-one'); $bogusTwo = new DeclarationBlock(); @@ -104,14 +102,14 @@ public function insertContentBeforeInsertsContentBeforeSibling(): void $sibling = new DeclarationBlock(); $sibling->setSelectors('.sibling'); - $this->subject->setContents([$bogusOne, $sibling, $bogusTwo]); + $subject->setContents([$bogusOne, $sibling, $bogusTwo]); - self::assertCount(3, $this->subject->getContents()); + self::assertCount(3, $subject->getContents()); - $this->subject->insertBefore($item, $sibling); + $subject->insertBefore($item, $sibling); - self::assertCount(4, $this->subject->getContents()); - self::assertSame([$bogusOne, $item, $sibling, $bogusTwo], $this->subject->getContents()); + self::assertCount(4, $subject->getContents()); + self::assertSame([$bogusOne, $item, $sibling, $bogusTwo], $subject->getContents()); } /** @@ -119,6 +117,8 @@ public function insertContentBeforeInsertsContentBeforeSibling(): void */ public function insertContentBeforeAppendsIfSiblingNotFound(): void { + $subject = new Document(); + $bogusOne = new DeclarationBlock(); $bogusOne->setSelectors('.bogus-one'); $bogusTwo = new DeclarationBlock(); @@ -133,13 +133,13 @@ public function insertContentBeforeAppendsIfSiblingNotFound(): void $orphan = new DeclarationBlock(); $orphan->setSelectors('.forever-alone'); - $this->subject->setContents([$bogusOne, $sibling, $bogusTwo]); + $subject->setContents([$bogusOne, $sibling, $bogusTwo]); - self::assertCount(3, $this->subject->getContents()); + self::assertCount(3, $subject->getContents()); - $this->subject->insertBefore($item, $orphan); + $subject->insertBefore($item, $orphan); - self::assertCount(4, $this->subject->getContents()); - self::assertSame([$bogusOne, $sibling, $bogusTwo, $item], $this->subject->getContents()); + self::assertCount(4, $subject->getContents()); + self::assertSame([$bogusOne, $sibling, $bogusTwo, $item], $subject->getContents()); } } From ee3d57c7e5a4c1763a878180e18e4b57008d7c71 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 20 Feb 2025 11:35:54 +0100 Subject: [PATCH 216/555] [TASK] Use native type declarations in `OutputFormatter` (#964) Also improve a related type annotation. Part of #811. --- CHANGELOG.md | 3 ++- config/phpstan-baseline.neon | 6 ------ src/OutputFormat.php | 1 + src/OutputFormatter.php | 39 ++++++++---------------------------- 4 files changed, 11 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44a3697a..b7c980da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,8 @@ Please also have a look at our - Only allow `string` for some `OutputFormat` properties (#885) - Make all non-private properties `@internal` (#886) - Use more native type declarations and strict mode - (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933) + (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933, #958, + #964) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 4fdf7a84..4150a6e8 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -120,12 +120,6 @@ parameters: count: 4 path: ../src/OutputFormat.php - - - message: '#^Parameters should have "string" types as the only types passed to this method$#' - identifier: typePerfect.narrowPublicClassMethodParamType - count: 3 - path: ../src/OutputFormatter.php - - message: '#^Default value of the parameter \#2 \$bIncludeEnd \(false\) of method Sabberworm\\CSS\\Parsing\\ParserState\:\:consumeUntil\(\) is incompatible with type string\.$#' identifier: parameter.defaultValue diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 4498b34b..86b8b640 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -284,6 +284,7 @@ public function set($aNames, $mValue) } /** + * @param non-empty-string $sMethodName * @param array $aArguments * * @return mixed diff --git a/src/OutputFormatter.php b/src/OutputFormatter.php index c414e9e9..9f815d6f 100644 --- a/src/OutputFormatter.php +++ b/src/OutputFormatter.php @@ -19,11 +19,7 @@ public function __construct(OutputFormat $outputFormat) $this->outputFormat = $outputFormat; } - /** - * @param string $sName - * @param string|null $sType - */ - public function space($sName, $sType = null): string + public function space(string $sName, ?string $sType = null): string { $sSpaceString = $this->outputFormat->get("Space$sName"); // If $sSpaceString is an array, we have multiple values configured @@ -78,9 +74,6 @@ public function spaceBeforeSelectorSeparator(): string return $this->space('BeforeSelectorSeparator'); } - /** - * @return string - */ public function spaceAfterSelectorSeparator(): string { return $this->space('AfterSelectorSeparator'); @@ -89,7 +82,7 @@ public function spaceAfterSelectorSeparator(): string /** * @param non-empty-string $sSeparator */ - public function spaceBeforeListArgumentSeparator($sSeparator): string + public function spaceBeforeListArgumentSeparator(string $sSeparator): string { $spaceForSeparator = $this->outputFormat->getSpaceBeforeListArgumentSeparators(); @@ -99,7 +92,7 @@ public function spaceBeforeListArgumentSeparator($sSeparator): string /** * @param non-empty-string $sSeparator */ - public function spaceAfterListArgumentSeparator($sSeparator): string + public function spaceAfterListArgumentSeparator(string $sSeparator): string { $spaceForSeparator = $this->outputFormat->getSpaceAfterListArgumentSeparators(); @@ -112,13 +105,9 @@ public function spaceBeforeOpeningBrace(): string } /** - * Runs the given code, either swallowing or passing exceptions, depending on the `bIgnoreExceptions` setting. - * - * @param string $cCode the name of the function to call - * - * @return string|null + * Runs the given code, either swallowing or passing exceptions, depending on the `ignoreExceptions` setting. */ - public function safely($cCode) + public function safely(callable $cCode): ?string { if ($this->outputFormat->get('IgnoreExceptions')) { // If output exceptions are ignored, run the code with exception guards @@ -137,9 +126,8 @@ public function safely($cCode) * Clone of the `implode` function, but calls `render` with the current output format instead of `__toString()`. * * @param array $aValues - * @param bool $bIncreaseLevel */ - public function implode(string $sSeparator, array $aValues, $bIncreaseLevel = false): string + public function implode(string $sSeparator, array $aValues, bool $bIncreaseLevel = false): string { $result = ''; $outputFormat = $this->outputFormat; @@ -162,12 +150,7 @@ public function implode(string $sSeparator, array $aValues, $bIncreaseLevel = fa return $result; } - /** - * @param string $sString - * - * @return string - */ - public function removeLastSemicolon($sString) + public function removeLastSemicolon(string $sString): string { if ($this->outputFormat->get('SemicolonAfterLastRule')) { return $sString; @@ -199,17 +182,11 @@ public function comments(Commentable $oCommentable): string return $result; } - /** - * @param string $sSpaceString - */ - private function prepareSpace($sSpaceString): string + private function prepareSpace(string $sSpaceString): string { return \str_replace("\n", "\n" . $this->indent(), $sSpaceString); } - /** - * @return string - */ private function indent(): string { return \str_repeat($this->outputFormat->sIndentation, $this->outputFormat->getIndentationLevel()); From 132374bb030eca3fd965869a4809a5b6c4351208 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 20 Feb 2025 12:50:08 +0100 Subject: [PATCH 217/555] [CLEANUP] Avoid Hungarian notation in `parseCharacter()` (#965) Part of #756 --- src/Parsing/ParserState.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index c64f9262..1dd93b25 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -170,8 +170,8 @@ public function parseCharacter($isForIdentifier) if (\preg_match('/[0-9a-fA-F]/Su', $this->peek()) === 0) { return $this->consume(1); } - $sUnicode = $this->consumeExpression('/^[0-9a-fA-F]{1,6}/u', 6); - if ($this->strlen($sUnicode) < 6) { + $hexCodePoint = $this->consumeExpression('/^[0-9a-fA-F]{1,6}/u', 6); + if ($this->strlen($hexCodePoint) < 6) { // Consume whitespace after incomplete unicode escape if (\preg_match('/\\s/isSu', $this->peek())) { if ($this->comes('\\r\\n')) { @@ -181,13 +181,13 @@ public function parseCharacter($isForIdentifier) } } } - $iUnicode = \intval($sUnicode, 16); - $sUtf32 = ''; + $codePoint = \intval($hexCodePoint, 16); + $utf32EncodedCharacter = ''; for ($i = 0; $i < 4; ++$i) { - $sUtf32 .= \chr($iUnicode & 0xff); - $iUnicode = $iUnicode >> 8; + $utf32EncodedCharacter .= \chr($codePoint & 0xff); + $codePoint = $codePoint >> 8; } - return \iconv('utf-32le', $this->charset, $sUtf32); + return \iconv('utf-32le', $this->charset, $utf32EncodedCharacter); } if ($isForIdentifier) { $peek = \ord($this->peek()); From f28fc1f5b0f7f75e6d76f338ccf311a9731d0fd7 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 20 Feb 2025 13:19:47 +0100 Subject: [PATCH 218/555] [TASK] Use native type declarations in `Commentable` (#967) Part of #811 --- CHANGELOG.md | 2 +- src/CSSList/CSSList.php | 2 +- src/Comment/Commentable.php | 2 +- src/Property/CSSNamespace.php | 2 +- src/Property/Charset.php | 2 +- src/Property/Import.php | 2 +- src/Rule/Rule.php | 2 +- src/RuleSet/RuleSet.php | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7c980da..1aea5b3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,7 @@ Please also have a look at our - Make all non-private properties `@internal` (#886) - Use more native type declarations and strict mode (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933, #958, - #964) + #964, #967) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index e7acc842..bc4f43df 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -470,7 +470,7 @@ public function addComments(array $comments): void /** * @return array */ - public function getComments() + public function getComments(): array { return $this->comments; } diff --git a/src/Comment/Commentable.php b/src/Comment/Commentable.php index 0cb3df24..5e0fba97 100644 --- a/src/Comment/Commentable.php +++ b/src/Comment/Commentable.php @@ -14,7 +14,7 @@ public function addComments(array $comments): void; /** * @return array */ - public function getComments(); + public function getComments(): array; /** * @param array $comments diff --git a/src/Property/CSSNamespace.php b/src/Property/CSSNamespace.php index 81f24e34..21135a35 100644 --- a/src/Property/CSSNamespace.php +++ b/src/Property/CSSNamespace.php @@ -128,7 +128,7 @@ public function addComments(array $comments): void /** * @return array */ - public function getComments() + public function getComments(): array { return $this->comments; } diff --git a/src/Property/Charset.php b/src/Property/Charset.php index 20522739..ac063f28 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -105,7 +105,7 @@ public function addComments(array $comments): void /** * @return array */ - public function getComments() + public function getComments(): array { return $this->comments; } diff --git a/src/Property/Import.php b/src/Property/Import.php index bc97d949..8240e28f 100644 --- a/src/Property/Import.php +++ b/src/Property/Import.php @@ -111,7 +111,7 @@ public function addComments(array $comments): void /** * @return array */ - public function getComments() + public function getComments(): array { return $this->comments; } diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index a2cbbb86..f4803b77 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -294,7 +294,7 @@ public function addComments(array $comments): void /** * @return array */ - public function getComments() + public function getComments(): array { return $this->comments; } diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 8e9cf1aa..b5306c39 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -305,7 +305,7 @@ public function addComments(array $comments): void /** * @return array */ - public function getComments() + public function getComments(): array { return $this->comments; } From d607453a2c8c47f051de03d00073b213ac10453c Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 20 Feb 2025 19:33:15 +0100 Subject: [PATCH 219/555] [CLEANUP] Use accessors in `OutputFormatter` (#966) Avoid direct access to `OutputFormat` properties as well as variable access and magic methods. Also drop some dead code. We might possibly want to drop the `space` method altogether, but that's out of scope for this change. --- src/OutputFormatter.php | 68 +++++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/src/OutputFormatter.php b/src/OutputFormatter.php index 9f815d6f..dc3e9b57 100644 --- a/src/OutputFormatter.php +++ b/src/OutputFormatter.php @@ -19,18 +19,54 @@ public function __construct(OutputFormat $outputFormat) $this->outputFormat = $outputFormat; } - public function space(string $sName, ?string $sType = null): string - { - $sSpaceString = $this->outputFormat->get("Space$sName"); - // If $sSpaceString is an array, we have multiple values configured - // depending on the type of object the space applies to - if (\is_array($sSpaceString)) { - if ($sType !== null && isset($sSpaceString[$sType])) { - $sSpaceString = $sSpaceString[$sType]; - } else { - $sSpaceString = \reset($sSpaceString); - } + /** + * @param non-empty-string $sName + * + * @throws \InvalidArgumentException + */ + public function space(string $sName): string + { + switch ($sName) { + case 'AfterRuleName': + $sSpaceString = $this->outputFormat->getSpaceAfterRuleName(); + break; + case 'BeforeRules': + $sSpaceString = $this->outputFormat->getSpaceBeforeRules(); + break; + case 'AfterRules': + $sSpaceString = $this->outputFormat->getSpaceAfterRules(); + break; + case 'BetweenRules': + $sSpaceString = $this->outputFormat->getSpaceBetweenRules(); + break; + case 'BeforeBlocks': + $sSpaceString = $this->outputFormat->getSpaceBeforeBlocks(); + break; + case 'AfterBlocks': + $sSpaceString = $this->outputFormat->getSpaceAfterBlocks(); + break; + case 'BetweenBlocks': + $sSpaceString = $this->outputFormat->getSpaceBetweenBlocks(); + break; + case 'BeforeSelectorSeparator': + $sSpaceString = $this->outputFormat->getSpaceBeforeSelectorSeparator(); + break; + case 'AfterSelectorSeparator': + $sSpaceString = $this->outputFormat->getSpaceAfterSelectorSeparator(); + break; + case 'BeforeOpeningBrace': + $sSpaceString = $this->outputFormat->getSpaceBeforeOpeningBrace(); + break; + case 'BeforeListArgumentSeparator': + $sSpaceString = $this->outputFormat->getSpaceBeforeListArgumentSeparator(); + break; + case 'AfterListArgumentSeparator': + $sSpaceString = $this->outputFormat->getSpaceAfterListArgumentSeparator(); + break; + default: + throw new \InvalidArgumentException("Unknown space type: $sName", 1740049248); } + return $this->prepareSpace($sSpaceString); } @@ -86,7 +122,7 @@ public function spaceBeforeListArgumentSeparator(string $sSeparator): string { $spaceForSeparator = $this->outputFormat->getSpaceBeforeListArgumentSeparators(); - return $spaceForSeparator[$sSeparator] ?? $this->space('BeforeListArgumentSeparator', $sSeparator); + return $spaceForSeparator[$sSeparator] ?? $this->space('BeforeListArgumentSeparator'); } /** @@ -96,7 +132,7 @@ public function spaceAfterListArgumentSeparator(string $sSeparator): string { $spaceForSeparator = $this->outputFormat->getSpaceAfterListArgumentSeparators(); - return $spaceForSeparator[$sSeparator] ?? $this->space('AfterListArgumentSeparator', $sSeparator); + return $spaceForSeparator[$sSeparator] ?? $this->space('AfterListArgumentSeparator'); } public function spaceBeforeOpeningBrace(): string @@ -109,7 +145,7 @@ public function spaceBeforeOpeningBrace(): string */ public function safely(callable $cCode): ?string { - if ($this->outputFormat->get('IgnoreExceptions')) { + if ($this->outputFormat->getIgnoreExceptions()) { // If output exceptions are ignored, run the code with exception guards try { return $cCode(); @@ -152,7 +188,7 @@ public function implode(string $sSeparator, array $aValues, bool $bIncreaseLevel public function removeLastSemicolon(string $sString): string { - if ($this->outputFormat->get('SemicolonAfterLastRule')) { + if ($this->outputFormat->getSemicolonAfterLastRule()) { return $sString; } $sString = \explode(';', $sString); @@ -167,7 +203,7 @@ public function removeLastSemicolon(string $sString): string public function comments(Commentable $oCommentable): string { - if (!$this->outputFormat->bRenderComments) { + if (!$this->outputFormat->getRenderComments()) { return ''; } From 9736e265df732a2406b644b0aca5411fdd9d8985 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 20 Feb 2025 19:34:51 +0100 Subject: [PATCH 220/555] [TASK] Add Tests for `Document::getAllDeclarationBlocks()` (#956) Also remove a misleading and incorrect comment. Part of #757 --- src/CSSList/Document.php | 1 - tests/Unit/CSSList/DocumentTest.php | 95 +++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/src/CSSList/Document.php b/src/CSSList/Document.php index c79ae9a2..00d4cabd 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -32,7 +32,6 @@ public static function parse(ParserState $parserState): Document /** * Gets all `DeclarationBlock` objects recursively, no matter how deeply nested the selectors are. - * Aliased as `getAllSelectors()`. * * @return array */ diff --git a/tests/Unit/CSSList/DocumentTest.php b/tests/Unit/CSSList/DocumentTest.php index 494b4a49..7b6a3df9 100644 --- a/tests/Unit/CSSList/DocumentTest.php +++ b/tests/Unit/CSSList/DocumentTest.php @@ -6,11 +6,18 @@ use PHPUnit\Framework\TestCase; use Sabberworm\CSS\Comment\Commentable; +use Sabberworm\CSS\CSSList\AtRuleBlockList; use Sabberworm\CSS\CSSList\Document; +use Sabberworm\CSS\Property\Charset; +use Sabberworm\CSS\Property\Import; use Sabberworm\CSS\Renderable; use Sabberworm\CSS\RuleSet\DeclarationBlock; +use Sabberworm\CSS\Value\CSSString; +use Sabberworm\CSS\Value\URL; /** + * @covers \Sabberworm\CSS\CSSList\CSSBlockList + * @covers \Sabberworm\CSS\CSSList\CSSList * @covers \Sabberworm\CSS\CSSList\Document */ final class DocumentTest extends TestCase @@ -142,4 +149,92 @@ public function insertContentBeforeAppendsIfSiblingNotFound(): void self::assertCount(4, $subject->getContents()); self::assertSame([$bogusOne, $sibling, $bogusTwo, $item], $subject->getContents()); } + + /** + * @test + */ + public function getAllDeclarationBlocksForNoContentsReturnsEmptyArray(): void + { + $subject = new Document(); + + self::assertSame([], $subject->getAllDeclarationBlocks()); + } + + /** + * @test + */ + public function getAllDeclarationBlocksCanReturnOneDirectDeclarationBlockContent(): void + { + $subject = new Document(); + + $declarationBlock = new DeclarationBlock(); + $subject->setContents([$declarationBlock]); + + $result = $subject->getAllDeclarationBlocks(); + + self::assertSame([$declarationBlock], $result); + } + + /** + * @test + */ + public function getAllDeclarationBlocksCanReturnMultipleDirectDeclarationBlockContents(): void + { + $subject = new Document(); + + $declarationBlock1 = new DeclarationBlock(); + $declarationBlock2 = new DeclarationBlock(); + $subject->setContents([$declarationBlock1, $declarationBlock2]); + + $result = $subject->getAllDeclarationBlocks(); + + self::assertSame([$declarationBlock1, $declarationBlock2], $result); + } + + /** + * @test + */ + public function getAllDeclarationBlocksReturnsDeclarationBlocksWithinAtRuleBlockList(): void + { + $subject = new Document(); + + $declarationBlock = new DeclarationBlock(); + $atRuleBlockList = new AtRuleBlockList('media'); + $atRuleBlockList->setContents([$declarationBlock]); + $subject->setContents([$atRuleBlockList]); + + $result = $subject->getAllDeclarationBlocks(); + + self::assertSame([$declarationBlock], $result); + } + + /** + * @test + */ + public function getAllDeclarationBlocksIgnoresImport(): void + { + $subject = new Document(); + + $import = new Import(new URL(new CSSString('https://www.example.com/')), ''); + $subject->setContents([$import]); + + $result = $subject->getAllDeclarationBlocks(); + + self::assertSame([], $result); + } + + /** + * @test + */ + public function getAllDeclarationBlocksIgnoresCharset(): void + { + $subject = new Document(); + + $charset = new Charset(new CSSString('UTF-8')); + $subject->setContents([$charset]); + + $result = $subject->getAllDeclarationBlocks(); + + self::assertSame([], $result); + } } From 5af88d7fe4277e03a693b5ab257c2fd6ef3aea73 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 20 Feb 2025 19:36:30 +0100 Subject: [PATCH 221/555] [CLEANUP] Avoid Hungarian notation for `comment(able)` (#968) Part of #756 --- src/OutputFormatter.php | 8 ++++---- src/Parsing/ParserState.php | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/OutputFormatter.php b/src/OutputFormatter.php index dc3e9b57..f3a8a6b2 100644 --- a/src/OutputFormatter.php +++ b/src/OutputFormatter.php @@ -201,18 +201,18 @@ public function removeLastSemicolon(string $sString): string return \implode(';', $sString); } - public function comments(Commentable $oCommentable): string + public function comments(Commentable $commentable): string { if (!$this->outputFormat->getRenderComments()) { return ''; } $result = ''; - $comments = $oCommentable->getComments(); + $comments = $commentable->getComments(); $iLastCommentIndex = \count($comments) - 1; - foreach ($comments as $i => $oComment) { - $result .= $oComment->render($this->outputFormat); + foreach ($comments as $i => $comment) { + $result .= $comment->render($this->outputFormat); $result .= $i === $iLastCommentIndex ? $this->spaceAfterBlocks() : $this->spaceBetweenBlocks(); } return $result; diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 1dd93b25..c18258f4 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -223,18 +223,18 @@ public function consumeWhiteSpace(): array } if ($this->parserSettings->bLenientParsing) { try { - $oComment = $this->consumeComment(); + $comment = $this->consumeComment(); } catch (UnexpectedEOFException $e) { $this->currentPosition = $this->length; return $comments; } } else { - $oComment = $this->consumeComment(); + $comment = $this->consumeComment(); } - if ($oComment !== false) { - $comments[] = $oComment; + if ($comment !== false) { + $comments[] = $comment; } - } while ($oComment !== false); + } while ($comment !== false); return $comments; } From 981464cf3cc47f6802bc64a757a78f8d9a3d78e7 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Thu, 20 Feb 2025 18:39:20 +0000 Subject: [PATCH 222/555] [CLEANUP] Don't store array length as a property (#954) Since PHP 5, if not earlier, the length of an array is internally stored by PHP and can be accessed in O(1) time. Maintaining both the array and its length in class properties is error-prone (particularly regarding possible future code changes). When used in a loop, `\count($array)` is more repetitively expensive than `$arrayLength`, but the latter can be set up as a local variable as and when needed. Reference: https://stackoverflow.com/questions/5835241/is-phps-count-function-o1-or-on-for-arrays Resolves #953. --- src/Parsing/ParserState.php | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index c18258f4..f230395a 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -44,11 +44,6 @@ class ParserState */ private $charset; - /** - * @var int - */ - private $length; - /** * @var int */ @@ -75,7 +70,6 @@ public function setCharset(string $charset): void { $this->charset = $charset; $this->characters = $this->strsplit($this->text); - $this->length = \count($this->characters); } /** @@ -225,7 +219,7 @@ public function consumeWhiteSpace(): array try { $comment = $this->consumeComment(); } catch (UnexpectedEOFException $e) { - $this->currentPosition = $this->length; + $this->currentPosition = \count($this->characters); return $comments; } } else { @@ -257,7 +251,7 @@ public function comes($sString, $bCaseInsensitive = false): bool public function peek($length = 1, $offset = 0): string { $offset += $this->currentPosition; - if ($offset >= $this->length) { + if ($offset >= \count($this->characters)) { return ''; } return $this->substr($offset, $length); @@ -286,7 +280,7 @@ public function consume($mValue = 1): string $this->currentPosition += $this->strlen($mValue); return $mValue; } else { - if ($this->currentPosition + $mValue > $this->length) { + if ($this->currentPosition + $mValue > \count($this->characters)) { throw new UnexpectedEOFException((string) $mValue, $this->peek(5), 'count', $this->lineNumber); } $result = $this->substr($this->currentPosition, $mValue); @@ -343,7 +337,7 @@ public function consumeComment() public function isEnd(): bool { - return $this->currentPosition >= $this->length; + return $this->currentPosition >= \count($this->characters); } /** @@ -436,10 +430,10 @@ public function strlen($sString): int private function substr($iStart, $length): string { if ($length < 0) { - $length = $this->length - $iStart + $length; + $length = \count($this->characters) - $iStart + $length; } - if ($iStart + $length > $this->length) { - $length = $this->length - $iStart; + if ($iStart + $length > \count($this->characters)) { + $length = \count($this->characters) - $iStart; } $result = ''; while ($length > 0) { From 2f0e48fab29a760fdb0fe29409b7c41cd67b085c Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 20 Feb 2025 23:15:41 +0100 Subject: [PATCH 223/555] [CLEANUP] Avoid Hungarian notation in `OutputFormatter` (#969) Part of #756 --- src/OutputFormatter.php | 88 ++++++++++++++++++++--------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/src/OutputFormatter.php b/src/OutputFormatter.php index f3a8a6b2..6fcfc2d8 100644 --- a/src/OutputFormatter.php +++ b/src/OutputFormatter.php @@ -20,54 +20,54 @@ public function __construct(OutputFormat $outputFormat) } /** - * @param non-empty-string $sName + * @param non-empty-string $name * * @throws \InvalidArgumentException */ - public function space(string $sName): string + public function space(string $name): string { - switch ($sName) { + switch ($name) { case 'AfterRuleName': - $sSpaceString = $this->outputFormat->getSpaceAfterRuleName(); + $spaceString = $this->outputFormat->getSpaceAfterRuleName(); break; case 'BeforeRules': - $sSpaceString = $this->outputFormat->getSpaceBeforeRules(); + $spaceString = $this->outputFormat->getSpaceBeforeRules(); break; case 'AfterRules': - $sSpaceString = $this->outputFormat->getSpaceAfterRules(); + $spaceString = $this->outputFormat->getSpaceAfterRules(); break; case 'BetweenRules': - $sSpaceString = $this->outputFormat->getSpaceBetweenRules(); + $spaceString = $this->outputFormat->getSpaceBetweenRules(); break; case 'BeforeBlocks': - $sSpaceString = $this->outputFormat->getSpaceBeforeBlocks(); + $spaceString = $this->outputFormat->getSpaceBeforeBlocks(); break; case 'AfterBlocks': - $sSpaceString = $this->outputFormat->getSpaceAfterBlocks(); + $spaceString = $this->outputFormat->getSpaceAfterBlocks(); break; case 'BetweenBlocks': - $sSpaceString = $this->outputFormat->getSpaceBetweenBlocks(); + $spaceString = $this->outputFormat->getSpaceBetweenBlocks(); break; case 'BeforeSelectorSeparator': - $sSpaceString = $this->outputFormat->getSpaceBeforeSelectorSeparator(); + $spaceString = $this->outputFormat->getSpaceBeforeSelectorSeparator(); break; case 'AfterSelectorSeparator': - $sSpaceString = $this->outputFormat->getSpaceAfterSelectorSeparator(); + $spaceString = $this->outputFormat->getSpaceAfterSelectorSeparator(); break; case 'BeforeOpeningBrace': - $sSpaceString = $this->outputFormat->getSpaceBeforeOpeningBrace(); + $spaceString = $this->outputFormat->getSpaceBeforeOpeningBrace(); break; case 'BeforeListArgumentSeparator': - $sSpaceString = $this->outputFormat->getSpaceBeforeListArgumentSeparator(); + $spaceString = $this->outputFormat->getSpaceBeforeListArgumentSeparator(); break; case 'AfterListArgumentSeparator': - $sSpaceString = $this->outputFormat->getSpaceAfterListArgumentSeparator(); + $spaceString = $this->outputFormat->getSpaceAfterListArgumentSeparator(); break; default: - throw new \InvalidArgumentException("Unknown space type: $sName", 1740049248); + throw new \InvalidArgumentException("Unknown space type: $name", 1740049248); } - return $this->prepareSpace($sSpaceString); + return $this->prepareSpace($spaceString); } public function spaceAfterRuleName(): string @@ -143,62 +143,62 @@ public function spaceBeforeOpeningBrace(): string /** * Runs the given code, either swallowing or passing exceptions, depending on the `ignoreExceptions` setting. */ - public function safely(callable $cCode): ?string + public function safely(callable $callable): ?string { if ($this->outputFormat->getIgnoreExceptions()) { // If output exceptions are ignored, run the code with exception guards try { - return $cCode(); + return $callable(); } catch (OutputException $e) { return null; } // Do nothing } else { // Run the code as-is - return $cCode(); + return $callable(); } } /** * Clone of the `implode` function, but calls `render` with the current output format instead of `__toString()`. * - * @param array $aValues + * @param array $values */ - public function implode(string $sSeparator, array $aValues, bool $bIncreaseLevel = false): string + public function implode(string $separator, array $values, bool $increaseLevel = false): string { $result = ''; $outputFormat = $this->outputFormat; - if ($bIncreaseLevel) { + if ($increaseLevel) { $outputFormat = $outputFormat->nextLevel(); } - $bIsFirst = true; - foreach ($aValues as $mValue) { - if ($bIsFirst) { - $bIsFirst = false; + $isFirst = true; + foreach ($values as $value) { + if ($isFirst) { + $isFirst = false; } else { - $result .= $sSeparator; + $result .= $separator; } - if ($mValue instanceof Renderable) { - $result .= $mValue->render($outputFormat); + if ($value instanceof Renderable) { + $result .= $value->render($outputFormat); } else { - $result .= $mValue; + $result .= $value; } } return $result; } - public function removeLastSemicolon(string $sString): string + public function removeLastSemicolon(string $string): string { if ($this->outputFormat->getSemicolonAfterLastRule()) { - return $sString; + return $string; } - $sString = \explode(';', $sString); - if (\count($sString) < 2) { - return $sString[0]; + $parts = \explode(';', $string); + if (\count($parts) < 2) { + return $parts[0]; } - $sLast = \array_pop($sString); - $sNextToLast = \array_pop($sString); - \array_push($sString, $sNextToLast . $sLast); - return \implode(';', $sString); + $sLast = \array_pop($parts); + $sNextToLast = \array_pop($parts); + \array_push($parts, $sNextToLast . $sLast); + return \implode(';', $parts); } public function comments(Commentable $commentable): string @@ -209,18 +209,18 @@ public function comments(Commentable $commentable): string $result = ''; $comments = $commentable->getComments(); - $iLastCommentIndex = \count($comments) - 1; + $lastCommentIndex = \count($comments) - 1; foreach ($comments as $i => $comment) { $result .= $comment->render($this->outputFormat); - $result .= $i === $iLastCommentIndex ? $this->spaceAfterBlocks() : $this->spaceBetweenBlocks(); + $result .= $i === $lastCommentIndex ? $this->spaceAfterBlocks() : $this->spaceBetweenBlocks(); } return $result; } - private function prepareSpace(string $sSpaceString): string + private function prepareSpace(string $spaceString): string { - return \str_replace("\n", "\n" . $this->indent(), $sSpaceString); + return \str_replace("\n", "\n" . $this->indent(), $spaceString); } private function indent(): string From f7532ed202e126f265cdc11a05cec8785c05d659 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 21 Feb 2025 00:20:09 +0100 Subject: [PATCH 224/555] [TASK] Add one more unit test for `Document` (#944) Part of #757 --- tests/Unit/CSSList/DocumentTest.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/Unit/CSSList/DocumentTest.php b/tests/Unit/CSSList/DocumentTest.php index 7b6a3df9..baec691b 100644 --- a/tests/Unit/CSSList/DocumentTest.php +++ b/tests/Unit/CSSList/DocumentTest.php @@ -237,4 +237,14 @@ public function getAllDeclarationBlocksIgnoresCharset(): void self::assertSame([], $result); } + + /** + * @test + */ + public function isRootListAlwaysReturnsTrue(): void + { + $subject = new Document(); + + self::assertTrue($subject->isRootList()); + } } From 6d3844e99e95ab163dfd73ab1eb7d6e5ffa1257f Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 21 Feb 2025 00:21:13 +0100 Subject: [PATCH 225/555] [CLEANUP] Avoid Hungarian notation for `caseInsensitive` (#970) Part of #756 --- src/Parsing/ParserState.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index f230395a..5a91d662 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -234,14 +234,14 @@ public function consumeWhiteSpace(): array /** * @param string $sString - * @param bool $bCaseInsensitive + * @param bool $caseInsensitive */ - public function comes($sString, $bCaseInsensitive = false): bool + public function comes($sString, $caseInsensitive = false): bool { $sPeek = $this->peek(\strlen($sString)); return ($sPeek == '') ? false - : $this->streql($sPeek, $sString, $bCaseInsensitive); + : $this->streql($sPeek, $sString, $caseInsensitive); } /** @@ -392,11 +392,11 @@ private function inputLeft(): string /** * @param string $sString1 * @param string $sString2 - * @param bool $bCaseInsensitive + * @param bool $caseInsensitive */ - public function streql($sString1, $sString2, $bCaseInsensitive = true): bool + public function streql($sString1, $sString2, $caseInsensitive = true): bool { - if ($bCaseInsensitive) { + if ($caseInsensitive) { return $this->strtolower($sString1) === $this->strtolower($sString2); } else { return $sString1 === $sString2; From ce0a04900dc5a7b1ecb62608345d0c31ca3e80d3 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 21 Feb 2025 00:30:17 +0100 Subject: [PATCH 226/555] [TASK] Extend and streamline `KeyFrameTest` (#971) - use a per-test subject similar to #963 - add a missing `@covers` annotation for the parent class - add tests for the line number Part of #757 --- tests/Unit/CSSList/KeyFrameTest.php | 39 +++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/tests/Unit/CSSList/KeyFrameTest.php b/tests/Unit/CSSList/KeyFrameTest.php index 7e9ebd8a..89f96953 100644 --- a/tests/Unit/CSSList/KeyFrameTest.php +++ b/tests/Unit/CSSList/KeyFrameTest.php @@ -11,41 +11,60 @@ use Sabberworm\CSS\Renderable; /** + * @covers \Sabberworm\CSS\CSSList\CSSList * @covers \Sabberworm\CSS\CSSList\KeyFrame */ final class KeyFrameTest extends TestCase { /** - * @var KeyFrame + * @test */ - private $subject; + public function implementsAtRule(): void + { + $subject = new KeyFrame(); + + self::assertInstanceOf(AtRule::class, $subject); + } - protected function setUp(): void + /** + * @test + */ + public function implementsRenderable(): void { - $this->subject = new KeyFrame(); + $subject = new KeyFrame(); + + self::assertInstanceOf(Renderable::class, $subject); } /** * @test */ - public function implementsAtRule(): void + public function implementsCommentable(): void { - self::assertInstanceOf(AtRule::class, $this->subject); + $subject = new KeyFrame(); + + self::assertInstanceOf(Commentable::class, $subject); } /** * @test */ - public function implementsRenderable(): void + public function getLineNoByDefaultReturnsZero(): void { - self::assertInstanceOf(Renderable::class, $this->subject); + $subject = new KeyFrame(); + + self::assertSame(0, $subject->getLineNo()); } /** * @test */ - public function implementsCommentable(): void + public function getLineNoReturnsLineNumberProvidedToConstructor(): void { - self::assertInstanceOf(Commentable::class, $this->subject); + $lineNumber = 42; + + $subject = new KeyFrame($lineNumber); + + self::assertSame($lineNumber, $subject->getLineNo()); } } From 6f1fc2c90b1c2444b6e9ae48dade7795a05dbb00 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 24 Feb 2025 11:31:19 +0100 Subject: [PATCH 227/555] [CLEANUP] Avoid Hungarian notation for `string*` (#972) Part of #756 --- src/OutputFormat.php | 6 ++-- src/Parsing/ParserState.php | 56 ++++++++++++++++---------------- src/RuleSet/DeclarationBlock.php | 12 +++---- src/Value/CSSString.php | 22 ++++++------- 4 files changed, 48 insertions(+), 48 deletions(-) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 86b8b640..caec89a5 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -13,7 +13,7 @@ class OutputFormat * * @internal since 8.8.0, will be made private in 9.0.0 */ - public $sStringQuotingType = '"'; + public $stringQuotingType = '"'; /** * Output RGB colors in hash notation if possible @@ -305,7 +305,7 @@ public function __call(string $sMethodName, array $aArguments) */ public function getStringQuotingType(): string { - return $this->sStringQuotingType; + return $this->stringQuotingType; } /** @@ -313,7 +313,7 @@ public function getStringQuotingType(): string */ public function setStringQuotingType(string $quotingType): self { - $this->sStringQuotingType = $quotingType; + $this->stringQuotingType = $quotingType; return $this; } diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 5a91d662..35e495cb 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -233,15 +233,15 @@ public function consumeWhiteSpace(): array } /** - * @param string $sString + * @param string $string * @param bool $caseInsensitive */ - public function comes($sString, $caseInsensitive = false): bool + public function comes($string, $caseInsensitive = false): bool { - $sPeek = $this->peek(\strlen($sString)); + $sPeek = $this->peek(\strlen($string)); return ($sPeek == '') ? false - : $this->streql($sPeek, $sString, $caseInsensitive); + : $this->streql($sPeek, $string, $caseInsensitive); } /** @@ -390,16 +390,16 @@ private function inputLeft(): string } /** - * @param string $sString1 - * @param string $sString2 + * @param string $string1 + * @param string $string2 * @param bool $caseInsensitive */ - public function streql($sString1, $sString2, $caseInsensitive = true): bool + public function streql($string1, $string2, $caseInsensitive = true): bool { if ($caseInsensitive) { - return $this->strtolower($sString1) === $this->strtolower($sString2); + return $this->strtolower($string1) === $this->strtolower($string2); } else { - return $sString1 === $sString2; + return $string1 === $string2; } } @@ -412,14 +412,14 @@ public function backtrack($iAmount): void } /** - * @param string $sString + * @param string $string */ - public function strlen($sString): int + public function strlen($string): int { if ($this->parserSettings->bMultibyteSupport) { - return \mb_strlen($sString, $this->charset); + return \mb_strlen($string, $this->charset); } else { - return \strlen($sString); + return \strlen($string); } } @@ -445,63 +445,63 @@ private function substr($iStart, $length): string } /** - * @param string $sString + * @param string $string */ - private function strtolower($sString): string + private function strtolower($string): string { if ($this->parserSettings->bMultibyteSupport) { - return \mb_strtolower($sString, $this->charset); + return \mb_strtolower($string, $this->charset); } else { - return \strtolower($sString); + return \strtolower($string); } } /** - * @param string $sString + * @param string $string * * @return array * * @throws SourceException if the charset is UTF-8 and the string contains invalid byte sequences */ - private function strsplit($sString) + private function strsplit($string) { if ($this->parserSettings->bMultibyteSupport) { if ($this->streql($this->charset, 'utf-8')) { - $result = \preg_split('//u', $sString, -1, PREG_SPLIT_NO_EMPTY); + $result = \preg_split('//u', $string, -1, PREG_SPLIT_NO_EMPTY); if (!\is_array($result)) { throw new SourceException('`preg_split` failed with error ' . \preg_last_error()); } return $result; } else { - $length = \mb_strlen($sString, $this->charset); + $length = \mb_strlen($string, $this->charset); $result = []; for ($i = 0; $i < $length; ++$i) { - $result[] = \mb_substr($sString, $i, 1, $this->charset); + $result[] = \mb_substr($string, $i, 1, $this->charset); } return $result; } } else { - if ($sString === '') { + if ($string === '') { return []; } else { - return \str_split($sString); + return \str_split($string); } } } /** - * @param string $sString + * @param string $string * @param string $sNeedle * @param int $offset * * @return int|false */ - private function strpos($sString, $sNeedle, $offset) + private function strpos($string, $sNeedle, $offset) { if ($this->parserSettings->bMultibyteSupport) { - return \mb_strpos($sString, $sNeedle, $offset, $this->charset); + return \mb_strpos($string, $sNeedle, $offset, $this->charset); } else { - return \strpos($sString, $sNeedle, $offset); + return \strpos($string, $sNeedle, $offset); } } } diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index a39eaaa8..5d025898 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -45,18 +45,18 @@ public static function parse(ParserState $parserState, $list = null) $result = new DeclarationBlock($parserState->currentLine()); try { $aSelectorParts = []; - $sStringWrapperChar = false; + $stringWrapperCharacter = false; do { $aSelectorParts[] = $parserState->consume(1) . $parserState->consumeUntil(['{', '}', '\'', '"'], false, false, $comments); if (\in_array($parserState->peek(), ['\'', '"'], true) && \substr(\end($aSelectorParts), -1) != '\\') { - if ($sStringWrapperChar === false) { - $sStringWrapperChar = $parserState->peek(); - } elseif ($sStringWrapperChar == $parserState->peek()) { - $sStringWrapperChar = false; + if ($stringWrapperCharacter === false) { + $stringWrapperCharacter = $parserState->peek(); + } elseif ($stringWrapperCharacter == $parserState->peek()) { + $stringWrapperCharacter = false; } } - } while (!\in_array($parserState->peek(), ['{', '}'], true) || $sStringWrapperChar !== false); + } while (!\in_array($parserState->peek(), ['{', '}'], true) || $stringWrapperCharacter !== false); $result->setSelectors(\implode('', $aSelectorParts), $list); if ($parserState->comes('{')) { $parserState->consume(1); diff --git a/src/Value/CSSString.php b/src/Value/CSSString.php index 496b9468..7ccdedaa 100644 --- a/src/Value/CSSString.php +++ b/src/Value/CSSString.php @@ -20,15 +20,15 @@ class CSSString extends PrimitiveValue /** * @var string */ - private $sString; + private $string; /** - * @param string $sString + * @param string $string * @param int<0, max> $lineNumber */ - public function __construct($sString, $lineNumber = 0) + public function __construct($string, $lineNumber = 0) { - $this->sString = $sString; + $this->string = $string; parent::__construct($lineNumber); } @@ -75,11 +75,11 @@ public static function parse(ParserState $parserState): CSSString } /** - * @param string $sString + * @param string $string */ - public function setString($sString): void + public function setString($string): void { - $this->sString = $sString; + $this->string = $string; } /** @@ -87,7 +87,7 @@ public function setString($sString): void */ public function getString() { - return $this->sString; + return $this->string; } public function __toString(): string @@ -97,8 +97,8 @@ public function __toString(): string public function render(OutputFormat $outputFormat): string { - $sString = \addslashes($this->sString); - $sString = \str_replace("\n", '\\A', $sString); - return $outputFormat->getStringQuotingType() . $sString . $outputFormat->getStringQuotingType(); + $string = \addslashes($this->string); + $string = \str_replace("\n", '\\A', $string); + return $outputFormat->getStringQuotingType() . $string . $outputFormat->getStringQuotingType(); } } From 7e812c84e1c6054ff26c21c2c9f47bb685ce3d4c Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 24 Feb 2025 12:50:46 +0100 Subject: [PATCH 228/555] [CLEANUP] Avoid Hungarian notation for `start(Position)` (#977) Part of #756 --- src/Parsing/ParserState.php | 14 +++++++------- src/Value/Value.php | 12 ++++++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 35e495cb..000d4f98 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -424,21 +424,21 @@ public function strlen($string): int } /** - * @param int $iStart + * @param int $offset * @param int $length */ - private function substr($iStart, $length): string + private function substr($offset, $length): string { if ($length < 0) { - $length = \count($this->characters) - $iStart + $length; + $length = \count($this->characters) - $offset + $length; } - if ($iStart + $length > \count($this->characters)) { - $length = \count($this->characters) - $iStart; + if ($offset + $length > \count($this->characters)) { + $length = \count($this->characters) - $offset; } $result = ''; while ($length > 0) { - $result .= $this->characters[$iStart]; - $iStart++; + $result .= $this->characters[$offset]; + $offset++; $length--; } return $result; diff --git a/src/Value/Value.php b/src/Value/Value.php index e2082d5e..2cb6eeb4 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -78,23 +78,23 @@ public static function parseValue(ParserState $parserState, array $aListDelimite return $aStack[0]; } $aNewStack = []; - for ($iStartPosition = 0; $iStartPosition < $iStackLength; ++$iStartPosition) { - if ($iStartPosition === ($iStackLength - 1) || $sDelimiter !== $aStack[$iStartPosition + 1]) { - $aNewStack[] = $aStack[$iStartPosition]; + for ($offset = 0; $offset < $iStackLength; ++$offset) { + if ($offset === ($iStackLength - 1) || $sDelimiter !== $aStack[$offset + 1]) { + $aNewStack[] = $aStack[$offset]; continue; } $length = 2; //Number of elements to be joined - for ($i = $iStartPosition + 3; $i < $iStackLength; $i += 2, ++$length) { + for ($i = $offset + 3; $i < $iStackLength; $i += 2, ++$length) { if ($sDelimiter !== $aStack[$i]) { break; } } $list = new RuleValueList($sDelimiter, $parserState->currentLine()); - for ($i = $iStartPosition; $i - $iStartPosition < $length * 2; $i += 2) { + for ($i = $offset; $i - $offset < $length * 2; $i += 2) { $list->addListComponent($aStack[$i]); } $aNewStack[] = $list; - $iStartPosition += $length * 2 - 2; + $offset += $length * 2 - 2; } $aStack = $aNewStack; } From 321dc5eb285e4bf7cc6cf459731bab352da4c1d8 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 24 Feb 2025 12:59:27 +0100 Subject: [PATCH 229/555] [CLEANUP] Add some comments for grouping tests (#978) Part of #973 --- tests/Unit/CSSList/AtRuleBlockListTest.php | 8 ++++++++ tests/Unit/CSSList/DocumentTest.php | 8 ++++++++ tests/Unit/CSSList/KeyFrameTest.php | 8 ++++++++ 3 files changed, 24 insertions(+) diff --git a/tests/Unit/CSSList/AtRuleBlockListTest.php b/tests/Unit/CSSList/AtRuleBlockListTest.php index 782d1e3c..0537f7d1 100644 --- a/tests/Unit/CSSList/AtRuleBlockListTest.php +++ b/tests/Unit/CSSList/AtRuleBlockListTest.php @@ -16,6 +16,10 @@ */ final class AtRuleBlockListTest extends TestCase { + /* + * Tests for the implemented interfaces + */ + /** * @test */ @@ -46,6 +50,10 @@ public function implementsCommentable(): void self::assertInstanceOf(Commentable::class, $subject); } + /* + * not grouped yet + */ + /** * @test */ diff --git a/tests/Unit/CSSList/DocumentTest.php b/tests/Unit/CSSList/DocumentTest.php index baec691b..3d1b1257 100644 --- a/tests/Unit/CSSList/DocumentTest.php +++ b/tests/Unit/CSSList/DocumentTest.php @@ -22,6 +22,10 @@ */ final class DocumentTest extends TestCase { + /* + * Tests for the implemented interfaces + */ + /** * @test */ @@ -38,6 +42,10 @@ public function implementsCommentable(): void self::assertInstanceOf(Commentable::class, new Document()); } + /* + * not grouped yet + */ + /** * @test */ diff --git a/tests/Unit/CSSList/KeyFrameTest.php b/tests/Unit/CSSList/KeyFrameTest.php index 89f96953..72ec89a4 100644 --- a/tests/Unit/CSSList/KeyFrameTest.php +++ b/tests/Unit/CSSList/KeyFrameTest.php @@ -16,6 +16,10 @@ */ final class KeyFrameTest extends TestCase { + /* + * Tests for the implemented interfaces + */ + /** * @test */ @@ -46,6 +50,10 @@ public function implementsCommentable(): void self::assertInstanceOf(Commentable::class, $subject); } + /* + * not grouped yet + */ + /** * @test */ From c71c8a31caae6a0546b9074d5a16bb09881b39b4 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 24 Feb 2025 13:35:01 +0100 Subject: [PATCH 230/555] [TASK] Add dedicated tests for the abstract `CSSList` class (#979) --- tests/Unit/CSSList/AtRuleBlockListTest.php | 25 +-- tests/Unit/CSSList/CSSListTest.php | 171 ++++++++++++++++++ tests/Unit/CSSList/DocumentTest.php | 115 +----------- .../Unit/CSSList/Fixtures/ConcreteCSSList.php | 21 +++ tests/Unit/CSSList/KeyFrameTest.php | 17 +- 5 files changed, 221 insertions(+), 128 deletions(-) create mode 100644 tests/Unit/CSSList/CSSListTest.php create mode 100644 tests/Unit/CSSList/Fixtures/ConcreteCSSList.php diff --git a/tests/Unit/CSSList/AtRuleBlockListTest.php b/tests/Unit/CSSList/AtRuleBlockListTest.php index 0537f7d1..86411142 100644 --- a/tests/Unit/CSSList/AtRuleBlockListTest.php +++ b/tests/Unit/CSSList/AtRuleBlockListTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\TestCase; use Sabberworm\CSS\Comment\Commentable; use Sabberworm\CSS\CSSList\AtRuleBlockList; +use Sabberworm\CSS\CSSList\CSSList; use Sabberworm\CSS\Renderable; /** @@ -17,8 +18,8 @@ final class AtRuleBlockListTest extends TestCase { /* - * Tests for the implemented interfaces - */ + * Tests for the implemented interfaces and superclasses + */ /** * @test @@ -50,6 +51,16 @@ public function implementsCommentable(): void self::assertInstanceOf(Commentable::class, $subject); } + /** + * @test + */ + public function isCSSList(): void + { + $subject = new AtRuleBlockList('supports'); + + self::assertInstanceOf(CSSList::class, $subject); + } + /* * not grouped yet */ @@ -66,16 +77,6 @@ public function atRuleNameReturnsTypeProvidedToConstructor(): void self::assertSame($type, $subject->atRuleName()); } - /** - * @test - */ - public function getLineNoByDefaultReturnsZero(): void - { - $subject = new AtRuleBlockList('supports'); - - self::assertSame(0, $subject->getLineNo()); - } - /** * @test */ diff --git a/tests/Unit/CSSList/CSSListTest.php b/tests/Unit/CSSList/CSSListTest.php new file mode 100644 index 00000000..69ee4d05 --- /dev/null +++ b/tests/Unit/CSSList/CSSListTest.php @@ -0,0 +1,171 @@ +getLineNo()); + } + + /** + * @test + */ + public function getLineNoReturnsLineNumberProvidedToConstructor(): void + { + $lineNumber = 42; + + $subject = new ConcreteCSSList($lineNumber); + + self::assertSame($lineNumber, $subject->getLineNo()); + } + + /** + * @test + */ + public function getContentsInitiallyReturnsEmptyArray(): void + { + $subject = new ConcreteCSSList(); + + self::assertSame([], $subject->getContents()); + } + + /** + * @return array}> + */ + public static function contentsDataProvider(): array + { + return [ + 'empty array' => [[]], + '1 item' => [[new DeclarationBlock()]], + '2 items' => [[new DeclarationBlock(), new DeclarationBlock()]], + ]; + } + + /** + * @test + * + * @param list $contents + * + * @dataProvider contentsDataProvider + */ + public function setContentsSetsContents(array $contents): void + { + $subject = new ConcreteCSSList(); + + $subject->setContents($contents); + + self::assertSame($contents, $subject->getContents()); + } + + /** + * @test + */ + public function setContentsReplacesContentsSetInPreviousCall(): void + { + $subject = new ConcreteCSSList(); + + $contents2 = [new DeclarationBlock()]; + + $subject->setContents([new DeclarationBlock()]); + $subject->setContents($contents2); + + self::assertSame($contents2, $subject->getContents()); + } + + /** + * @test + */ + public function insertBeforeInsertsContentBeforeSibling(): void + { + $subject = new ConcreteCSSList(); + + $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'); + + $subject->setContents([$bogusOne, $sibling, $bogusTwo]); + + self::assertCount(3, $subject->getContents()); + + $subject->insertBefore($item, $sibling); + + self::assertCount(4, $subject->getContents()); + self::assertSame([$bogusOne, $item, $sibling, $bogusTwo], $subject->getContents()); + } + + /** + * @test + */ + public function insertBeforeAppendsIfSiblingNotFound(): void + { + $subject = new ConcreteCSSList(); + + $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'); + + $subject->setContents([$bogusOne, $sibling, $bogusTwo]); + + self::assertCount(3, $subject->getContents()); + + $subject->insertBefore($item, $orphan); + + self::assertCount(4, $subject->getContents()); + self::assertSame([$bogusOne, $sibling, $bogusTwo, $item], $subject->getContents()); + } +} diff --git a/tests/Unit/CSSList/DocumentTest.php b/tests/Unit/CSSList/DocumentTest.php index 3d1b1257..5ac66b5b 100644 --- a/tests/Unit/CSSList/DocumentTest.php +++ b/tests/Unit/CSSList/DocumentTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\TestCase; use Sabberworm\CSS\Comment\Commentable; use Sabberworm\CSS\CSSList\AtRuleBlockList; +use Sabberworm\CSS\CSSList\CSSList; use Sabberworm\CSS\CSSList\Document; use Sabberworm\CSS\Property\Charset; use Sabberworm\CSS\Property\Import; @@ -23,8 +24,8 @@ final class DocumentTest extends TestCase { /* - * Tests for the implemented interfaces - */ + * Tests for the implemented interfaces and superclasses + */ /** * @test @@ -42,121 +43,19 @@ public function implementsCommentable(): void self::assertInstanceOf(Commentable::class, new Document()); } - /* - * not grouped yet - */ - - /** - * @test - */ - public function getContentsInitiallyReturnsEmptyArray(): void - { - $subject = new Document(); - - self::assertSame([], $subject->getContents()); - } - - /** - * @return array}> - */ - public static function contentsDataProvider(): array - { - return [ - 'empty array' => [[]], - '1 item' => [[new DeclarationBlock()]], - '2 items' => [[new DeclarationBlock(), new DeclarationBlock()]], - ]; - } - - /** - * @test - * - * @param list $contents - * - * @dataProvider contentsDataProvider - */ - public function setContentsSetsContents(array $contents): void - { - $subject = new Document(); - - $subject->setContents($contents); - - self::assertSame($contents, $subject->getContents()); - } - - /** - * @test - */ - public function setContentsReplacesContentsSetInPreviousCall(): void - { - $subject = new Document(); - - $contents2 = [new DeclarationBlock()]; - - $subject->setContents([new DeclarationBlock()]); - $subject->setContents($contents2); - - self::assertSame($contents2, $subject->getContents()); - } - /** * @test */ - public function insertContentBeforeInsertsContentBeforeSibling(): void + public function isCSSList(): void { $subject = new Document(); - $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'); - - $subject->setContents([$bogusOne, $sibling, $bogusTwo]); - - self::assertCount(3, $subject->getContents()); - - $subject->insertBefore($item, $sibling); - - self::assertCount(4, $subject->getContents()); - self::assertSame([$bogusOne, $item, $sibling, $bogusTwo], $subject->getContents()); + self::assertInstanceOf(CSSList::class, $subject); } - /** - * @test + /* + * not grouped yet */ - public function insertContentBeforeAppendsIfSiblingNotFound(): void - { - $subject = new Document(); - - $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'); - - $subject->setContents([$bogusOne, $sibling, $bogusTwo]); - - self::assertCount(3, $subject->getContents()); - - $subject->insertBefore($item, $orphan); - - self::assertCount(4, $subject->getContents()); - self::assertSame([$bogusOne, $sibling, $bogusTwo, $item], $subject->getContents()); - } /** * @test diff --git a/tests/Unit/CSSList/Fixtures/ConcreteCSSList.php b/tests/Unit/CSSList/Fixtures/ConcreteCSSList.php new file mode 100644 index 00000000..250f2a1b --- /dev/null +++ b/tests/Unit/CSSList/Fixtures/ConcreteCSSList.php @@ -0,0 +1,21 @@ +getLineNo()); + self::assertInstanceOf(CSSList::class, $subject); } + /* + * not grouped yet + */ + /** * @test */ From e725c2e380aaec425262aabc0b5a6b911385d64d Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 24 Feb 2025 13:49:18 +0100 Subject: [PATCH 231/555] [CLEANUP] Avoid Hungarian notation in the exception classes (#981) Part of #756 --- src/Parsing/SourceException.php | 6 ++-- src/Parsing/UnexpectedTokenException.php | 36 ++++++++++++------------ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/Parsing/SourceException.php b/src/Parsing/SourceException.php index 31fc0a59..45b35772 100644 --- a/src/Parsing/SourceException.php +++ b/src/Parsing/SourceException.php @@ -14,13 +14,13 @@ class SourceException extends \Exception /** * @param int<0, max> $lineNumber */ - public function __construct(string $sMessage, int $lineNumber = 0) + public function __construct(string $message, int $lineNumber = 0) { $this->lineNumber = $lineNumber; if ($lineNumber !== 0) { - $sMessage .= " [line no: $lineNumber]"; + $message .= " [line no: $lineNumber]"; } - parent::__construct($sMessage); + parent::__construct($message); } /** diff --git a/src/Parsing/UnexpectedTokenException.php b/src/Parsing/UnexpectedTokenException.php index 95fd3d6d..c89474c7 100644 --- a/src/Parsing/UnexpectedTokenException.php +++ b/src/Parsing/UnexpectedTokenException.php @@ -12,38 +12,38 @@ class UnexpectedTokenException extends SourceException /** * @var string */ - private $sExpected; + private $expected; /** * @var string */ - private $sFound; + private $found; /** * @var 'literal'|'identifier'|'count'|'expression'|'search'|'custom' */ - private $sMatchType; + private $matchType; /** - * @param 'literal'|'identifier'|'count'|'expression'|'search'|'custom' $sMatchType + * @param 'literal'|'identifier'|'count'|'expression'|'search'|'custom' $matchType * @param int<0, max> $lineNumber */ - public function __construct(string $sExpected, string $sFound, string $sMatchType = 'literal', int $lineNumber = 0) + public function __construct(string $expected, string $found, string $matchType = 'literal', int $lineNumber = 0) { - $this->sExpected = $sExpected; - $this->sFound = $sFound; - $this->sMatchType = $sMatchType; - $sMessage = "Token “{$sExpected}” ({$sMatchType}) not found. Got “{$sFound}”."; - if ($this->sMatchType === 'search') { - $sMessage = "Search for “{$sExpected}” returned no results. Context: “{$sFound}”."; - } elseif ($this->sMatchType === 'count') { - $sMessage = "Next token was expected to have {$sExpected} chars. Context: “{$sFound}”."; - } elseif ($this->sMatchType === 'identifier') { - $sMessage = "Identifier expected. Got “{$sFound}”"; - } elseif ($this->sMatchType === 'custom') { - $sMessage = \trim("$sExpected $sFound"); + $this->expected = $expected; + $this->found = $found; + $this->matchType = $matchType; + $message = "Token “{$expected}” ({$matchType}) not found. Got “{$found}”."; + if ($this->matchType === 'search') { + $message = "Search for “{$expected}” returned no results. Context: “{$found}”."; + } elseif ($this->matchType === 'count') { + $message = "Next token was expected to have {$expected} chars. Context: “{$found}”."; + } elseif ($this->matchType === 'identifier') { + $message = "Identifier expected. Got “{$found}”"; + } elseif ($this->matchType === 'custom') { + $message = \trim("$expected $found"); } - parent::__construct($sMessage, $lineNumber); + parent::__construct($message, $lineNumber); } } From a3e4b93d2b3026ec7b2ddc19c922b7c39cf3d685 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 24 Feb 2025 20:08:53 +0100 Subject: [PATCH 232/555] [CLEANUP] Avoid Hungarian notation in `ParserState` (#980) Part of #756 --- src/Parsing/ParserState.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 000d4f98..67bf7ccd 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -404,11 +404,11 @@ public function streql($string1, $string2, $caseInsensitive = true): bool } /** - * @param int $iAmount + * @param int $numberOfCharacters */ - public function backtrack($iAmount): void + public function backtrack($numberOfCharacters): void { - $this->currentPosition -= $iAmount; + $this->currentPosition -= $numberOfCharacters; } /** @@ -490,18 +490,18 @@ private function strsplit($string) } /** - * @param string $string - * @param string $sNeedle + * @param string $haystack + * @param string $needle * @param int $offset * * @return int|false */ - private function strpos($string, $sNeedle, $offset) + private function strpos($haystack, $needle, $offset) { if ($this->parserSettings->bMultibyteSupport) { - return \mb_strpos($string, $sNeedle, $offset, $this->charset); + return \mb_strpos($haystack, $needle, $offset, $this->charset); } else { - return \strpos($string, $sNeedle, $offset); + return \strpos($haystack, $needle, $offset); } } } From 3503c77be3d6806a0e7bab7c9c4bf8401d62fa16 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 24 Feb 2025 20:11:40 +0100 Subject: [PATCH 233/555] [CLEANUP] Avoid Hungarian notation in `Charset` (#982) Part of #756 --- src/Property/Charset.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Property/Charset.php b/src/Property/Charset.php index ac063f28..d0bb4276 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -21,7 +21,7 @@ class Charset implements AtRule /** * @var CSSString */ - private $oCharset; + private $charset; /** * @var int<0, max> @@ -40,9 +40,9 @@ class Charset implements AtRule /** * @param int<0, max> $lineNumber */ - public function __construct(CSSString $oCharset, $lineNumber = 0) + public function __construct(CSSString $charset, $lineNumber = 0) { - $this->oCharset = $oCharset; + $this->charset = $charset; $this->lineNumber = $lineNumber; } @@ -60,7 +60,7 @@ public function getLineNo(): int public function setCharset($charset): void { $charset = $charset instanceof CSSString ? $charset : new CSSString($charset); - $this->oCharset = $charset; + $this->charset = $charset; } /** @@ -68,7 +68,7 @@ public function setCharset($charset): void */ public function getCharset() { - return $this->oCharset->getString(); + return $this->charset->getString(); } public function __toString(): string @@ -78,7 +78,7 @@ public function __toString(): string public function render(OutputFormat $outputFormat): string { - return "{$outputFormat->comments($this)}@charset {$this->oCharset->render($outputFormat)};"; + return "{$outputFormat->comments($this)}@charset {$this->charset->render($outputFormat)};"; } public function atRuleName(): string @@ -91,7 +91,7 @@ public function atRuleName(): string */ public function atRuleArgs() { - return $this->oCharset; + return $this->charset; } /** From 0dcf0b4d430142eeea1eb3ac54c63b568db84161 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 24 Feb 2025 20:17:13 +0100 Subject: [PATCH 234/555] [TASK] Drop unnecessary properties from `UnexpectedTokenException` (#983) --- src/Parsing/UnexpectedTokenException.php | 26 ++++-------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/src/Parsing/UnexpectedTokenException.php b/src/Parsing/UnexpectedTokenException.php index c89474c7..8a16794c 100644 --- a/src/Parsing/UnexpectedTokenException.php +++ b/src/Parsing/UnexpectedTokenException.php @@ -9,38 +9,20 @@ */ class UnexpectedTokenException extends SourceException { - /** - * @var string - */ - private $expected; - - /** - * @var string - */ - private $found; - - /** - * @var 'literal'|'identifier'|'count'|'expression'|'search'|'custom' - */ - private $matchType; - /** * @param 'literal'|'identifier'|'count'|'expression'|'search'|'custom' $matchType * @param int<0, max> $lineNumber */ public function __construct(string $expected, string $found, string $matchType = 'literal', int $lineNumber = 0) { - $this->expected = $expected; - $this->found = $found; - $this->matchType = $matchType; $message = "Token “{$expected}” ({$matchType}) not found. Got “{$found}”."; - if ($this->matchType === 'search') { + if ($matchType === 'search') { $message = "Search for “{$expected}” returned no results. Context: “{$found}”."; - } elseif ($this->matchType === 'count') { + } elseif ($matchType === 'count') { $message = "Next token was expected to have {$expected} chars. Context: “{$found}”."; - } elseif ($this->matchType === 'identifier') { + } elseif ($matchType === 'identifier') { $message = "Identifier expected. Got “{$found}”"; - } elseif ($this->matchType === 'custom') { + } elseif ($matchType === 'custom') { $message = \trim("$expected $found"); } From 5c193b9168a8adb9e81385e6ff867d78e89bf285 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 24 Feb 2025 20:25:26 +0100 Subject: [PATCH 235/555] [CLEANUP] Avoid Hungarian notation in `CSSNamespace` (#985) Part of #756 --- src/Property/CSSNamespace.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Property/CSSNamespace.php b/src/Property/CSSNamespace.php index 21135a35..0d6750f0 100644 --- a/src/Property/CSSNamespace.php +++ b/src/Property/CSSNamespace.php @@ -15,7 +15,7 @@ class CSSNamespace implements AtRule /** * @var string */ - private $mUrl; + private $url; /** * @var string @@ -35,13 +35,13 @@ class CSSNamespace implements AtRule protected $comments = []; /** - * @param string $mUrl + * @param string $url * @param string|null $prefix * @param int<0, max> $lineNumber */ - public function __construct($mUrl, $prefix = null, $lineNumber = 0) + public function __construct($url, $prefix = null, $lineNumber = 0) { - $this->mUrl = $mUrl; + $this->url = $url; $this->prefix = $prefix; $this->lineNumber = $lineNumber; } @@ -62,7 +62,7 @@ public function __toString(): string public function render(OutputFormat $outputFormat): string { return '@namespace ' . ($this->prefix === null ? '' : $this->prefix . ' ') - . $this->mUrl->render($outputFormat) . ';'; + . $this->url->render($outputFormat) . ';'; } /** @@ -70,7 +70,7 @@ public function render(OutputFormat $outputFormat): string */ public function getUrl() { - return $this->mUrl; + return $this->url; } /** @@ -82,11 +82,11 @@ public function getPrefix() } /** - * @param string $mUrl + * @param string $url */ - public function setUrl($mUrl): void + public function setUrl($url): void { - $this->mUrl = $mUrl; + $this->url = $url; } /** @@ -110,7 +110,7 @@ public function atRuleName(): string */ public function atRuleArgs(): array { - $result = [$this->mUrl]; + $result = [$this->url]; if ($this->prefix) { \array_unshift($result, $this->prefix); } From afdd72f603cc5fc84744153b78e0b775d902f2ff Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 24 Feb 2025 23:25:40 +0100 Subject: [PATCH 236/555] [TASK] Add a dedicated testcase for the abstract `CSSBlockList` (#984) --- tests/Unit/CSSList/AtRuleBlockListTest.php | 11 +++++ tests/Unit/CSSList/CSSBlockListTest.php | 48 +++++++++++++++++++ tests/Unit/CSSList/DocumentTest.php | 11 +++++ .../CSSList/Fixtures/ConcreteCSSBlockList.php | 21 ++++++++ 4 files changed, 91 insertions(+) create mode 100644 tests/Unit/CSSList/CSSBlockListTest.php create mode 100644 tests/Unit/CSSList/Fixtures/ConcreteCSSBlockList.php diff --git a/tests/Unit/CSSList/AtRuleBlockListTest.php b/tests/Unit/CSSList/AtRuleBlockListTest.php index 86411142..1e587f8e 100644 --- a/tests/Unit/CSSList/AtRuleBlockListTest.php +++ b/tests/Unit/CSSList/AtRuleBlockListTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\TestCase; use Sabberworm\CSS\Comment\Commentable; use Sabberworm\CSS\CSSList\AtRuleBlockList; +use Sabberworm\CSS\CSSList\CSSBlockList; use Sabberworm\CSS\CSSList\CSSList; use Sabberworm\CSS\Renderable; @@ -51,6 +52,16 @@ public function implementsCommentable(): void self::assertInstanceOf(Commentable::class, $subject); } + /** + * @test + */ + public function isCSSBLockList(): void + { + $subject = new AtRuleBlockList('supports'); + + self::assertInstanceOf(CSSBlockList::class, $subject); + } + /** * @test */ diff --git a/tests/Unit/CSSList/CSSBlockListTest.php b/tests/Unit/CSSList/CSSBlockListTest.php new file mode 100644 index 00000000..9d7ef076 --- /dev/null +++ b/tests/Unit/CSSList/CSSBlockListTest.php @@ -0,0 +1,48 @@ + Date: Tue, 25 Feb 2025 09:28:15 +0100 Subject: [PATCH 237/555] [TASK] Move `CalcRuleValueListTest` to unit tests (#987) Also add the missing `@covers` annotations for the superclasses of the tested class. Part of #758. --- tests/{ => Unit}/Value/CalcRuleValueListTest.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) rename tests/{ => Unit}/Value/CalcRuleValueListTest.php (86%) diff --git a/tests/Value/CalcRuleValueListTest.php b/tests/Unit/Value/CalcRuleValueListTest.php similarity index 86% rename from tests/Value/CalcRuleValueListTest.php rename to tests/Unit/Value/CalcRuleValueListTest.php index 51d3a72f..5d73d9e9 100644 --- a/tests/Value/CalcRuleValueListTest.php +++ b/tests/Unit/Value/CalcRuleValueListTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Sabberworm\CSS\Tests\Value; +namespace Sabberworm\CSS\Tests\Unit\Value; use PHPUnit\Framework\TestCase; use Sabberworm\CSS\Value\CalcRuleValueList; @@ -10,6 +10,9 @@ /** * @covers \Sabberworm\CSS\Value\CalcRuleValueList + * @covers \Sabberworm\CSS\Value\RuleValueList + * @covers \Sabberworm\CSS\Value\Value + * @covers \Sabberworm\CSS\Value\ValueList */ final class CalcRuleValueListTest extends TestCase { From 6ff4aa5f0e19c3e2049d32dc1ea0e9574292182d Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Tue, 25 Feb 2025 08:31:35 +0000 Subject: [PATCH 238/555] [CLEANUP] Use `null` for unset value in `DeclarationBlock::parse()` (#988) The variable is either a string or it isn't. The best practice for when it isn't is for it to be `null` or `unset()`. Using `false` for when it isn't can lead to type-safety issues. Reference: https://vulke.medium.com/when-should-variables-be-null-false-undefined-or-an-empty-string-in-php-4ebd73c7a954 Also use `===` in a comparison in the affected code. Resolves #975. Part of #976. --- config/phpstan-baseline.neon | 6 ------ src/RuleSet/DeclarationBlock.php | 9 ++++----- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 4150a6e8..3f311ad1 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -330,12 +330,6 @@ parameters: count: 1 path: ../src/RuleSet/DeclarationBlock.php - - - message: '#^Loose comparison via "\=\=" is not allowed\.$#' - identifier: equal.notAllowed - count: 1 - path: ../src/RuleSet/DeclarationBlock.php - - message: '#^Parameters should have "string" types as the only types passed to this method$#' identifier: typePerfect.narrowPublicClassMethodParamType diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 5d025898..4dc0ce2d 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -45,18 +45,17 @@ public static function parse(ParserState $parserState, $list = null) $result = new DeclarationBlock($parserState->currentLine()); try { $aSelectorParts = []; - $stringWrapperCharacter = false; do { $aSelectorParts[] = $parserState->consume(1) . $parserState->consumeUntil(['{', '}', '\'', '"'], false, false, $comments); if (\in_array($parserState->peek(), ['\'', '"'], true) && \substr(\end($aSelectorParts), -1) != '\\') { - if ($stringWrapperCharacter === false) { + if (!isset($stringWrapperCharacter)) { $stringWrapperCharacter = $parserState->peek(); - } elseif ($stringWrapperCharacter == $parserState->peek()) { - $stringWrapperCharacter = false; + } elseif ($stringWrapperCharacter === $parserState->peek()) { + unset($stringWrapperCharacter); } } - } while (!\in_array($parserState->peek(), ['{', '}'], true) || $stringWrapperCharacter !== false); + } while (!\in_array($parserState->peek(), ['{', '}'], true) || isset($stringWrapperCharacter)); $result->setSelectors(\implode('', $aSelectorParts), $list); if ($parserState->comes('{')) { $parserState->consume(1); From ce26af61738e66a4f4af805f458648614f8d902d Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 25 Feb 2025 09:32:32 +0100 Subject: [PATCH 239/555] [CLEANUP] Avoid Hungarian notation for `selector` (#986) Part of #756 --- src/Property/Selector.php | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Property/Selector.php b/src/Property/Selector.php index 28e63ec2..3b1c0573 100644 --- a/src/Property/Selector.php +++ b/src/Property/Selector.php @@ -67,7 +67,7 @@ class Selector /** * @var string */ - private $sSelector; + private $selector; /** * @var int|null @@ -75,22 +75,22 @@ class Selector private $iSpecificity; /** - * @param string $sSelector + * @param string $selector * * @return bool */ - public static function isValid($sSelector) + public static function isValid($selector) { - return \preg_match(static::SELECTOR_VALIDATION_RX, $sSelector); + return \preg_match(static::SELECTOR_VALIDATION_RX, $selector); } /** - * @param string $sSelector + * @param string $selector * @param bool $bCalculateSpecificity */ - public function __construct($sSelector, $bCalculateSpecificity = false) + public function __construct($selector, $bCalculateSpecificity = false) { - $this->setSelector($sSelector); + $this->setSelector($selector); if ($bCalculateSpecificity) { $this->getSpecificity(); } @@ -101,15 +101,15 @@ public function __construct($sSelector, $bCalculateSpecificity = false) */ public function getSelector() { - return $this->sSelector; + return $this->selector; } /** - * @param string $sSelector + * @param string $selector */ - public function setSelector($sSelector): void + public function setSelector($selector): void { - $this->sSelector = \trim($sSelector); + $this->selector = \trim($selector); $this->iSpecificity = null; } @@ -127,9 +127,9 @@ public function getSpecificity() $a = 0; /// @todo should exclude \# as well as "#" $aMatches = null; - $b = \substr_count($this->sSelector, '#'); - $c = \preg_match_all(self::NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX, $this->sSelector, $aMatches); - $d = \preg_match_all(self::ELEMENTS_AND_PSEUDO_ELEMENTS_RX, $this->sSelector, $aMatches); + $b = \substr_count($this->selector, '#'); + $c = \preg_match_all(self::NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX, $this->selector, $aMatches); + $d = \preg_match_all(self::ELEMENTS_AND_PSEUDO_ELEMENTS_RX, $this->selector, $aMatches); $this->iSpecificity = ($a * 1000) + ($b * 100) + ($c * 10) + $d; } return $this->iSpecificity; From 3e96720483efc1356b997dfa37a8a5cfe219d7b9 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 25 Feb 2025 10:23:07 +0100 Subject: [PATCH 240/555] [CLEANUP] Avoid Hungarian notation in `Selector` (#989) Part of #756 --- src/Property/Selector.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Property/Selector.php b/src/Property/Selector.php index 3b1c0573..c7226d3c 100644 --- a/src/Property/Selector.php +++ b/src/Property/Selector.php @@ -72,7 +72,7 @@ class Selector /** * @var int|null */ - private $iSpecificity; + private $specificity; /** * @param string $selector @@ -86,12 +86,12 @@ public static function isValid($selector) /** * @param string $selector - * @param bool $bCalculateSpecificity + * @param bool $calculateSpecificity */ - public function __construct($selector, $bCalculateSpecificity = false) + public function __construct($selector, $calculateSpecificity = false) { $this->setSelector($selector); - if ($bCalculateSpecificity) { + if ($calculateSpecificity) { $this->getSpecificity(); } } @@ -110,7 +110,7 @@ public function getSelector() public function setSelector($selector): void { $this->selector = \trim($selector); - $this->iSpecificity = null; + $this->specificity = null; } public function __toString(): string @@ -123,15 +123,15 @@ public function __toString(): string */ public function getSpecificity() { - if ($this->iSpecificity === null) { + if ($this->specificity === null) { $a = 0; /// @todo should exclude \# as well as "#" $aMatches = null; $b = \substr_count($this->selector, '#'); $c = \preg_match_all(self::NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX, $this->selector, $aMatches); $d = \preg_match_all(self::ELEMENTS_AND_PSEUDO_ELEMENTS_RX, $this->selector, $aMatches); - $this->iSpecificity = ($a * 1000) + ($b * 100) + ($c * 10) + $d; + $this->specificity = ($a * 1000) + ($b * 100) + ($c * 10) + $d; } - return $this->iSpecificity; + return $this->specificity; } } From 66fbb730f6594d3ec0d16a6bb70950f451fee0b6 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 25 Feb 2025 10:29:21 +0100 Subject: [PATCH 241/555] [CLEANUP] Drop some comments from testcases (#973) These comments are no longer needed now that the abstract base classes have their own testcases. --- tests/Unit/CSSList/AtRuleBlockListTest.php | 8 -------- tests/Unit/CSSList/DocumentTest.php | 8 -------- tests/Unit/CSSList/KeyFrameTest.php | 8 -------- 3 files changed, 24 deletions(-) diff --git a/tests/Unit/CSSList/AtRuleBlockListTest.php b/tests/Unit/CSSList/AtRuleBlockListTest.php index 1e587f8e..bc83ccc8 100644 --- a/tests/Unit/CSSList/AtRuleBlockListTest.php +++ b/tests/Unit/CSSList/AtRuleBlockListTest.php @@ -18,10 +18,6 @@ */ final class AtRuleBlockListTest extends TestCase { - /* - * Tests for the implemented interfaces and superclasses - */ - /** * @test */ @@ -72,10 +68,6 @@ public function isCSSList(): void self::assertInstanceOf(CSSList::class, $subject); } - /* - * not grouped yet - */ - /** * @test */ diff --git a/tests/Unit/CSSList/DocumentTest.php b/tests/Unit/CSSList/DocumentTest.php index 00158590..ed1a2b22 100644 --- a/tests/Unit/CSSList/DocumentTest.php +++ b/tests/Unit/CSSList/DocumentTest.php @@ -24,10 +24,6 @@ */ final class DocumentTest extends TestCase { - /* - * Tests for the implemented interfaces and superclasses - */ - /** * @test */ @@ -64,10 +60,6 @@ public function isCSSList(): void self::assertInstanceOf(CSSList::class, $subject); } - /* - * not grouped yet - */ - /** * @test */ diff --git a/tests/Unit/CSSList/KeyFrameTest.php b/tests/Unit/CSSList/KeyFrameTest.php index a176b3bf..552dcada 100644 --- a/tests/Unit/CSSList/KeyFrameTest.php +++ b/tests/Unit/CSSList/KeyFrameTest.php @@ -17,10 +17,6 @@ */ final class KeyFrameTest extends TestCase { - /* - * Tests for the implemented interfaces and superclasses - */ - /** * @test */ @@ -61,10 +57,6 @@ public function isCSSList(): void self::assertInstanceOf(CSSList::class, $subject); } - /* - * not grouped yet - */ - /** * @test */ From 4384bbee4c1010fd26779240d237bb7e17c665ed Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 25 Feb 2025 11:35:58 +0100 Subject: [PATCH 242/555] [CLEANUP] Avoid Hungarian notation in `Rule` (#992) Part of #756 --- src/Rule/Rule.php | 116 +++++++++++++++++++++++----------------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index f4803b77..7a51709a 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -24,22 +24,22 @@ class Rule implements Renderable, Commentable /** * @var string */ - private $sRule; + private $rule; /** * @var RuleValueList|string|null */ - private $mValue; + private $value; /** * @var bool */ - private $bIsImportant = false; + private $isImportant = false; /** * @var array */ - private $aIeHack = []; + private $ieHack = []; /** * @var int @@ -51,7 +51,7 @@ class Rule implements Renderable, Commentable * * @internal since 8.8.0 */ - protected $iColNo; + protected $columnNumber; /** * @var array @@ -61,15 +61,15 @@ class Rule implements Renderable, Commentable protected $comments = []; /** - * @param string $sRule + * @param string $rule * @param int<0, max> $lineNumber - * @param int $iColNo + * @param int $columnNumber */ - public function __construct($sRule, $lineNumber = 0, $iColNo = 0) + public function __construct($rule, $lineNumber = 0, $columnNumber = 0) { - $this->sRule = $sRule; + $this->rule = $rule; $this->lineNumber = $lineNumber; - $this->iColNo = $iColNo; + $this->columnNumber = $columnNumber; } /** @@ -89,8 +89,8 @@ public static function parse(ParserState $parserState): Rule $rule->setComments($comments); $rule->addComments($parserState->consumeWhiteSpace()); $parserState->consume(':'); - $oValue = Value::parseValue($parserState, self::listDelimiterForRule($rule->getRule())); - $rule->setValue($oValue); + $value = Value::parseValue($parserState, self::listDelimiterForRule($rule->getRule())); + $rule->setValue($value); if ($parserState->getSettings()->bLenientParsing) { while ($parserState->comes('\\')) { $parserState->consume('\\'); @@ -120,17 +120,17 @@ public static function parse(ParserState $parserState): Rule * 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 + * @param string $rule * * @return list */ - private static function listDelimiterForRule($sRule): array + private static function listDelimiterForRule($rule): array { - if (\preg_match('/^font($|-)/', $sRule)) { + if (\preg_match('/^font($|-)/', $rule)) { return [',', '/', ' ']; } - switch ($sRule) { + switch ($rule) { case 'src': return [' ', ',']; default: @@ -151,25 +151,25 @@ public function getLineNo(): int */ public function getColNo() { - return $this->iColNo; + return $this->columnNumber; } /** - * @param int $iLine - * @param int $iColumn + * @param int $lineNumber + * @param int $columnNumber */ - public function setPosition($iLine, $iColumn): void + public function setPosition($lineNumber, $columnNumber): void { - $this->iColNo = $iColumn; - $this->lineNumber = $iLine; + $this->columnNumber = $columnNumber; + $this->lineNumber = $lineNumber; } /** - * @param string $sRule + * @param string $rule */ - public function setRule($sRule): void + public function setRule($rule): void { - $this->sRule = $sRule; + $this->rule = $rule; } /** @@ -177,7 +177,7 @@ public function setRule($sRule): void */ public function getRule() { - return $this->sRule; + return $this->rule; } /** @@ -185,7 +185,7 @@ public function getRule() */ public function getValue() { - return $this->mValue; + return $this->value; } /** @@ -193,47 +193,47 @@ public function getValue() */ public function setValue($mValue): void { - $this->mValue = $mValue; + $this->value = $mValue; } /** * Adds a value to the existing value. Value will be appended if a `RuleValueList` exists of the given type. * Otherwise, the existing value will be wrapped by one. * - * @param RuleValueList|array $mValue - * @param string $sType + * @param RuleValueList|array $value + * @param string $type */ - public function addValue($mValue, $sType = ' '): void + public function addValue($value, $type = ' '): void { - if (!\is_array($mValue)) { - $mValue = [$mValue]; + if (!\is_array($value)) { + $value = [$value]; } - if (!($this->mValue instanceof RuleValueList) || $this->mValue->getListSeparator() !== $sType) { - $mCurrentValue = $this->mValue; - $this->mValue = new RuleValueList($sType, $this->lineNumber); - if ($mCurrentValue) { - $this->mValue->addListComponent($mCurrentValue); + if (!($this->value instanceof RuleValueList) || $this->value->getListSeparator() !== $type) { + $currentValue = $this->value; + $this->value = new RuleValueList($type, $this->lineNumber); + if ($currentValue) { + $this->value->addListComponent($currentValue); } } - foreach ($mValue as $mValueItem) { - $this->mValue->addListComponent($mValueItem); + foreach ($value as $valueItem) { + $this->value->addListComponent($valueItem); } } /** - * @param int $iModifier + * @param int $modifier */ - public function addIeHack($iModifier): void + public function addIeHack($modifier): void { - $this->aIeHack[] = $iModifier; + $this->ieHack[] = $modifier; } /** - * @param array $aModifiers + * @param array $modifiers */ - public function setIeHack(array $aModifiers): void + public function setIeHack(array $modifiers): void { - $this->aIeHack = $aModifiers; + $this->ieHack = $modifiers; } /** @@ -241,15 +241,15 @@ public function setIeHack(array $aModifiers): void */ public function getIeHack() { - return $this->aIeHack; + return $this->ieHack; } /** - * @param bool $bIsImportant + * @param bool $isImportant */ - public function setIsImportant($bIsImportant): void + public function setIsImportant($isImportant): void { - $this->bIsImportant = $bIsImportant; + $this->isImportant = $isImportant; } /** @@ -257,7 +257,7 @@ public function setIsImportant($bIsImportant): void */ public function getIsImportant() { - return $this->bIsImportant; + return $this->isImportant; } public function __toString(): string @@ -267,16 +267,16 @@ public function __toString(): string public function render(OutputFormat $outputFormat): string { - $result = "{$outputFormat->comments($this)}{$this->sRule}:{$outputFormat->spaceAfterRuleName()}"; - if ($this->mValue instanceof Value) { // Can also be a ValueList - $result .= $this->mValue->render($outputFormat); + $result = "{$outputFormat->comments($this)}{$this->rule}:{$outputFormat->spaceAfterRuleName()}"; + if ($this->value instanceof Value) { // Can also be a ValueList + $result .= $this->value->render($outputFormat); } else { - $result .= $this->mValue; + $result .= $this->value; } - if (!empty($this->aIeHack)) { - $result .= ' \\' . \implode('\\', $this->aIeHack); + if (!empty($this->ieHack)) { + $result .= ' \\' . \implode('\\', $this->ieHack); } - if ($this->bIsImportant) { + if ($this->isImportant) { $result .= ' !important'; } $result .= ';'; From 00e23e948f939b6749dd12b59bd504336d49a4b7 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 26 Feb 2025 01:06:41 +0100 Subject: [PATCH 243/555] [TASK] Add tests for `Document::getAllRuleSets()` (#991) Part of #757 Co-authored-by: JakeQZ --- tests/Unit/CSSList/DocumentTest.php | 137 ++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/tests/Unit/CSSList/DocumentTest.php b/tests/Unit/CSSList/DocumentTest.php index ed1a2b22..07afcf1b 100644 --- a/tests/Unit/CSSList/DocumentTest.php +++ b/tests/Unit/CSSList/DocumentTest.php @@ -13,6 +13,7 @@ use Sabberworm\CSS\Property\Charset; use Sabberworm\CSS\Property\Import; use Sabberworm\CSS\Renderable; +use Sabberworm\CSS\RuleSet\AtRuleSet; use Sabberworm\CSS\RuleSet\DeclarationBlock; use Sabberworm\CSS\Value\CSSString; use Sabberworm\CSS\Value\URL; @@ -148,6 +149,142 @@ public function getAllDeclarationBlocksIgnoresCharset(): void self::assertSame([], $result); } + /** + * @test + */ + public function getAllRuleSetsWhenNoContentSetReturnsEmptyArray(): void + { + $subject = new Document(); + + self::assertSame([], $subject->getAllRuleSets()); + } + + /** + * @test + */ + public function getAllRuleSetsReturnsOneDeclarationBlockDirectlySetAsContent(): void + { + $subject = new Document(); + + $declarationBlock = new DeclarationBlock(); + $subject->setContents([$declarationBlock]); + + $result = $subject->getAllRuleSets(); + + self::assertSame([$declarationBlock], $result); + } + + /** + * @test + */ + public function getAllRuleSetsReturnsOneAtRuleSetDirectlySetAsContent(): void + { + $subject = new Document(); + + $atRuleSet = new AtRuleSet('media'); + $subject->setContents([$atRuleSet]); + + $result = $subject->getAllRuleSets(); + + self::assertSame([$atRuleSet], $result); + } + + /** + * @test + */ + public function getAllRuleSetsReturnsMultipleDeclarationBlocksDirectlySetAsContents(): void + { + $subject = new Document(); + + $declarationBlock1 = new DeclarationBlock(); + $declarationBlock2 = new DeclarationBlock(); + $subject->setContents([$declarationBlock1, $declarationBlock2]); + + $result = $subject->getAllRuleSets(); + + self::assertSame([$declarationBlock1, $declarationBlock2], $result); + } + + /** + * @test + */ + public function getAllRuleSetsReturnsMultipleAtRuleSetsDirectlySetAsContents(): void + { + $subject = new Document(); + + $atRuleSet1 = new AtRuleSet('media'); + $atRuleSet2 = new AtRuleSet('media'); + $subject->setContents([$atRuleSet1, $atRuleSet2]); + + $result = $subject->getAllRuleSets(); + + self::assertSame([$atRuleSet1, $atRuleSet2], $result); + } + + /** + * @test + */ + public function getAllRuleSetsReturnsDeclarationBlocksWithinAtRuleBlockList(): void + { + $subject = new Document(); + + $declarationBlock = new DeclarationBlock(); + $atRuleBlockList = new AtRuleBlockList('media'); + $atRuleBlockList->setContents([$declarationBlock]); + $subject->setContents([$atRuleBlockList]); + + $result = $subject->getAllRuleSets(); + + self::assertSame([$declarationBlock], $result); + } + + /** + * @test + */ + public function getAllRuleSetsReturnsAtRuleSetsWithinAtRuleBlockList(): void + { + $subject = new Document(); + + $atRule = new AtRuleSet('media'); + $atRuleBlockList = new AtRuleBlockList('media'); + $atRuleBlockList->setContents([$atRule]); + $subject->setContents([$atRuleBlockList]); + + $result = $subject->getAllRuleSets(); + + self::assertSame([$atRule], $result); + } + + /** + * @test + */ + public function getAllRuleSetsIgnoresImport(): void + { + $subject = new Document(); + + $import = new Import(new URL(new CSSString('https://www.example.com/')), ''); + $subject->setContents([$import]); + + $result = $subject->getAllRuleSets(); + + self::assertSame([], $result); + } + + /** + * @test + */ + public function getAllRuleSetsIgnoresCharset(): void + { + $subject = new Document(); + + $charset = new Charset(new CSSString('UTF-8')); + $subject->setContents([$charset]); + + $result = $subject->getAllRuleSets(); + + self::assertSame([], $result); + } + /** * @test */ From 6d9656c7833881349a073119baced51d187bddf3 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 26 Feb 2025 01:08:43 +0100 Subject: [PATCH 244/555] [CLEANUP] Avoid Hungarian notation in `AtRuleSet` (#997) Part of #756 --- src/RuleSet/AtRuleSet.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/RuleSet/AtRuleSet.php b/src/RuleSet/AtRuleSet.php index 601198d6..a1e2ddb7 100644 --- a/src/RuleSet/AtRuleSet.php +++ b/src/RuleSet/AtRuleSet.php @@ -18,7 +18,7 @@ class AtRuleSet extends RuleSet implements AtRule /** * @var string */ - private $sType; + private $type; /** * @var string @@ -26,14 +26,14 @@ class AtRuleSet extends RuleSet implements AtRule private $arguments; /** - * @param string $sType + * @param string $type * @param string $arguments * @param int<0, max> $lineNumber */ - public function __construct($sType, $arguments = '', $lineNumber = 0) + public function __construct($type, $arguments = '', $lineNumber = 0) { parent::__construct($lineNumber); - $this->sType = $sType; + $this->type = $type; $this->arguments = $arguments; } @@ -42,7 +42,7 @@ public function __construct($sType, $arguments = '', $lineNumber = 0) */ public function atRuleName() { - return $this->sType; + return $this->type; } /** @@ -65,7 +65,7 @@ public function render(OutputFormat $outputFormat): string if ($arguments) { $arguments = ' ' . $arguments; } - $result .= "@{$this->sType}$arguments{$outputFormat->spaceBeforeOpeningBrace()}{"; + $result .= "@{$this->type}$arguments{$outputFormat->spaceBeforeOpeningBrace()}{"; $result .= $this->renderRules($outputFormat); $result .= '}'; return $result; From 577b99a0425deae8146b960980621559e55195ce Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 26 Feb 2025 01:20:32 +0100 Subject: [PATCH 245/555] [TASK] Add some basic tests for `CSSString` (#999) Part of #757 --- config/phpstan-baseline.neon | 6 +++ tests/Unit/Value/CSSStringTest.php | 84 ++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 tests/Unit/Value/CSSStringTest.php diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 3f311ad1..0de2d16a 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -414,6 +414,12 @@ parameters: count: 1 path: ../src/RuleSet/RuleSet.php + - + message: '#^Parameters should have "string" types as the only types passed to this method$#' + identifier: typePerfect.narrowPublicClassMethodParamType + count: 1 + path: ../src/Value/CSSString.php + - message: '#^Loose comparison via "\!\=" is not allowed\.$#' identifier: notEqual.notAllowed diff --git a/tests/Unit/Value/CSSStringTest.php b/tests/Unit/Value/CSSStringTest.php new file mode 100644 index 00000000..e88f3554 --- /dev/null +++ b/tests/Unit/Value/CSSStringTest.php @@ -0,0 +1,84 @@ +getString()); + } + + /** + * @test + */ + public function setStringSetsString(): void + { + $subject = new CSSString(''); + $string = 'coffee'; + + $subject->setString($string); + + self::assertSame($string, $subject->getString()); + } + + /** + * @test + */ + public function getLineNoByDefaultReturnsZero(): void + { + $subject = new CSSString(''); + + self::assertSame(0, $subject->getLineNo()); + } + + /** + * @test + */ + public function getLineNoReturnsLineNumberProvidedToConstructor(): void + { + $lineNumber = 42; + + $subject = new CSSString('', $lineNumber); + + self::assertSame($lineNumber, $subject->getLineNo()); + } +} From b3abf1a5deabc4f2c76fa4973e8558c3991cc627 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 26 Feb 2025 01:21:31 +0100 Subject: [PATCH 246/555] [TASK] Use native type declarations in `AtRuleBlockList` (#1000) Part of #811 --- CHANGELOG.md | 2 +- src/CSSList/AtRuleBlockList.php | 14 +++----------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1aea5b3a..b39384b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,7 @@ Please also have a look at our - Make all non-private properties `@internal` (#886) - Use more native type declarations and strict mode (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933, #958, - #964, #967) + #964, #967, #1000) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/src/CSSList/AtRuleBlockList.php b/src/CSSList/AtRuleBlockList.php index 3266b828..8ced1a97 100644 --- a/src/CSSList/AtRuleBlockList.php +++ b/src/CSSList/AtRuleBlockList.php @@ -23,29 +23,21 @@ class AtRuleBlockList extends CSSBlockList implements AtRule private $arguments; /** - * @param string $type - * @param string $arguments * @param int<0, max> $lineNumber */ - public function __construct($type, $arguments = '', $lineNumber = 0) + public function __construct(string $type, string $arguments = '', int $lineNumber = 0) { parent::__construct($lineNumber); $this->type = $type; $this->arguments = $arguments; } - /** - * @return string - */ - public function atRuleName() + public function atRuleName(): string { return $this->type; } - /** - * @return string - */ - public function atRuleArgs() + public function atRuleArgs(): string { return $this->arguments; } From 0642f2b94d2bcfef4102d158ce6eee46f57bec5d Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 26 Feb 2025 11:17:01 +0100 Subject: [PATCH 247/555] [CLEANUP] Avoid Hungarian notation in `DeclarationBlock` (#1002) Also fix some variable name collisions. Part of #756 --- config/phpstan-baseline.neon | 6 ---- src/RuleSet/DeclarationBlock.php | 48 ++++++++++++++++---------------- 2 files changed, 24 insertions(+), 30 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 0de2d16a..535469d6 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -318,12 +318,6 @@ parameters: count: 1 path: ../src/RuleSet/DeclarationBlock.php - - - message: '#^Foreach overwrites \$mSelector with its value variable\.$#' - identifier: foreach.valueOverwrite - count: 1 - path: ../src/RuleSet/DeclarationBlock.php - - message: '#^Loose comparison via "\!\=" is not allowed\.$#' identifier: notEqual.notAllowed diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 4dc0ce2d..5bcc32e1 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -27,7 +27,7 @@ class DeclarationBlock extends RuleSet /** * @var array */ - private $aSelectors = []; + private $selectors = []; /** * @param CSSList|null $list @@ -76,38 +76,38 @@ public static function parse(ParserState $parserState, $list = null) } /** - * @param array|string $mSelector + * @param array|string $selectors * @param CSSList|null $list * * @throws UnexpectedTokenException */ - public function setSelectors($mSelector, $list = null): void + public function setSelectors($selectors, $list = null): void { - if (\is_array($mSelector)) { - $this->aSelectors = $mSelector; + if (\is_array($selectors)) { + $this->selectors = $selectors; } else { - $this->aSelectors = \explode(',', $mSelector); + $this->selectors = \explode(',', $selectors); } - foreach ($this->aSelectors as $key => $mSelector) { - if (!($mSelector instanceof Selector)) { + foreach ($this->selectors as $key => $selector) { + if (!($selector instanceof Selector)) { if ($list === null || !($list instanceof KeyFrame)) { - if (!Selector::isValid($mSelector)) { + if (!Selector::isValid($selector)) { throw new UnexpectedTokenException( "Selector did not match '" . Selector::SELECTOR_VALIDATION_RX . "'.", - $mSelector, + $selectors, 'custom' ); } - $this->aSelectors[$key] = new Selector($mSelector); + $this->selectors[$key] = new Selector($selector); } else { - if (!KeyframeSelector::isValid($mSelector)) { + if (!KeyframeSelector::isValid($selector)) { throw new UnexpectedTokenException( "Selector did not match '" . KeyframeSelector::SELECTOR_VALIDATION_RX . "'.", - $mSelector, + $selector, 'custom' ); } - $this->aSelectors[$key] = new KeyframeSelector($mSelector); + $this->selectors[$key] = new KeyframeSelector($selector); } } } @@ -116,16 +116,16 @@ public function setSelectors($mSelector, $list = null): void /** * Remove one of the selectors of the block. * - * @param Selector|string $mSelector + * @param Selector|string $selectorToRemove */ - public function removeSelector($mSelector): bool + public function removeSelector($selectorToRemove): bool { - if ($mSelector instanceof Selector) { - $mSelector = $mSelector->getSelector(); + if ($selectorToRemove instanceof Selector) { + $selectorToRemove = $selectorToRemove->getSelector(); } - foreach ($this->aSelectors as $key => $selector) { - if ($selector->getSelector() === $mSelector) { - unset($this->aSelectors[$key]); + foreach ($this->selectors as $key => $selector) { + if ($selector->getSelector() === $selectorToRemove) { + unset($this->selectors[$key]); return true; } } @@ -137,7 +137,7 @@ public function removeSelector($mSelector): bool */ public function getSelectors() { - return $this->aSelectors; + return $this->selectors; } /** @@ -154,14 +154,14 @@ public function __toString(): string public function render(OutputFormat $outputFormat): string { $result = $outputFormat->comments($this); - if (\count($this->aSelectors) === 0) { + if (\count($this->selectors) === 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->lineNumber); } $result .= $outputFormat->sBeforeDeclarationBlock; $result .= $outputFormat->implode( $outputFormat->spaceBeforeSelectorSeparator() . ',' . $outputFormat->spaceAfterSelectorSeparator(), - $this->aSelectors + $this->selectors ); $result .= $outputFormat->sAfterDeclarationBlockSelectors; $result .= $outputFormat->spaceBeforeOpeningBrace() . '{'; From a1f9f58c0771142ab20e4188aec767ea9996bfff Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 26 Feb 2025 19:26:46 +0100 Subject: [PATCH 248/555] [TASK] Move up `getAllDeclarationBlocks` to `CSSBlockList` (#996) Move this method up without any changes to the method or its callers. We'll clean this up in later changes. Part of #994. --- src/CSSList/CSSBlockList.php | 13 ++++ src/CSSList/Document.php | 14 ---- tests/Unit/CSSList/CSSBlockListTest.php | 94 +++++++++++++++++++++++++ tests/Unit/CSSList/DocumentTest.php | 88 ----------------------- 4 files changed, 107 insertions(+), 102 deletions(-) diff --git a/src/CSSList/CSSBlockList.php b/src/CSSList/CSSBlockList.php index 76216279..0f2817c3 100644 --- a/src/CSSList/CSSBlockList.php +++ b/src/CSSList/CSSBlockList.php @@ -20,6 +20,19 @@ */ abstract class CSSBlockList extends CSSList { + /** + * Gets all `DeclarationBlock` objects recursively, no matter how deeply nested the selectors are. + * + * @return array + */ + public function getAllDeclarationBlocks(): array + { + /** @var array $result */ + $result = []; + $this->allDeclarationBlocks($result); + return $result; + } + /** * @param array $result */ diff --git a/src/CSSList/Document.php b/src/CSSList/Document.php index 00d4cabd..06a8064f 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -8,7 +8,6 @@ use Sabberworm\CSS\Parsing\ParserState; use Sabberworm\CSS\Parsing\SourceException; use Sabberworm\CSS\Property\Selector; -use Sabberworm\CSS\RuleSet\DeclarationBlock; use Sabberworm\CSS\RuleSet\RuleSet; use Sabberworm\CSS\Value\Value; @@ -30,19 +29,6 @@ public static function parse(ParserState $parserState): Document return $oDocument; } - /** - * Gets all `DeclarationBlock` objects recursively, no matter how deeply nested the selectors are. - * - * @return array - */ - public function getAllDeclarationBlocks(): array - { - /** @var array $result */ - $result = []; - $this->allDeclarationBlocks($result); - return $result; - } - /** * Returns all `RuleSet` objects recursively found in the tree, no matter how deeply nested the rule sets are. * diff --git a/tests/Unit/CSSList/CSSBlockListTest.php b/tests/Unit/CSSList/CSSBlockListTest.php index 9d7ef076..b44f7114 100644 --- a/tests/Unit/CSSList/CSSBlockListTest.php +++ b/tests/Unit/CSSList/CSSBlockListTest.php @@ -6,9 +6,15 @@ use PHPUnit\Framework\TestCase; use Sabberworm\CSS\Comment\Commentable; +use Sabberworm\CSS\CSSList\AtRuleBlockList; use Sabberworm\CSS\CSSList\CSSList; +use Sabberworm\CSS\Property\Charset; +use Sabberworm\CSS\Property\Import; use Sabberworm\CSS\Renderable; +use Sabberworm\CSS\RuleSet\DeclarationBlock; use Sabberworm\CSS\Tests\Unit\CSSList\Fixtures\ConcreteCSSBlockList; +use Sabberworm\CSS\Value\CSSString; +use Sabberworm\CSS\Value\URL; /** * @covers \Sabberworm\CSS\CSSList\CSSBlockList @@ -45,4 +51,92 @@ public function isCSSList(): void self::assertInstanceOf(CSSList::class, $subject); } + + /** + * @test + */ + public function getAllDeclarationBlocksForNoContentsReturnsEmptyArray(): void + { + $subject = new ConcreteCSSBlockList(); + + self::assertSame([], $subject->getAllDeclarationBlocks()); + } + + /** + * @test + */ + public function getAllDeclarationBlocksCanReturnOneDirectDeclarationBlockContent(): void + { + $subject = new ConcreteCSSBlockList(); + + $declarationBlock = new DeclarationBlock(); + $subject->setContents([$declarationBlock]); + + $result = $subject->getAllDeclarationBlocks(); + + self::assertSame([$declarationBlock], $result); + } + + /** + * @test + */ + public function getAllDeclarationBlocksCanReturnMultipleDirectDeclarationBlockContents(): void + { + $subject = new ConcreteCSSBlockList(); + + $declarationBlock1 = new DeclarationBlock(); + $declarationBlock2 = new DeclarationBlock(); + $subject->setContents([$declarationBlock1, $declarationBlock2]); + + $result = $subject->getAllDeclarationBlocks(); + + self::assertSame([$declarationBlock1, $declarationBlock2], $result); + } + + /** + * @test + */ + public function getAllDeclarationBlocksReturnsDeclarationBlocksWithinAtRuleBlockList(): void + { + $subject = new ConcreteCSSBlockList(); + + $declarationBlock = new DeclarationBlock(); + $atRuleBlockList = new AtRuleBlockList('media'); + $atRuleBlockList->setContents([$declarationBlock]); + $subject->setContents([$atRuleBlockList]); + + $result = $subject->getAllDeclarationBlocks(); + + self::assertSame([$declarationBlock], $result); + } + + /** + * @test + */ + public function getAllDeclarationBlocksIgnoresImport(): void + { + $subject = new ConcreteCSSBlockList(); + + $import = new Import(new URL(new CSSString('https://www.example.com/')), ''); + $subject->setContents([$import]); + + $result = $subject->getAllDeclarationBlocks(); + + self::assertSame([], $result); + } + + /** + * @test + */ + public function getAllDeclarationBlocksIgnoresCharset(): void + { + $subject = new ConcreteCSSBlockList(); + + $charset = new Charset(new CSSString('UTF-8')); + $subject->setContents([$charset]); + + $result = $subject->getAllDeclarationBlocks(); + + self::assertSame([], $result); + } } diff --git a/tests/Unit/CSSList/DocumentTest.php b/tests/Unit/CSSList/DocumentTest.php index 07afcf1b..509b4b9d 100644 --- a/tests/Unit/CSSList/DocumentTest.php +++ b/tests/Unit/CSSList/DocumentTest.php @@ -61,94 +61,6 @@ public function isCSSList(): void self::assertInstanceOf(CSSList::class, $subject); } - /** - * @test - */ - public function getAllDeclarationBlocksForNoContentsReturnsEmptyArray(): void - { - $subject = new Document(); - - self::assertSame([], $subject->getAllDeclarationBlocks()); - } - - /** - * @test - */ - public function getAllDeclarationBlocksCanReturnOneDirectDeclarationBlockContent(): void - { - $subject = new Document(); - - $declarationBlock = new DeclarationBlock(); - $subject->setContents([$declarationBlock]); - - $result = $subject->getAllDeclarationBlocks(); - - self::assertSame([$declarationBlock], $result); - } - - /** - * @test - */ - public function getAllDeclarationBlocksCanReturnMultipleDirectDeclarationBlockContents(): void - { - $subject = new Document(); - - $declarationBlock1 = new DeclarationBlock(); - $declarationBlock2 = new DeclarationBlock(); - $subject->setContents([$declarationBlock1, $declarationBlock2]); - - $result = $subject->getAllDeclarationBlocks(); - - self::assertSame([$declarationBlock1, $declarationBlock2], $result); - } - - /** - * @test - */ - public function getAllDeclarationBlocksReturnsDeclarationBlocksWithinAtRuleBlockList(): void - { - $subject = new Document(); - - $declarationBlock = new DeclarationBlock(); - $atRuleBlockList = new AtRuleBlockList('media'); - $atRuleBlockList->setContents([$declarationBlock]); - $subject->setContents([$atRuleBlockList]); - - $result = $subject->getAllDeclarationBlocks(); - - self::assertSame([$declarationBlock], $result); - } - - /** - * @test - */ - public function getAllDeclarationBlocksIgnoresImport(): void - { - $subject = new Document(); - - $import = new Import(new URL(new CSSString('https://www.example.com/')), ''); - $subject->setContents([$import]); - - $result = $subject->getAllDeclarationBlocks(); - - self::assertSame([], $result); - } - - /** - * @test - */ - public function getAllDeclarationBlocksIgnoresCharset(): void - { - $subject = new Document(); - - $charset = new Charset(new CSSString('UTF-8')); - $subject->setContents([$charset]); - - $result = $subject->getAllDeclarationBlocks(); - - self::assertSame([], $result); - } - /** * @test */ From f661c96517ac7a336780c88dbab4818accc3b2fa Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 27 Feb 2025 02:39:20 +0100 Subject: [PATCH 249/555] [CLEANUP] Improve naming of the `getAllDeclarationBlocks` tests (#1007) Fixes #1005 --- tests/Unit/CSSList/CSSBlockListTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Unit/CSSList/CSSBlockListTest.php b/tests/Unit/CSSList/CSSBlockListTest.php index b44f7114..05a50dac 100644 --- a/tests/Unit/CSSList/CSSBlockListTest.php +++ b/tests/Unit/CSSList/CSSBlockListTest.php @@ -55,7 +55,7 @@ public function isCSSList(): void /** * @test */ - public function getAllDeclarationBlocksForNoContentsReturnsEmptyArray(): void + public function getAllDeclarationBlocksWhenNoContentSetReturnsEmptyArray(): void { $subject = new ConcreteCSSBlockList(); @@ -65,7 +65,7 @@ public function getAllDeclarationBlocksForNoContentsReturnsEmptyArray(): void /** * @test */ - public function getAllDeclarationBlocksCanReturnOneDirectDeclarationBlockContent(): void + public function getAllDeclarationBlocksReturnsOneDeclarationBlockDirectlySetAsContent(): void { $subject = new ConcreteCSSBlockList(); @@ -80,7 +80,7 @@ public function getAllDeclarationBlocksCanReturnOneDirectDeclarationBlockContent /** * @test */ - public function getAllDeclarationBlocksCanReturnMultipleDirectDeclarationBlockContents(): void + public function getAllDeclarationBlocksReturnsMultipleDeclarationBlocksDirectlySetAsContents(): void { $subject = new ConcreteCSSBlockList(); From 215c1999ee7e06cda29fb190c322125aabcc9d78 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 27 Feb 2025 02:46:22 +0100 Subject: [PATCH 250/555] [CLEANUP] Avoid Hungarian notation in `Settings` (#1008) We'll change the direct accesses to use the accessors in a later chance. Part of #756 --- src/CSSList/CSSList.php | 6 +++--- src/Parsing/ParserState.php | 14 +++++++------- src/Rule/Rule.php | 2 +- src/RuleSet/DeclarationBlock.php | 2 +- src/RuleSet/RuleSet.php | 2 +- src/Settings.php | 18 +++++++++--------- src/Value/LineName.php | 2 +- src/Value/Value.php | 2 +- tests/Unit/SettingsTest.php | 14 +++++++------- 9 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index bc4f43df..1ec6cd7f 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -74,7 +74,7 @@ public static function parseList(ParserState $parserState, CSSList $list): void if (\is_string($parserState)) { $parserState = new ParserState($parserState, Settings::create()); } - $usesLenientParsing = $parserState->getSettings()->bLenientParsing; + $usesLenientParsing = $parserState->getSettings()->lenientParsing; $comments = []; while (!$parserState->isEnd()) { $comments = \array_merge($comments, $parserState->consumeWhiteSpace()); @@ -138,7 +138,7 @@ private static function parseListItem(ParserState $parserState, CSSList $list) return $atRule; } elseif ($parserState->comes('}')) { if ($isRoot) { - if ($parserState->getSettings()->bLenientParsing) { + if ($parserState->getSettings()->lenientParsing) { return DeclarationBlock::parse($parserState); } else { throw new SourceException('Unopened {', $parserState->currentLine()); @@ -215,7 +215,7 @@ private static function parseAtRule(ParserState $parserState) // Unknown other at rule (font-face or such) $arguments = \trim($parserState->consumeUntil('{', false, true)); if (\substr_count($arguments, '(') != \substr_count($arguments, ')')) { - if ($parserState->getSettings()->bLenientParsing) { + if ($parserState->getSettings()->lenientParsing) { return null; } else { throw new SourceException('Unmatched brace count in media query', $parserState->currentLine()); diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 67bf7ccd..91efa301 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -58,7 +58,7 @@ public function __construct($text, Settings $parserSettings, $lineNumber = 1) $this->parserSettings = $parserSettings; $this->text = $text; $this->lineNumber = $lineNumber; - $this->setCharset($this->parserSettings->sDefaultCharset); + $this->setCharset($this->parserSettings->defaultCharset); } /** @@ -151,7 +151,7 @@ public function parseCharacter($isForIdentifier) { if ($this->peek() === '\\') { if ( - $isForIdentifier && $this->parserSettings->bLenientParsing + $isForIdentifier && $this->parserSettings->lenientParsing && ($this->comes('\\0') || $this->comes('\\9')) ) { // Non-strings can contain \0 or \9 which is an IE hack supported in lenient parsing. @@ -215,7 +215,7 @@ public function consumeWhiteSpace(): array while (\preg_match('/\\s/isSu', $this->peek()) === 1) { $this->consume(1); } - if ($this->parserSettings->bLenientParsing) { + if ($this->parserSettings->lenientParsing) { try { $comment = $this->consumeComment(); } catch (UnexpectedEOFException $e) { @@ -416,7 +416,7 @@ public function backtrack($numberOfCharacters): void */ public function strlen($string): int { - if ($this->parserSettings->bMultibyteSupport) { + if ($this->parserSettings->multibyteSupport) { return \mb_strlen($string, $this->charset); } else { return \strlen($string); @@ -449,7 +449,7 @@ private function substr($offset, $length): string */ private function strtolower($string): string { - if ($this->parserSettings->bMultibyteSupport) { + if ($this->parserSettings->multibyteSupport) { return \mb_strtolower($string, $this->charset); } else { return \strtolower($string); @@ -465,7 +465,7 @@ private function strtolower($string): string */ private function strsplit($string) { - if ($this->parserSettings->bMultibyteSupport) { + if ($this->parserSettings->multibyteSupport) { if ($this->streql($this->charset, 'utf-8')) { $result = \preg_split('//u', $string, -1, PREG_SPLIT_NO_EMPTY); if (!\is_array($result)) { @@ -498,7 +498,7 @@ private function strsplit($string) */ private function strpos($haystack, $needle, $offset) { - if ($this->parserSettings->bMultibyteSupport) { + if ($this->parserSettings->multibyteSupport) { return \mb_strpos($haystack, $needle, $offset, $this->charset); } else { return \strpos($haystack, $needle, $offset); diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index 7a51709a..341f6dfb 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -91,7 +91,7 @@ public static function parse(ParserState $parserState): Rule $parserState->consume(':'); $value = Value::parseValue($parserState, self::listDelimiterForRule($rule->getRule())); $rule->setValue($value); - if ($parserState->getSettings()->bLenientParsing) { + if ($parserState->getSettings()->lenientParsing) { while ($parserState->comes('\\')) { $parserState->consume('\\'); $rule->addIeHack($parserState->consume()); diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 5bcc32e1..7db4d3af 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -61,7 +61,7 @@ public static function parse(ParserState $parserState, $list = null) $parserState->consume(1); } } catch (UnexpectedTokenException $e) { - if ($parserState->getSettings()->bLenientParsing) { + if ($parserState->getSettings()->lenientParsing) { if (!$parserState->comes('}')) { $parserState->consumeUntil('}', false, true); } diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index b5306c39..9f686223 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -64,7 +64,7 @@ public static function parseRuleSet(ParserState $parserState, RuleSet $ruleSet): } while (!$parserState->comes('}')) { $rule = null; - if ($parserState->getSettings()->bLenientParsing) { + if ($parserState->getSettings()->lenientParsing) { try { $rule = Rule::parse($parserState); } catch (UnexpectedTokenException $e) { diff --git a/src/Settings.php b/src/Settings.php index 1765aafa..3bf92bf3 100644 --- a/src/Settings.php +++ b/src/Settings.php @@ -21,7 +21,7 @@ class Settings * * @internal since 8.8.0, will be made private in 9.0.0 */ - public $bMultibyteSupport; + public $multibyteSupport; /** * The default charset for the CSS if no `@charset` declaration is found. Defaults to utf-8. @@ -30,7 +30,7 @@ class Settings * * @internal since 8.8.0, will be made private in 9.0.0 */ - public $sDefaultCharset = 'utf-8'; + public $defaultCharset = 'utf-8'; /** * Whether the parser silently ignore invalid rules instead of choking on them. @@ -39,11 +39,11 @@ class Settings * * @internal since 8.8.0, will be made private in 9.0.0 */ - public $bLenientParsing = true; + public $lenientParsing = true; private function __construct() { - $this->bMultibyteSupport = \extension_loaded('mbstring'); + $this->multibyteSupport = \extension_loaded('mbstring'); } public static function create(): self @@ -59,9 +59,9 @@ public static function create(): self * * @return $this fluent interface */ - public function withMultibyteSupport(bool $bMultibyteSupport = true): self + public function withMultibyteSupport(bool $multibyteSupport = true): self { - $this->bMultibyteSupport = $bMultibyteSupport; + $this->multibyteSupport = $multibyteSupport; return $this; } @@ -70,9 +70,9 @@ public function withMultibyteSupport(bool $bMultibyteSupport = true): self * * @return $this fluent interface */ - public function withDefaultCharset(string $sDefaultCharset): self + public function withDefaultCharset(string $defaultCharset): self { - $this->sDefaultCharset = $sDefaultCharset; + $this->defaultCharset = $defaultCharset; return $this; } @@ -83,7 +83,7 @@ public function withDefaultCharset(string $sDefaultCharset): self */ public function withLenientParsing(bool $usesLenientParsing = true): self { - $this->bLenientParsing = $usesLenientParsing; + $this->lenientParsing = $usesLenientParsing; return $this; } diff --git a/src/Value/LineName.php b/src/Value/LineName.php index 57259dea..b134793f 100644 --- a/src/Value/LineName.php +++ b/src/Value/LineName.php @@ -32,7 +32,7 @@ public static function parse(ParserState $parserState): LineName $parserState->consumeWhiteSpace(); $aNames = []; do { - if ($parserState->getSettings()->bLenientParsing) { + if ($parserState->getSettings()->lenientParsing) { try { $aNames[] = $parserState->parseIdentifier(); } catch (UnexpectedTokenException $e) { diff --git a/src/Value/Value.php b/src/Value/Value.php index 2cb6eeb4..cfc23ca3 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -166,7 +166,7 @@ public static function parsePrimitiveValue(ParserState $parserState) $oValue = Color::parse($parserState); } elseif ($parserState->comes("'") || $parserState->comes('"')) { $oValue = CSSString::parse($parserState); - } elseif ($parserState->comes('progid:') && $parserState->getSettings()->bLenientParsing) { + } elseif ($parserState->comes('progid:') && $parserState->getSettings()->lenientParsing) { $oValue = self::parseMicrosoftFilter($parserState); } elseif ($parserState->comes('[')) { $oValue = LineName::parse($parserState); diff --git a/tests/Unit/SettingsTest.php b/tests/Unit/SettingsTest.php index 3fcbcbb4..ed21c9b3 100644 --- a/tests/Unit/SettingsTest.php +++ b/tests/Unit/SettingsTest.php @@ -48,7 +48,7 @@ public function createReturnsANewInstanceForEachCall(): void */ public function multibyteSupportByDefaultStateOfMbStringExtension(): void { - self::assertSame(\extension_loaded('mbstring'), $this->subject->bMultibyteSupport); + self::assertSame(\extension_loaded('mbstring'), $this->subject->multibyteSupport); } /** @@ -78,7 +78,7 @@ public function withMultibyteSupportSetsMultibyteSupport(bool $value): void { $this->subject->withMultibyteSupport($value); - self::assertSame($value, $this->subject->bMultibyteSupport); + self::assertSame($value, $this->subject->multibyteSupport); } /** @@ -86,7 +86,7 @@ public function withMultibyteSupportSetsMultibyteSupport(bool $value): void */ public function defaultCharsetByDefaultIsUtf8(): void { - self::assertSame('utf-8', $this->subject->sDefaultCharset); + self::assertSame('utf-8', $this->subject->defaultCharset); } /** @@ -105,7 +105,7 @@ public function withDefaultCharsetSetsDefaultCharset(): void $charset = 'ISO-8859-1'; $this->subject->withDefaultCharset($charset); - self::assertSame($charset, $this->subject->sDefaultCharset); + self::assertSame($charset, $this->subject->defaultCharset); } /** @@ -113,7 +113,7 @@ public function withDefaultCharsetSetsDefaultCharset(): void */ public function lenientParsingByDefaultIsTrue(): void { - self::assertTrue($this->subject->bLenientParsing); + self::assertTrue($this->subject->lenientParsing); } /** @@ -132,7 +132,7 @@ public function withLenientParsingSetsLenientParsing(bool $value): void { $this->subject->withLenientParsing($value); - self::assertSame($value, $this->subject->bLenientParsing); + self::assertSame($value, $this->subject->lenientParsing); } /** @@ -150,6 +150,6 @@ public function beStrictSetsLenientParsingToFalse(): void { $this->subject->beStrict(); - self::assertFalse($this->subject->bLenientParsing); + self::assertFalse($this->subject->lenientParsing); } } From 0bfa3f3301eb56b0ca7e895a59bbf15f90819163 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 27 Feb 2025 10:09:07 +0100 Subject: [PATCH 251/555] [TASK] Deprecate `__toString()` (#1006) Part of #998 --- CHANGELOG.md | 1 + src/CSSList/AtRuleBlockList.php | 3 +++ src/CSSList/CSSList.php | 3 +++ src/CSSList/KeyFrame.php | 3 +++ src/Comment/Comment.php | 3 +++ src/OutputFormatter.php | 2 +- src/Property/CSSNamespace.php | 3 +++ src/Property/Charset.php | 3 +++ src/Property/Import.php | 3 +++ src/Property/Selector.php | 3 +++ src/Renderable.php | 3 +++ src/Rule/Rule.php | 3 +++ src/RuleSet/AtRuleSet.php | 3 +++ src/RuleSet/DeclarationBlock.php | 2 ++ src/RuleSet/RuleSet.php | 3 +++ src/Value/CSSFunction.php | 3 +++ src/Value/CSSString.php | 3 +++ src/Value/Color.php | 3 +++ src/Value/LineName.php | 3 +++ src/Value/Size.php | 3 +++ src/Value/URL.php | 3 +++ src/Value/ValueList.php | 3 +++ tests/Functional/Comment/CommentTest.php | 13 --------- tests/FunctionalDeprecated/.gitkeep | 0 .../Comment/CommentTest.php | 27 +++++++++++++++++++ 25 files changed, 88 insertions(+), 14 deletions(-) delete mode 100644 tests/FunctionalDeprecated/.gitkeep create mode 100644 tests/FunctionalDeprecated/Comment/CommentTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index b39384b6..6665202c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ Please also have a look at our ### Deprecated +- Deprecate `__toString()` (#1006) - `OutputFormat` properties for space around list separators as an array (#880) ### Removed diff --git a/src/CSSList/AtRuleBlockList.php b/src/CSSList/AtRuleBlockList.php index 8ced1a97..892acfd4 100644 --- a/src/CSSList/AtRuleBlockList.php +++ b/src/CSSList/AtRuleBlockList.php @@ -42,6 +42,9 @@ public function atRuleArgs(): string return $this->arguments; } + /** + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. + */ public function __toString(): string { return $this->render(new OutputFormat()); diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 1ec6cd7f..77831fd3 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -402,6 +402,9 @@ public function removeDeclarationBlockBySelector($selectors, $removeAll = false) } } + /** + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. + */ public function __toString(): string { return $this->render(new OutputFormat()); diff --git a/src/CSSList/KeyFrame.php b/src/CSSList/KeyFrame.php index 1fce204b..3177ac19 100644 --- a/src/CSSList/KeyFrame.php +++ b/src/CSSList/KeyFrame.php @@ -51,6 +51,9 @@ public function getAnimationName() return $this->animationName; } + /** + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. + */ public function __toString(): string { return $this->render(new OutputFormat()); diff --git a/src/Comment/Comment.php b/src/Comment/Comment.php index 9d8b5ed3..761d6d82 100644 --- a/src/Comment/Comment.php +++ b/src/Comment/Comment.php @@ -50,6 +50,9 @@ public function setComment(string $commentText): void $this->commentText = $commentText; } + /** + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. + */ public function __toString(): string { return $this->render(new OutputFormat()); diff --git a/src/OutputFormatter.php b/src/OutputFormatter.php index 6fcfc2d8..1e39d210 100644 --- a/src/OutputFormatter.php +++ b/src/OutputFormatter.php @@ -159,7 +159,7 @@ public function safely(callable $callable): ?string } /** - * Clone of the `implode` function, but calls `render` with the current output format instead of `__toString()`. + * Clone of the `implode` function, but calls `render` with the current output format. * * @param array $values */ diff --git a/src/Property/CSSNamespace.php b/src/Property/CSSNamespace.php index 0d6750f0..7a4f64a9 100644 --- a/src/Property/CSSNamespace.php +++ b/src/Property/CSSNamespace.php @@ -54,6 +54,9 @@ public function getLineNo(): int return $this->lineNumber; } + /** + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. + */ public function __toString(): string { return $this->render(new OutputFormat()); diff --git a/src/Property/Charset.php b/src/Property/Charset.php index d0bb4276..dea13e16 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -71,6 +71,9 @@ public function getCharset() return $this->charset->getString(); } + /** + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. + */ public function __toString(): string { return $this->render(new OutputFormat()); diff --git a/src/Property/Import.php b/src/Property/Import.php index 8240e28f..520e0120 100644 --- a/src/Property/Import.php +++ b/src/Property/Import.php @@ -72,6 +72,9 @@ public function getLocation() return $this->location; } + /** + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. + */ public function __toString(): string { return $this->render(new OutputFormat()); diff --git a/src/Property/Selector.php b/src/Property/Selector.php index c7226d3c..11e4fd8a 100644 --- a/src/Property/Selector.php +++ b/src/Property/Selector.php @@ -113,6 +113,9 @@ public function setSelector($selector): void $this->specificity = null; } + /** + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. + */ public function __toString(): string { return $this->getSelector(); diff --git a/src/Renderable.php b/src/Renderable.php index 3a833f9a..ff96d66a 100644 --- a/src/Renderable.php +++ b/src/Renderable.php @@ -6,6 +6,9 @@ interface Renderable { + /** + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. + */ public function __toString(): string; public function render(OutputFormat $outputFormat): string; diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index 341f6dfb..4ac75724 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -260,6 +260,9 @@ public function getIsImportant() return $this->isImportant; } + /** + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. + */ public function __toString(): string { return $this->render(new OutputFormat()); diff --git a/src/RuleSet/AtRuleSet.php b/src/RuleSet/AtRuleSet.php index a1e2ddb7..9f7a1705 100644 --- a/src/RuleSet/AtRuleSet.php +++ b/src/RuleSet/AtRuleSet.php @@ -53,6 +53,9 @@ public function atRuleArgs() return $this->arguments; } + /** + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. + */ public function __toString(): string { return $this->render(new OutputFormat()); diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 7db4d3af..7e0dfc1b 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -142,6 +142,8 @@ public function getSelectors() /** * @throws OutputException + * + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. */ public function __toString(): string { diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 9f686223..5364e484 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -255,6 +255,9 @@ public function removeRule($mRule): void } } + /** + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. + */ public function __toString(): string { return $this->render(new OutputFormat()); diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index fa435549..b32c9c2f 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -105,6 +105,9 @@ public function getArguments() return $this->aComponents; } + /** + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. + */ public function __toString(): string { return $this->render(new OutputFormat()); diff --git a/src/Value/CSSString.php b/src/Value/CSSString.php index 7ccdedaa..8f87c6e1 100644 --- a/src/Value/CSSString.php +++ b/src/Value/CSSString.php @@ -90,6 +90,9 @@ public function getString() return $this->string; } + /** + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. + */ public function __toString(): string { return $this->render(new OutputFormat()); diff --git a/src/Value/Color.php b/src/Value/Color.php index 76b81a04..81cd7554 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -224,6 +224,9 @@ public function getColorDescription() return $this->getName(); } + /** + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. + */ public function __toString(): string { return $this->render(new OutputFormat()); diff --git a/src/Value/LineName.php b/src/Value/LineName.php index b134793f..9af9149e 100644 --- a/src/Value/LineName.php +++ b/src/Value/LineName.php @@ -49,6 +49,9 @@ public static function parse(ParserState $parserState): LineName return new LineName($aNames, $parserState->currentLine()); } + /** + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. + */ public function __toString(): string { return $this->render(new OutputFormat()); diff --git a/src/Value/Size.php b/src/Value/Size.php index 748e2849..36a2ac6e 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -210,6 +210,9 @@ public function isRelative(): bool return false; } + /** + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. + */ public function __toString(): string { return $this->render(new OutputFormat()); diff --git a/src/Value/URL.php b/src/Value/URL.php index 5353f966..15eb52e0 100644 --- a/src/Value/URL.php +++ b/src/Value/URL.php @@ -76,6 +76,9 @@ public function getURL() return $this->oURL; } + /** + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. + */ public function __toString(): string { return $this->render(new OutputFormat()); diff --git a/src/Value/ValueList.php b/src/Value/ValueList.php index 28c30368..5c7b8f84 100644 --- a/src/Value/ValueList.php +++ b/src/Value/ValueList.php @@ -83,6 +83,9 @@ public function setListSeparator($sSeparator): void $this->sSeparator = $sSeparator; } + /** + * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. + */ public function __toString(): string { return $this->render(new OutputFormat()); diff --git a/tests/Functional/Comment/CommentTest.php b/tests/Functional/Comment/CommentTest.php index 100d2531..bbfa06a7 100644 --- a/tests/Functional/Comment/CommentTest.php +++ b/tests/Functional/Comment/CommentTest.php @@ -13,19 +13,6 @@ */ final class CommentTest extends TestCase { - /** - * @test - */ - public function toStringRendersCommentEnclosedInCommentDelimiters(): void - { - $comment = 'There is no spoon.'; - $subject = new Comment(); - - $subject->setComment($comment); - - self::assertSame('/*' . $comment . '*/', (string) $subject); - } - /** * @test */ diff --git a/tests/FunctionalDeprecated/.gitkeep b/tests/FunctionalDeprecated/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/FunctionalDeprecated/Comment/CommentTest.php b/tests/FunctionalDeprecated/Comment/CommentTest.php new file mode 100644 index 00000000..8d9c1d31 --- /dev/null +++ b/tests/FunctionalDeprecated/Comment/CommentTest.php @@ -0,0 +1,27 @@ +setComment($comment); + + self::assertSame('/*' . $comment . '*/', (string) $subject); + } +} From e1f8bd3a118cc9a0d8896352b6653cfa9cd0721e Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 27 Feb 2025 10:24:13 +0100 Subject: [PATCH 252/555] [CLEANUP] Refactor `::getAllDeclarationBlocks()` (#990) Part of #994. --- src/CSSList/CSSBlockList.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/CSSList/CSSBlockList.php b/src/CSSList/CSSBlockList.php index 0f2817c3..cae6d9f0 100644 --- a/src/CSSList/CSSBlockList.php +++ b/src/CSSList/CSSBlockList.php @@ -23,13 +23,20 @@ abstract class CSSBlockList extends CSSList /** * Gets all `DeclarationBlock` objects recursively, no matter how deeply nested the selectors are. * - * @return array + * @return list */ public function getAllDeclarationBlocks(): array { - /** @var array $result */ $result = []; - $this->allDeclarationBlocks($result); + + foreach ($this->contents as $item) { + if ($item instanceof DeclarationBlock) { + $result[] = $item; + } elseif ($item instanceof CSSBlockList) { + $result = \array_merge($result, $item->getAllDeclarationBlocks()); + } + } + return $result; } From 09c8f831ab6eaf20cd9a4175092582ea9e0fb059 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 27 Feb 2025 12:11:53 +0100 Subject: [PATCH 253/555] [FEATURE] Add internal getters to `Settings` and use them (#1013) Also make a type annotation more specific along the way. --- src/CSSList/CSSList.php | 6 +++--- src/Parsing/ParserState.php | 14 +++++++------- src/Rule/Rule.php | 2 +- src/RuleSet/DeclarationBlock.php | 2 +- src/RuleSet/RuleSet.php | 2 +- src/Settings.php | 33 +++++++++++++++++++++++++++++++- src/Value/LineName.php | 2 +- src/Value/Value.php | 2 +- tests/Unit/SettingsTest.php | 14 +++++++------- 9 files changed, 54 insertions(+), 23 deletions(-) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 77831fd3..cfc5d7bb 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -74,7 +74,7 @@ public static function parseList(ParserState $parserState, CSSList $list): void if (\is_string($parserState)) { $parserState = new ParserState($parserState, Settings::create()); } - $usesLenientParsing = $parserState->getSettings()->lenientParsing; + $usesLenientParsing = $parserState->getSettings()->usesLenientParsing(); $comments = []; while (!$parserState->isEnd()) { $comments = \array_merge($comments, $parserState->consumeWhiteSpace()); @@ -138,7 +138,7 @@ private static function parseListItem(ParserState $parserState, CSSList $list) return $atRule; } elseif ($parserState->comes('}')) { if ($isRoot) { - if ($parserState->getSettings()->lenientParsing) { + if ($parserState->getSettings()->usesLenientParsing()) { return DeclarationBlock::parse($parserState); } else { throw new SourceException('Unopened {', $parserState->currentLine()); @@ -215,7 +215,7 @@ private static function parseAtRule(ParserState $parserState) // Unknown other at rule (font-face or such) $arguments = \trim($parserState->consumeUntil('{', false, true)); if (\substr_count($arguments, '(') != \substr_count($arguments, ')')) { - if ($parserState->getSettings()->lenientParsing) { + if ($parserState->getSettings()->usesLenientParsing()) { return null; } else { throw new SourceException('Unmatched brace count in media query', $parserState->currentLine()); diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 91efa301..b4885c17 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -58,7 +58,7 @@ public function __construct($text, Settings $parserSettings, $lineNumber = 1) $this->parserSettings = $parserSettings; $this->text = $text; $this->lineNumber = $lineNumber; - $this->setCharset($this->parserSettings->defaultCharset); + $this->setCharset($this->parserSettings->getDefaultCharset()); } /** @@ -151,7 +151,7 @@ public function parseCharacter($isForIdentifier) { if ($this->peek() === '\\') { if ( - $isForIdentifier && $this->parserSettings->lenientParsing + $isForIdentifier && $this->parserSettings->usesLenientParsing() && ($this->comes('\\0') || $this->comes('\\9')) ) { // Non-strings can contain \0 or \9 which is an IE hack supported in lenient parsing. @@ -215,7 +215,7 @@ public function consumeWhiteSpace(): array while (\preg_match('/\\s/isSu', $this->peek()) === 1) { $this->consume(1); } - if ($this->parserSettings->lenientParsing) { + if ($this->parserSettings->usesLenientParsing()) { try { $comment = $this->consumeComment(); } catch (UnexpectedEOFException $e) { @@ -416,7 +416,7 @@ public function backtrack($numberOfCharacters): void */ public function strlen($string): int { - if ($this->parserSettings->multibyteSupport) { + if ($this->parserSettings->hasMultibyteSupport()) { return \mb_strlen($string, $this->charset); } else { return \strlen($string); @@ -449,7 +449,7 @@ private function substr($offset, $length): string */ private function strtolower($string): string { - if ($this->parserSettings->multibyteSupport) { + if ($this->parserSettings->hasMultibyteSupport()) { return \mb_strtolower($string, $this->charset); } else { return \strtolower($string); @@ -465,7 +465,7 @@ private function strtolower($string): string */ private function strsplit($string) { - if ($this->parserSettings->multibyteSupport) { + if ($this->parserSettings->hasMultibyteSupport()) { if ($this->streql($this->charset, 'utf-8')) { $result = \preg_split('//u', $string, -1, PREG_SPLIT_NO_EMPTY); if (!\is_array($result)) { @@ -498,7 +498,7 @@ private function strsplit($string) */ private function strpos($haystack, $needle, $offset) { - if ($this->parserSettings->multibyteSupport) { + if ($this->parserSettings->hasMultibyteSupport()) { return \mb_strpos($haystack, $needle, $offset, $this->charset); } else { return \strpos($haystack, $needle, $offset); diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index 4ac75724..45cf24d0 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -91,7 +91,7 @@ public static function parse(ParserState $parserState): Rule $parserState->consume(':'); $value = Value::parseValue($parserState, self::listDelimiterForRule($rule->getRule())); $rule->setValue($value); - if ($parserState->getSettings()->lenientParsing) { + if ($parserState->getSettings()->usesLenientParsing()) { while ($parserState->comes('\\')) { $parserState->consume('\\'); $rule->addIeHack($parserState->consume()); diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 7e0dfc1b..ff6c2c24 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -61,7 +61,7 @@ public static function parse(ParserState $parserState, $list = null) $parserState->consume(1); } } catch (UnexpectedTokenException $e) { - if ($parserState->getSettings()->lenientParsing) { + if ($parserState->getSettings()->usesLenientParsing()) { if (!$parserState->comes('}')) { $parserState->consumeUntil('}', false, true); } diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 5364e484..991c5043 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -64,7 +64,7 @@ public static function parseRuleSet(ParserState $parserState, RuleSet $ruleSet): } while (!$parserState->comes('}')) { $rule = null; - if ($parserState->getSettings()->lenientParsing) { + if ($parserState->getSettings()->usesLenientParsing()) { try { $rule = Rule::parse($parserState); } catch (UnexpectedTokenException $e) { diff --git a/src/Settings.php b/src/Settings.php index 3bf92bf3..965f430a 100644 --- a/src/Settings.php +++ b/src/Settings.php @@ -26,7 +26,7 @@ class Settings /** * The default charset for the CSS if no `@charset` declaration is found. Defaults to utf-8. * - * @var string + * @var non-empty-string * * @internal since 8.8.0, will be made private in 9.0.0 */ @@ -62,17 +62,21 @@ public static function create(): self public function withMultibyteSupport(bool $multibyteSupport = true): self { $this->multibyteSupport = $multibyteSupport; + return $this; } /** * Sets the charset to be used if the CSS does not contain an `@charset` declaration. * + * @param non-empty-string $defaultCharset + * * @return $this fluent interface */ public function withDefaultCharset(string $defaultCharset): self { $this->defaultCharset = $defaultCharset; + return $this; } @@ -84,6 +88,7 @@ public function withDefaultCharset(string $defaultCharset): self public function withLenientParsing(bool $usesLenientParsing = true): self { $this->lenientParsing = $usesLenientParsing; + return $this; } @@ -96,4 +101,30 @@ public function beStrict(): self { return $this->withLenientParsing(false); } + + /** + * @internal + */ + public function hasMultibyteSupport(): bool + { + return $this->multibyteSupport; + } + + /** + * @return non-empty-string + * + * @internal + */ + public function getDefaultCharset(): string + { + return $this->defaultCharset; + } + + /** + * @internal + */ + public function usesLenientParsing(): bool + { + return $this->lenientParsing; + } } diff --git a/src/Value/LineName.php b/src/Value/LineName.php index 9af9149e..379aae87 100644 --- a/src/Value/LineName.php +++ b/src/Value/LineName.php @@ -32,7 +32,7 @@ public static function parse(ParserState $parserState): LineName $parserState->consumeWhiteSpace(); $aNames = []; do { - if ($parserState->getSettings()->lenientParsing) { + if ($parserState->getSettings()->usesLenientParsing()) { try { $aNames[] = $parserState->parseIdentifier(); } catch (UnexpectedTokenException $e) { diff --git a/src/Value/Value.php b/src/Value/Value.php index cfc23ca3..dcd8686a 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -166,7 +166,7 @@ public static function parsePrimitiveValue(ParserState $parserState) $oValue = Color::parse($parserState); } elseif ($parserState->comes("'") || $parserState->comes('"')) { $oValue = CSSString::parse($parserState); - } elseif ($parserState->comes('progid:') && $parserState->getSettings()->lenientParsing) { + } elseif ($parserState->comes('progid:') && $parserState->getSettings()->usesLenientParsing()) { $oValue = self::parseMicrosoftFilter($parserState); } elseif ($parserState->comes('[')) { $oValue = LineName::parse($parserState); diff --git a/tests/Unit/SettingsTest.php b/tests/Unit/SettingsTest.php index ed21c9b3..63f94521 100644 --- a/tests/Unit/SettingsTest.php +++ b/tests/Unit/SettingsTest.php @@ -48,7 +48,7 @@ public function createReturnsANewInstanceForEachCall(): void */ public function multibyteSupportByDefaultStateOfMbStringExtension(): void { - self::assertSame(\extension_loaded('mbstring'), $this->subject->multibyteSupport); + self::assertSame(\extension_loaded('mbstring'), $this->subject->hasMultibyteSupport()); } /** @@ -78,7 +78,7 @@ public function withMultibyteSupportSetsMultibyteSupport(bool $value): void { $this->subject->withMultibyteSupport($value); - self::assertSame($value, $this->subject->multibyteSupport); + self::assertSame($value, $this->subject->hasMultibyteSupport()); } /** @@ -86,7 +86,7 @@ public function withMultibyteSupportSetsMultibyteSupport(bool $value): void */ public function defaultCharsetByDefaultIsUtf8(): void { - self::assertSame('utf-8', $this->subject->defaultCharset); + self::assertSame('utf-8', $this->subject->getDefaultCharset()); } /** @@ -105,7 +105,7 @@ public function withDefaultCharsetSetsDefaultCharset(): void $charset = 'ISO-8859-1'; $this->subject->withDefaultCharset($charset); - self::assertSame($charset, $this->subject->defaultCharset); + self::assertSame($charset, $this->subject->getDefaultCharset()); } /** @@ -113,7 +113,7 @@ public function withDefaultCharsetSetsDefaultCharset(): void */ public function lenientParsingByDefaultIsTrue(): void { - self::assertTrue($this->subject->lenientParsing); + self::assertTrue($this->subject->usesLenientParsing()); } /** @@ -132,7 +132,7 @@ public function withLenientParsingSetsLenientParsing(bool $value): void { $this->subject->withLenientParsing($value); - self::assertSame($value, $this->subject->lenientParsing); + self::assertSame($value, $this->subject->usesLenientParsing()); } /** @@ -150,6 +150,6 @@ public function beStrictSetsLenientParsingToFalse(): void { $this->subject->beStrict(); - self::assertFalse($this->subject->lenientParsing); + self::assertFalse($this->subject->usesLenientParsing()); } } From a06211d5373ad77106be987f9bedb138dd9d7cf8 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 27 Feb 2025 12:16:50 +0100 Subject: [PATCH 254/555] [TASK] Make the `OutputFormat` properties private (#1014) Also use the accessors as necessary. Fixes #882 --- src/CSSList/AtRuleBlockList.php | 4 +- src/OutputFormat.php | 100 ++++++++----------------------- src/OutputFormatter.php | 2 +- src/RuleSet/DeclarationBlock.php | 6 +- 4 files changed, 31 insertions(+), 81 deletions(-) diff --git a/src/CSSList/AtRuleBlockList.php b/src/CSSList/AtRuleBlockList.php index 892acfd4..577c6b8f 100644 --- a/src/CSSList/AtRuleBlockList.php +++ b/src/CSSList/AtRuleBlockList.php @@ -53,7 +53,7 @@ public function __toString(): string public function render(OutputFormat $outputFormat): string { $result = $outputFormat->comments($this); - $result .= $outputFormat->sBeforeAtRuleBlock; + $result .= $outputFormat->getBeforeAtRuleBlock(); $arguments = $this->arguments; if ($arguments) { $arguments = ' ' . $arguments; @@ -61,7 +61,7 @@ public function render(OutputFormat $outputFormat): string $result .= "@{$this->type}$arguments{$outputFormat->spaceBeforeOpeningBrace()}{"; $result .= $this->renderListContents($outputFormat); $result .= '}'; - $result .= $outputFormat->sAfterAtRuleBlock; + $result .= $outputFormat->getAfterAtRuleBlock(); return $result; } diff --git a/src/OutputFormat.php b/src/OutputFormat.php index caec89a5..30c2e8fe 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -10,19 +10,15 @@ 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 $stringQuotingType = '"'; + private $stringQuotingType = '"'; /** * Output RGB colors in hash notation if possible * * @var bool - * - * @internal since 8.8.0, will be made private in 9.0.0 */ - public $bRGBHashNotation = true; + private $bRGBHashNotation = true; /** * Declaration format @@ -30,10 +26,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; + private $bSemicolonAfterLastRule = true; /** * Spacing @@ -43,177 +37,133 @@ class OutputFormat * (e.g. `$outputFormat->set('Space*Rules', "\n");`) * * @var string - * - * @internal since 8.8.0, will be made private in 9.0.0 */ - public $sSpaceAfterRuleName = ' '; + private $sSpaceAfterRuleName = ' '; /** * @var string - * - * @internal since 8.8.0, will be made private in 9.0.0 */ - public $sSpaceBeforeRules = ''; + private $sSpaceBeforeRules = ''; /** * @var string - * - * @internal since 8.8.0, will be made private in 9.0.0 */ - public $sSpaceAfterRules = ''; + private $sSpaceAfterRules = ''; /** * @var string - * - * @internal since 8.8.0, will be made private in 9.0.0 */ - public $sSpaceBetweenRules = ''; + private $sSpaceBetweenRules = ''; /** * @var string - * - * @internal since 8.8.0, will be made private in 9.0.0 */ - public $sSpaceBeforeBlocks = ''; + private $sSpaceBeforeBlocks = ''; /** * @var string - * - * @internal since 8.8.0, will be made private in 9.0.0 */ - public $sSpaceAfterBlocks = ''; + private $sSpaceAfterBlocks = ''; /** * @var string - * - * @internal since 8.8.0, will be made private in 9.0.0 */ - public $sSpaceBetweenBlocks = "\n"; + private $sSpaceBetweenBlocks = "\n"; /** * 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 = ''; + private $sBeforeAtRuleBlock = ''; /** * @var string - * - * @internal since 8.8.0, will be made private in 9.0.0 */ - public $sAfterAtRuleBlock = ''; + private $sAfterAtRuleBlock = ''; /** * 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 = ''; + private $sSpaceBeforeSelectorSeparator = ''; /** * @var string - * - * @internal since 8.8.0, will be made private in 9.0.0 */ - public $sSpaceAfterSelectorSeparator = ' '; + private $sSpaceAfterSelectorSeparator = ' '; /** * This is what’s inserted before the separator in value lists, by default. * * @var string - * - * @internal since 8.8.0, will be made private in 9.0.0 */ - public $sSpaceBeforeListArgumentSeparator = ''; + private $sSpaceBeforeListArgumentSeparator = ''; /** * 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 = []; + private $aSpaceBeforeListArgumentSeparators = []; /** * This is what’s inserted after the separator in value lists, by default. * * @var string - * - * @internal since 8.8.0, will be made private in 9.0.0 */ - public $sSpaceAfterListArgumentSeparator = ''; + private $sSpaceAfterListArgumentSeparator = ''; /** * 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 = []; + private $aSpaceAfterListArgumentSeparators = []; /** * @var string - * - * @internal since 8.8.0, will be made private in 9.0.0 */ - public $sSpaceBeforeOpeningBrace = ' '; + private $sSpaceBeforeOpeningBrace = ' '; /** * Content injected in and around declaration blocks. * * @var string - * - * @internal since 8.8.0, will be made private in 9.0.0 */ - public $sBeforeDeclarationBlock = ''; + private $sBeforeDeclarationBlock = ''; /** * @var string - * - * @internal since 8.8.0, will be made private in 9.0.0 */ - public $sAfterDeclarationBlockSelectors = ''; + private $sAfterDeclarationBlockSelectors = ''; /** * @var string - * - * @internal since 8.8.0, will be made private in 9.0.0 */ - public $sAfterDeclarationBlock = ''; + private $sAfterDeclarationBlock = ''; /** * 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"; + private $sIndentation = "\t"; /** * Output exceptions. * * @var bool - * - * @internal since 8.8.0, will be made private in 9.0.0 */ - public $bIgnoreExceptions = false; + private $bIgnoreExceptions = false; /** * Render comments for lists and RuleSets * * @var bool - * - * @internal since 8.8.0, will be made private in 9.0.0 */ - public $bRenderComments = false; + private $bRenderComments = false; /** * @var OutputFormatter|null diff --git a/src/OutputFormatter.php b/src/OutputFormatter.php index 1e39d210..7b61b0ab 100644 --- a/src/OutputFormatter.php +++ b/src/OutputFormatter.php @@ -225,6 +225,6 @@ private function prepareSpace(string $spaceString): string private function indent(): string { - return \str_repeat($this->outputFormat->sIndentation, $this->outputFormat->getIndentationLevel()); + return \str_repeat($this->outputFormat->getIndentation(), $this->outputFormat->getIndentationLevel()); } } diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index ff6c2c24..5f914d1d 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -160,16 +160,16 @@ public function render(OutputFormat $outputFormat): string // If all the selectors have been removed, this declaration block becomes invalid throw new OutputException('Attempt to print declaration block with missing selector', $this->lineNumber); } - $result .= $outputFormat->sBeforeDeclarationBlock; + $result .= $outputFormat->getBeforeDeclarationBlock(); $result .= $outputFormat->implode( $outputFormat->spaceBeforeSelectorSeparator() . ',' . $outputFormat->spaceAfterSelectorSeparator(), $this->selectors ); - $result .= $outputFormat->sAfterDeclarationBlockSelectors; + $result .= $outputFormat->getAfterDeclarationBlockSelectors(); $result .= $outputFormat->spaceBeforeOpeningBrace() . '{'; $result .= $this->renderRules($outputFormat); $result .= '}'; - $result .= $outputFormat->sAfterDeclarationBlock; + $result .= $outputFormat->getAfterDeclarationBlock(); return $result; } } From e0392d0e9424363c6fd09a7447be067262b567d4 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 27 Feb 2025 12:47:23 +0100 Subject: [PATCH 255/555] [TASK] Deprecate greedy calculation of selector specificity (#1018) This constructor parameter is not used, and having the specificity calculation always done lazily is not a problem. --- CHANGELOG.md | 1 + src/Property/Selector.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6665202c..3a223514 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ Please also have a look at our ### Deprecated +- Deprecate greedy calculation of selector specificity (#1018) - Deprecate `__toString()` (#1006) - `OutputFormat` properties for space around list separators as an array (#880) diff --git a/src/Property/Selector.php b/src/Property/Selector.php index 11e4fd8a..63080a6a 100644 --- a/src/Property/Selector.php +++ b/src/Property/Selector.php @@ -86,7 +86,7 @@ public static function isValid($selector) /** * @param string $selector - * @param bool $calculateSpecificity + * @param bool $calculateSpecificity @deprecated since V8.8.0, will be removed in V9.0.0 */ public function __construct($selector, $calculateSpecificity = false) { From 571b91be2ca59e4c50e3fd5aafa8b68129dcd2bb Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 27 Feb 2025 12:50:23 +0100 Subject: [PATCH 256/555] [TASK] Make the `Settings` properties `private` (#1019) --- src/Settings.php | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/Settings.php b/src/Settings.php index 965f430a..a26d10e9 100644 --- a/src/Settings.php +++ b/src/Settings.php @@ -18,28 +18,22 @@ 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 $multibyteSupport; + private $multibyteSupport; /** * The default charset for the CSS if no `@charset` declaration is found. Defaults to utf-8. * * @var non-empty-string - * - * @internal since 8.8.0, will be made private in 9.0.0 */ - public $defaultCharset = 'utf-8'; + private $defaultCharset = 'utf-8'; /** * 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 $lenientParsing = true; + private $lenientParsing = true; private function __construct() { From 9192d954f675633cb6f213a26df9eba5adb5311c Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 27 Feb 2025 12:58:11 +0100 Subject: [PATCH 257/555] [TASK] Add the first basic functional tests for `Parser` (#1020) Part of #757 --- tests/Functional/ParserTest.php | 39 +++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 tests/Functional/ParserTest.php diff --git a/tests/Functional/ParserTest.php b/tests/Functional/ParserTest.php new file mode 100644 index 00000000..982bb3a2 --- /dev/null +++ b/tests/Functional/ParserTest.php @@ -0,0 +1,39 @@ +parse(); + + self::assertInstanceOf(Document::class, $result); + } + + /** + * @test + */ + public function parseWithOneRuleSetReturnsDocument(): void + { + $parser = new Parser('.thing { }'); + + $result = $parser->parse(); + + self::assertInstanceOf(Document::class, $result); + } +} From 58ef076bffefc9bc9a77dba4089473baf0855dff Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 27 Feb 2025 19:54:10 +0100 Subject: [PATCH 258/555] [CLEANUP] Avoid Hungarian notation for `isFirst` (#1023) Part of #756 --- src/RuleSet/RuleSet.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 991c5043..076ccd49 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -269,7 +269,7 @@ public function __toString(): string protected function renderRules(OutputFormat $outputFormat) { $result = ''; - $bIsFirst = true; + $isFirst = true; $oNextLevel = $outputFormat->nextLevel(); foreach ($this->aRules as $aRules) { foreach ($aRules as $rule) { @@ -279,8 +279,8 @@ protected function renderRules(OutputFormat $outputFormat) if ($sRendered === null) { continue; } - if ($bIsFirst) { - $bIsFirst = false; + if ($isFirst) { + $isFirst = false; $result .= $oNextLevel->spaceBeforeRules(); } else { $result .= $oNextLevel->spaceBetweenRules(); @@ -289,7 +289,7 @@ protected function renderRules(OutputFormat $outputFormat) } } - if (!$bIsFirst) { + if (!$isFirst) { // Had some output $result .= $outputFormat->spaceAfterRules(); } From c22d738409bc4e11c9124d2c7bb65eca52b9f29b Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 27 Feb 2025 20:24:03 +0100 Subject: [PATCH 259/555] [CLEANUP] Avoid Hungarian notation for `rules` (#1024) Part of #756 --- src/RuleSet/RuleSet.php | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 076ccd49..c7380ccb 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -27,7 +27,7 @@ abstract class RuleSet implements Renderable, Commentable /** * @var array */ - private $aRules = []; + private $rules = []; /** * @var int<0, max> @@ -104,14 +104,14 @@ public function getLineNo(): int public function addRule(Rule $rule, ?Rule $oSibling = null): void { $sRule = $rule->getRule(); - if (!isset($this->aRules[$sRule])) { - $this->aRules[$sRule] = []; + if (!isset($this->rules[$sRule])) { + $this->rules[$sRule] = []; } - $position = \count($this->aRules[$sRule]); + $position = \count($this->rules[$sRule]); if ($oSibling !== null) { - $iSiblingPos = \array_search($oSibling, $this->aRules[$sRule], true); + $iSiblingPos = \array_search($oSibling, $this->rules[$sRule], true); if ($iSiblingPos !== false) { $position = $iSiblingPos; $rule->setPosition($oSibling->getLineNo(), $oSibling->getColNo() - 1); @@ -127,7 +127,7 @@ public function addRule(Rule $rule, ?Rule $oSibling = null): void } } - \array_splice($this->aRules[$sRule], $position, 0, [$rule]); + \array_splice($this->rules[$sRule], $position, 0, [$rule]); } /** @@ -153,7 +153,7 @@ public function getRules($mRule = null) } /** @var array $result */ $result = []; - foreach ($this->aRules as $sName => $aRules) { + foreach ($this->rules as $sName => $rules) { // 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. if ( @@ -163,7 +163,7 @@ public function getRules($mRule = null) && (\strpos($sName, $mRule) === 0 || $sName === \substr($mRule, 0, -1)) ) ) { - $result = \array_merge($result, $aRules); + $result = \array_merge($result, $rules); } } \usort($result, static function (Rule $first, Rule $second): int { @@ -178,12 +178,12 @@ public function getRules($mRule = null) /** * Overrides all the rules of this set. * - * @param array $aRules The rules to override with. + * @param array $rules The rules to override with. */ - public function setRules(array $aRules): void + public function setRules(array $rules): void { - $this->aRules = []; - foreach ($aRules as $rule) { + $this->rules = []; + foreach ($rules as $rule) { $this->addRule($rule); } } @@ -231,16 +231,16 @@ public function removeRule($mRule): void { if ($mRule instanceof Rule) { $sRule = $mRule->getRule(); - if (!isset($this->aRules[$sRule])) { + if (!isset($this->rules[$sRule])) { return; } - foreach ($this->aRules[$sRule] as $key => $rule) { + foreach ($this->rules[$sRule] as $key => $rule) { if ($rule === $mRule) { - unset($this->aRules[$sRule][$key]); + unset($this->rules[$sRule][$key]); } } } else { - foreach ($this->aRules as $sName => $aRules) { + foreach ($this->rules as $sName => $rules) { // 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). @@ -249,7 +249,7 @@ public function removeRule($mRule): void || (\strrpos($mRule, '-') === \strlen($mRule) - \strlen('-') && (\strpos($sName, $mRule) === 0 || $sName === \substr($mRule, 0, -1))) ) { - unset($this->aRules[$sName]); + unset($this->rules[$sName]); } } } @@ -271,8 +271,8 @@ protected function renderRules(OutputFormat $outputFormat) $result = ''; $isFirst = true; $oNextLevel = $outputFormat->nextLevel(); - foreach ($this->aRules as $aRules) { - foreach ($aRules as $rule) { + foreach ($this->rules as $rules) { + foreach ($rules as $rule) { $sRendered = $oNextLevel->safely(static function () use ($rule, $oNextLevel): string { return $rule->render($oNextLevel); }); From 5fe8e87753dfeff01480271f57bee0a79e73007d Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 28 Feb 2025 01:48:19 +0100 Subject: [PATCH 260/555] [CLEANUP] Avoid Hungarian notation for `nextLevelFormat` (#1029) Part of #756 --- src/OutputFormat.php | 12 ++++++------ src/RuleSet/RuleSet.php | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 30c2e8fe..151e698c 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -173,7 +173,7 @@ class OutputFormat /** * @var OutputFormat|null */ - private $oNextLevelFormat; + private $nextLevelFormat; /** * @var int @@ -737,12 +737,12 @@ public function indentWithSpaces(int $numberOfSpaces = 2): self */ public function nextLevel(): self { - if ($this->oNextLevelFormat === null) { - $this->oNextLevelFormat = clone $this; - $this->oNextLevelFormat->iIndentationLevel++; - $this->oNextLevelFormat->outputFormatter = null; + if ($this->nextLevelFormat === null) { + $this->nextLevelFormat = clone $this; + $this->nextLevelFormat->iIndentationLevel++; + $this->nextLevelFormat->outputFormatter = null; } - return $this->oNextLevelFormat; + return $this->nextLevelFormat; } public function beLenient(): void diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index c7380ccb..147556ec 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -270,20 +270,20 @@ protected function renderRules(OutputFormat $outputFormat) { $result = ''; $isFirst = true; - $oNextLevel = $outputFormat->nextLevel(); + $nextLevelFormat = $outputFormat->nextLevel(); foreach ($this->rules as $rules) { foreach ($rules as $rule) { - $sRendered = $oNextLevel->safely(static function () use ($rule, $oNextLevel): string { - return $rule->render($oNextLevel); + $sRendered = $nextLevelFormat->safely(static function () use ($rule, $nextLevelFormat): string { + return $rule->render($nextLevelFormat); }); if ($sRendered === null) { continue; } if ($isFirst) { $isFirst = false; - $result .= $oNextLevel->spaceBeforeRules(); + $result .= $nextLevelFormat->spaceBeforeRules(); } else { - $result .= $oNextLevel->spaceBetweenRules(); + $result .= $nextLevelFormat->spaceBetweenRules(); } $result .= $sRendered; } From cd2cfde2f08554a48159475ec9577eb353653b3f Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 28 Feb 2025 01:54:08 +0100 Subject: [PATCH 261/555] [CLEANUP] Avoid Hungarian notation for `searchPattern` (#1030) This was previously (somewhat anonymously) named `$mRule` as a parameter. Part of #756 --- src/RuleSet/RuleSet.php | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 147556ec..0c451ec7 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -138,7 +138,7 @@ public function addRule(Rule $rule, ?Rule $oSibling = null): void * @example $ruleSet->getRules('font-') * //returns an array of all rules either beginning with font- or matching font. * - * @param Rule|string|null $mRule + * @param Rule|string|null $searchPattern * 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. @@ -146,10 +146,10 @@ public function addRule(Rule $rule, ?Rule $oSibling = null): void * * @return array */ - public function getRules($mRule = null) + public function getRules($searchPattern = null) { - if ($mRule instanceof Rule) { - $mRule = $mRule->getRule(); + if ($searchPattern instanceof Rule) { + $searchPattern = $searchPattern->getRule(); } /** @var array $result */ $result = []; @@ -157,10 +157,10 @@ public function getRules($mRule = null) // 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. if ( - !$mRule || $sName === $mRule + !$searchPattern || $sName === $searchPattern || ( - \strrpos($mRule, '-') === \strlen($mRule) - \strlen('-') - && (\strpos($sName, $mRule) === 0 || $sName === \substr($mRule, 0, -1)) + \strrpos($searchPattern, '-') === \strlen($searchPattern) - \strlen('-') + && (\strpos($sName, $searchPattern) === 0 || $sName === \substr($searchPattern, 0, -1)) ) ) { $result = \array_merge($result, $rules); @@ -196,18 +196,18 @@ public function setRules(array $rules): void * like `{ background-color: green; background-color; rgba(0, 127, 0, 0.7); }` will only yield an associative array * containing the rgba-valued rule while `getRules()` would yield an indexed array containing both. * - * @param Rule|string|null $mRule $mRule + * @param Rule|string|null $searchPattern * 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())`. * * @return array */ - public function getRulesAssoc($mRule = null) + public function getRulesAssoc($searchPattern = null) { /** @var array $result */ $result = []; - foreach ($this->getRules($mRule) as $rule) { + foreach ($this->getRules($searchPattern) as $rule) { $result[$rule->getRule()] = $rule; } return $result; @@ -222,20 +222,20 @@ public function getRulesAssoc($mRule = null) * 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($rule->getRule())`. * - * @param Rule|string|null $mRule - * pattern to remove. If $mRule is null, all rules are removed. If the pattern ends in a dash, + * @param Rule|string|null $searchPattern + * pattern to remove. If 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. */ - public function removeRule($mRule): void + public function removeRule($searchPattern): void { - if ($mRule instanceof Rule) { - $sRule = $mRule->getRule(); + if ($searchPattern instanceof Rule) { + $sRule = $searchPattern->getRule(); if (!isset($this->rules[$sRule])) { return; } foreach ($this->rules[$sRule] as $key => $rule) { - if ($rule === $mRule) { + if ($rule === $searchPattern) { unset($this->rules[$sRule][$key]); } } @@ -245,9 +245,9 @@ public function removeRule($mRule): void // 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))) + !$searchPattern || $sName === $searchPattern + || (\strrpos($searchPattern, '-') === \strlen($searchPattern) - \strlen('-') + && (\strpos($sName, $searchPattern) === 0 || $sName === \substr($searchPattern, 0, -1))) ) { unset($this->rules[$sName]); } From 78425304d5e149d32609d9f568af44ec85b4963e Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 28 Feb 2025 01:56:27 +0100 Subject: [PATCH 262/555] [CLEANUP] Avoid Hungarian notation for `sibling` (#1031) Part of #756 --- src/RuleSet/RuleSet.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 0c451ec7..27750dcf 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -101,7 +101,7 @@ public function getLineNo(): int return $this->lineNumber; } - public function addRule(Rule $rule, ?Rule $oSibling = null): void + public function addRule(Rule $rule, ?Rule $sibling = null): void { $sRule = $rule->getRule(); if (!isset($this->rules[$sRule])) { @@ -110,11 +110,11 @@ public function addRule(Rule $rule, ?Rule $oSibling = null): void $position = \count($this->rules[$sRule]); - if ($oSibling !== null) { - $iSiblingPos = \array_search($oSibling, $this->rules[$sRule], true); + if ($sibling !== null) { + $iSiblingPos = \array_search($sibling, $this->rules[$sRule], true); if ($iSiblingPos !== false) { $position = $iSiblingPos; - $rule->setPosition($oSibling->getLineNo(), $oSibling->getColNo() - 1); + $rule->setPosition($sibling->getLineNo(), $sibling->getColNo() - 1); } } if ($rule->getLineNo() === 0 && $rule->getColNo() === 0) { From c785369e5c4bc5248abe7857d49ea5c04bec773e Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 28 Feb 2025 20:15:44 +0100 Subject: [PATCH 263/555] [CLEANUP] Avoid Hungarian notation for `renderedRule` (#1032) Part of #756 --- src/RuleSet/RuleSet.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 27750dcf..da6d5eb5 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -273,10 +273,10 @@ protected function renderRules(OutputFormat $outputFormat) $nextLevelFormat = $outputFormat->nextLevel(); foreach ($this->rules as $rules) { foreach ($rules as $rule) { - $sRendered = $nextLevelFormat->safely(static function () use ($rule, $nextLevelFormat): string { + $renderedRule = $nextLevelFormat->safely(static function () use ($rule, $nextLevelFormat): string { return $rule->render($nextLevelFormat); }); - if ($sRendered === null) { + if ($renderedRule === null) { continue; } if ($isFirst) { @@ -285,7 +285,7 @@ protected function renderRules(OutputFormat $outputFormat) } else { $result .= $nextLevelFormat->spaceBetweenRules(); } - $result .= $sRendered; + $result .= $renderedRule; } } From e6ce845e8963b15b23aaa56e02627c5747b3cfd3 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 28 Feb 2025 20:18:17 +0100 Subject: [PATCH 264/555] [CLEANUP] Avoid Hungarian notation for `consumedText` (#1033) Part of #756 --- src/RuleSet/RuleSet.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index da6d5eb5..48f8adb1 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -69,9 +69,9 @@ public static function parseRuleSet(ParserState $parserState, RuleSet $ruleSet): $rule = Rule::parse($parserState); } catch (UnexpectedTokenException $e) { try { - $sConsume = $parserState->consumeUntil(["\n", ';', '}'], true); + $consumedText = $parserState->consumeUntil(["\n", ';', '}'], true); // We need to “unfind” the matches to the end of the ruleSet as this will be matched later - if ($parserState->streql(\substr($sConsume, -1), '}')) { + if ($parserState->streql(\substr($consumedText, -1), '}')) { $parserState->backtrack(1); } else { while ($parserState->comes(';')) { From ee15f8f8f6a77438565f74650167ca3c016548db Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 28 Feb 2025 20:20:18 +0100 Subject: [PATCH 265/555] [CLEANUP] Improve a parameter name in `RuleSet::addRule()` (#1034) --- src/RuleSet/RuleSet.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 48f8adb1..42f7878e 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -101,9 +101,9 @@ public function getLineNo(): int return $this->lineNumber; } - public function addRule(Rule $rule, ?Rule $sibling = null): void + public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void { - $sRule = $rule->getRule(); + $sRule = $ruleToAdd->getRule(); if (!isset($this->rules[$sRule])) { $this->rules[$sRule] = []; } @@ -114,20 +114,20 @@ public function addRule(Rule $rule, ?Rule $sibling = null): void $iSiblingPos = \array_search($sibling, $this->rules[$sRule], true); if ($iSiblingPos !== false) { $position = $iSiblingPos; - $rule->setPosition($sibling->getLineNo(), $sibling->getColNo() - 1); + $ruleToAdd->setPosition($sibling->getLineNo(), $sibling->getColNo() - 1); } } - if ($rule->getLineNo() === 0 && $rule->getColNo() === 0) { + if ($ruleToAdd->getLineNo() === 0 && $ruleToAdd->getColNo() === 0) { //this node is added manually, give it the next best line $rules = $this->getRules(); $pos = \count($rules); if ($pos > 0) { $last = $rules[$pos - 1]; - $rule->setPosition($last->getLineNo() + 1, 0); + $ruleToAdd->setPosition($last->getLineNo() + 1, 0); } } - \array_splice($this->rules[$sRule], $position, 0, [$rule]); + \array_splice($this->rules[$sRule], $position, 0, [$ruleToAdd]); } /** From 4dc5a899cf2b35b4cedfd2e756054cafe0d09e01 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 28 Feb 2025 20:26:06 +0100 Subject: [PATCH 266/555] [TASK] Add tests for `Selector::__toString()` (#1035) Part of #757 --- .../Property/SelectorTest.php | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 tests/FunctionalDeprecated/Property/SelectorTest.php diff --git a/tests/FunctionalDeprecated/Property/SelectorTest.php b/tests/FunctionalDeprecated/Property/SelectorTest.php new file mode 100644 index 00000000..917d227f --- /dev/null +++ b/tests/FunctionalDeprecated/Property/SelectorTest.php @@ -0,0 +1,25 @@ + Date: Fri, 28 Feb 2025 20:28:18 +0100 Subject: [PATCH 267/555] [TASK] Add unit tests for `Selector.selector` (#1036) Part of #757 --- tests/Unit/Property/SelectorTest.php | 38 ++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 tests/Unit/Property/SelectorTest.php diff --git a/tests/Unit/Property/SelectorTest.php b/tests/Unit/Property/SelectorTest.php new file mode 100644 index 00000000..d7d5dfc2 --- /dev/null +++ b/tests/Unit/Property/SelectorTest.php @@ -0,0 +1,38 @@ +getSelector()); + } + + /** + * @test + */ + public function setSelectorOverwritesSelectorProvidedToConstructor(): void + { + $subject = new Selector('a'); + + $selector = 'input'; + $subject->setSelector($selector); + + self::assertSame($selector, $subject->getSelector()); + } +} From 33157735326dabd9fe3bcf4736a2aa3cf8dc5dbc Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 28 Feb 2025 23:23:41 +0100 Subject: [PATCH 268/555] [TASK] Mark `Selector::isValid()` as `@internal` (#1037) --- CHANGELOG.md | 1 + src/Property/Selector.php | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a223514..23dfb133 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Please also have a look at our ### Changed +- Mark `Selector::isValid()` as `@internal` (#1037) - Mark parsing-related methods of most CSS elements as `@internal` (#908) - Mark `OutputFormat::nextLevel()` as `@internal` (#901) - Only allow `string` for some `OutputFormat` properties (#885) diff --git a/src/Property/Selector.php b/src/Property/Selector.php index 63080a6a..9f9a22dd 100644 --- a/src/Property/Selector.php +++ b/src/Property/Selector.php @@ -78,6 +78,8 @@ class Selector * @param string $selector * * @return bool + * + * @internal since V8.8.0 */ public static function isValid($selector) { From 2093b19f0276a559c2b09cf026d19d90fe882787 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 1 Mar 2025 00:29:55 +0100 Subject: [PATCH 269/555] [TASK] Drop `getLineNo()` from the `Renderable` interface (#1038) The concept of being able to get rendered is logically independent from having a position in the input stream. Hence, both concepts should not be conflated in the `Renderable` interface. (We might want to add a dedicated interface for classes that are linked to specific points in the input stream later, though.) --- CHANGELOG.md | 1 + src/Renderable.php | 5 ----- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23dfb133..4bbee72e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ Please also have a look at our ### Removed +- Drop `getLineNo()` from the `Renderable` interface (#1038) - Remove `OutputFormat::level()` (#874) - Remove expansion of shorthand properties (#838) - Remove `Parser::setCharset/getCharset` (#808) diff --git a/src/Renderable.php b/src/Renderable.php index ff96d66a..ed633654 100644 --- a/src/Renderable.php +++ b/src/Renderable.php @@ -12,9 +12,4 @@ interface Renderable public function __toString(): string; public function render(OutputFormat $outputFormat): string; - - /** - * @return int<0, max> - */ - public function getLineNo(): int; } From 7ef82db1560d84367e04268dcb0581307b08a475 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 1 Mar 2025 01:38:13 +0100 Subject: [PATCH 270/555] [TASK] Add tests for the selector specificity (#1025) Mostly move tests from `ParserTest` that belong in the unit tests. Part of #757 Part of #758 --- config/phpstan-baseline.neon | 6 ---- tests/ParserTest.php | 24 ------------- tests/Unit/Property/SelectorTest.php | 52 ++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 30 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 535469d6..f2969599 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -222,12 +222,6 @@ parameters: count: 1 path: ../src/Property/Import.php - - - message: '#^Parameters should have "string" types as the only types passed to this method$#' - identifier: typePerfect.narrowPublicClassMethodParamType - count: 1 - path: ../src/Property/Selector.php - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:comments\(\)\.$#' identifier: method.notFound diff --git a/tests/ParserTest.php b/tests/ParserTest.php index 9ff7b283..7a66f852 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -246,30 +246,6 @@ public function unicodeRangeParsing(): void public function specificity(): void { $document = self::parsedStructureForFile('specificity'); - $declarationBlocks = $document->getAllDeclarationBlocks(); - $declarationBlock = $declarationBlocks[0]; - $selectors = $declarationBlock->getSelectors(); - foreach ($selectors as $selector) { - switch ($selector->getSelector()) { - case '#test .help': - self::assertSame(110, $selector->getSpecificity()); - break; - case '#file': - self::assertSame(100, $selector->getSpecificity()); - break; - case '.help:hover': - self::assertSame(20, $selector->getSpecificity()); - break; - case 'ol li::before': - self::assertSame(3, $selector->getSpecificity()); - break; - case 'li.green': - self::assertSame(11, $selector->getSpecificity()); - break; - default: - self::fail('specificity: untested selector ' . $selector->getSelector()); - } - } self::assertEquals([new Selector('#test .help', true)], $document->getSelectorsBySpecificity('> 100')); self::assertEquals( [new Selector('#test .help', true), new Selector('#file', true)], diff --git a/tests/Unit/Property/SelectorTest.php b/tests/Unit/Property/SelectorTest.php index d7d5dfc2..0d030d94 100644 --- a/tests/Unit/Property/SelectorTest.php +++ b/tests/Unit/Property/SelectorTest.php @@ -35,4 +35,56 @@ public function setSelectorOverwritesSelectorProvidedToConstructor(): void self::assertSame($selector, $subject->getSelector()); } + + /** + * @return array}> + */ + public static function provideSelectorsAndSpecificities(): array + { + return [ + 'element' => ['a', 1], + 'element and descendant with pseudo-selector' => ['ol li::before', 3], + 'class' => ['.highlighted', 10], + 'element with class' => ['li.green', 11], + 'class with pseudo-selector' => ['.help:hover', 20], + 'ID' => ['#file', 100], + 'ID and descendant class' => ['#test .help', 110], + ]; + } + + /** + * @test + * + * @param non-empty-string $selector + * @param int<0, max> $expectedSpecificity + * + * @dataProvider provideSelectorsAndSpecificities + */ + public function getSpecificityByDefaultReturnsSpecificityOfSelectorProvidedToConstructor( + string $selector, + int $expectedSpecificity + ): void { + $subject = new Selector($selector); + + self::assertSame($expectedSpecificity, $subject->getSpecificity()); + } + + /** + * @test + * + * @param non-empty-string $selector + * @param int<0, max> $expectedSpecificity + * + * @dataProvider provideSelectorsAndSpecificities + */ + public function getSpecificityReturnsSpecificityOfSelectorLastProvidedViaSetSelector( + string $selector, + int $expectedSpecificity + ): void { + $subject = new Selector('p'); + + $subject->setSelector($selector); + + self::assertSame($expectedSpecificity, $subject->getSpecificity()); + } } From b6d0b7031c1f3c1cdd108247a25640d4a46b0312 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 1 Mar 2025 11:33:06 +0100 Subject: [PATCH 271/555] [TASK] Make `Selector` a `Renderable` (#1017) This is required to be able to drop `__toString` for this class. --- CHANGELOG.md | 1 + src/Property/Selector.php | 10 +++- tests/Functional/Property/SelectorTest.php | 59 ++++++++++++++++++++++ tests/Unit/Property/SelectorTest.php | 11 ++++ 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 tests/Functional/Property/SelectorTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bbee72e..37f76d9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Please also have a look at our ### Changed +- Make `Selector` a `Renderable` (#1017) - 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/Property/Selector.php b/src/Property/Selector.php index 9f9a22dd..0f68df5f 100644 --- a/src/Property/Selector.php +++ b/src/Property/Selector.php @@ -4,11 +4,14 @@ namespace Sabberworm\CSS\Property; +use Sabberworm\CSS\OutputFormat; +use Sabberworm\CSS\Renderable; + /** * Class representing a single CSS selector. Selectors have to be split by the comma prior to being passed into this * class. */ -class Selector +class Selector implements Renderable { /** * regexp for specificity calculations @@ -139,4 +142,9 @@ public function getSpecificity() } return $this->specificity; } + + public function render(OutputFormat $outputFormat): string + { + return $this->getSelector(); + } } diff --git a/tests/Functional/Property/SelectorTest.php b/tests/Functional/Property/SelectorTest.php new file mode 100644 index 00000000..397dbc72 --- /dev/null +++ b/tests/Functional/Property/SelectorTest.php @@ -0,0 +1,59 @@ +render(new OutputFormat())); + } + + /** + * @test + */ + public function renderWithDefaultOutputFormatRendersSelectorPassedToConstructor(): void + { + $pattern = 'a'; + $subject = new Selector($pattern); + + self::assertSame($pattern, $subject->render(OutputFormat::create())); + } + + /** + * @test + */ + public function renderWithCompactOutputFormatRendersSelectorPassedToConstructor(): void + { + $pattern = 'a'; + $subject = new Selector($pattern); + + self::assertSame($pattern, $subject->render(OutputFormat::createCompact())); + } + + /** + * @test + */ + public function renderWithPrettyOutputFormatRendersSelectorPassedToConstructor(): void + { + $pattern = 'a'; + $subject = new Selector($pattern); + + self::assertSame($pattern, $subject->render(OutputFormat::createPretty())); + } +} diff --git a/tests/Unit/Property/SelectorTest.php b/tests/Unit/Property/SelectorTest.php index 0d030d94..de888fd5 100644 --- a/tests/Unit/Property/SelectorTest.php +++ b/tests/Unit/Property/SelectorTest.php @@ -6,12 +6,23 @@ use PHPUnit\Framework\TestCase; use Sabberworm\CSS\Property\Selector; +use Sabberworm\CSS\Renderable; /** * @covers \Sabberworm\CSS\Property\Selector */ final class SelectorTest extends TestCase { + /** + * @test + */ + public function implementsRenderable(): void + { + $subject = new Selector('a'); + + self::assertInstanceOf(Renderable::class, $subject); + } + /** * @test */ From 50045f7320ae2f065995e926cbb5180ceafdc8c4 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 1 Mar 2025 11:36:48 +0100 Subject: [PATCH 272/555] [TASK] Add more native type declarations for `Selector` (#1044) The return type of `::isValid()` cannot use a native return type declaration yet as the method's return values currently are of a different type (#1043). Part of #811 --- CHANGELOG.md | 2 +- src/Property/Selector.php | 21 ++++++--------------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37f76d9d..4bfcf0fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,7 +29,7 @@ Please also have a look at our - Make all non-private properties `@internal` (#886) - Use more native type declarations and strict mode (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933, #958, - #964, #967, #1000) + #964, #967, #1000, #1044) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/src/Property/Selector.php b/src/Property/Selector.php index 0f68df5f..ceed44a6 100644 --- a/src/Property/Selector.php +++ b/src/Property/Selector.php @@ -78,22 +78,19 @@ class Selector implements Renderable private $specificity; /** - * @param string $selector - * * @return bool * * @internal since V8.8.0 */ - public static function isValid($selector) + public static function isValid(string $selector) { return \preg_match(static::SELECTOR_VALIDATION_RX, $selector); } /** - * @param string $selector * @param bool $calculateSpecificity @deprecated since V8.8.0, will be removed in V9.0.0 */ - public function __construct($selector, $calculateSpecificity = false) + public function __construct(string $selector, bool $calculateSpecificity = false) { $this->setSelector($selector); if ($calculateSpecificity) { @@ -101,18 +98,12 @@ public function __construct($selector, $calculateSpecificity = false) } } - /** - * @return string - */ - public function getSelector() + public function getSelector(): string { return $this->selector; } - /** - * @param string $selector - */ - public function setSelector($selector): void + public function setSelector(string $selector): void { $this->selector = \trim($selector); $this->specificity = null; @@ -127,9 +118,9 @@ public function __toString(): string } /** - * @return int + * @return int<0, max> */ - public function getSpecificity() + public function getSpecificity(): int { if ($this->specificity === null) { $a = 0; From 713eec948a62cbb80da04b37945f4e794c34ae93 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 1 Mar 2025 20:54:08 +0100 Subject: [PATCH 273/555] [CLEANUP] Avoid Hungarian notation for `document` (#1048) Part of #756 --- src/CSSList/Document.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/CSSList/Document.php b/src/CSSList/Document.php index 06a8064f..680a9029 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -24,9 +24,10 @@ class Document extends CSSBlockList */ public static function parse(ParserState $parserState): Document { - $oDocument = new Document($parserState->currentLine()); - CSSList::parseList($parserState, $oDocument); - return $oDocument; + $document = new Document($parserState->currentLine()); + CSSList::parseList($parserState, $document); + + return $document; } /** From f912e7126bf217ffec07068b24d9b162eb92c7e1 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 2 Mar 2025 02:42:06 +0100 Subject: [PATCH 274/555] [TASK] Move up `getAllRuleSets` to `CSSBlockList` (#1047) Move this method up without any changes to the method or its callers. We'll clean this up in later changes. Part of #994. --- src/CSSList/CSSBlockList.php | 13 +++ src/CSSList/Document.php | 13 --- tests/Unit/CSSList/CSSBlockListTest.php | 137 +++++++++++++++++++++++ tests/Unit/CSSList/DocumentTest.php | 143 ------------------------ 4 files changed, 150 insertions(+), 156 deletions(-) diff --git a/src/CSSList/CSSBlockList.php b/src/CSSList/CSSBlockList.php index cae6d9f0..0f8bbe36 100644 --- a/src/CSSList/CSSBlockList.php +++ b/src/CSSList/CSSBlockList.php @@ -54,6 +54,19 @@ protected function allDeclarationBlocks(array &$result): void } } + /** + * Returns all `RuleSet` objects recursively found in the tree, no matter how deeply nested the rule sets are. + * + * @return array + */ + public function getAllRuleSets(): array + { + /** @var array $result */ + $result = []; + $this->allRuleSets($result); + return $result; + } + /** * @param array $result */ diff --git a/src/CSSList/Document.php b/src/CSSList/Document.php index 680a9029..2051d3c2 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -30,19 +30,6 @@ public static function parse(ParserState $parserState): Document return $document; } - /** - * Returns all `RuleSet` objects recursively found in the tree, no matter how deeply nested the rule sets are. - * - * @return array - */ - public function getAllRuleSets(): array - { - /** @var array $result */ - $result = []; - $this->allRuleSets($result); - return $result; - } - /** * Returns all `Value` objects found recursively in `Rule`s in the tree. * diff --git a/tests/Unit/CSSList/CSSBlockListTest.php b/tests/Unit/CSSList/CSSBlockListTest.php index 05a50dac..0028288f 100644 --- a/tests/Unit/CSSList/CSSBlockListTest.php +++ b/tests/Unit/CSSList/CSSBlockListTest.php @@ -11,6 +11,7 @@ use Sabberworm\CSS\Property\Charset; use Sabberworm\CSS\Property\Import; use Sabberworm\CSS\Renderable; +use Sabberworm\CSS\RuleSet\AtRuleSet; use Sabberworm\CSS\RuleSet\DeclarationBlock; use Sabberworm\CSS\Tests\Unit\CSSList\Fixtures\ConcreteCSSBlockList; use Sabberworm\CSS\Value\CSSString; @@ -139,4 +140,140 @@ public function getAllDeclarationBlocksIgnoresCharset(): void self::assertSame([], $result); } + + /** + * @test + */ + public function getAllRuleSetsWhenNoContentSetReturnsEmptyArray(): void + { + $subject = new ConcreteCSSBlockList(); + + self::assertSame([], $subject->getAllRuleSets()); + } + + /** + * @test + */ + public function getAllRuleSetsReturnsOneDeclarationBlockDirectlySetAsContent(): void + { + $subject = new ConcreteCSSBlockList(); + + $declarationBlock = new DeclarationBlock(); + $subject->setContents([$declarationBlock]); + + $result = $subject->getAllRuleSets(); + + self::assertSame([$declarationBlock], $result); + } + + /** + * @test + */ + public function getAllRuleSetsReturnsOneAtRuleSetDirectlySetAsContent(): void + { + $subject = new ConcreteCSSBlockList(); + + $atRuleSet = new AtRuleSet('media'); + $subject->setContents([$atRuleSet]); + + $result = $subject->getAllRuleSets(); + + self::assertSame([$atRuleSet], $result); + } + + /** + * @test + */ + public function getAllRuleSetsReturnsMultipleDeclarationBlocksDirectlySetAsContents(): void + { + $subject = new ConcreteCSSBlockList(); + + $declarationBlock1 = new DeclarationBlock(); + $declarationBlock2 = new DeclarationBlock(); + $subject->setContents([$declarationBlock1, $declarationBlock2]); + + $result = $subject->getAllRuleSets(); + + self::assertSame([$declarationBlock1, $declarationBlock2], $result); + } + + /** + * @test + */ + public function getAllRuleSetsReturnsMultipleAtRuleSetsDirectlySetAsContents(): void + { + $subject = new ConcreteCSSBlockList(); + + $atRuleSet1 = new AtRuleSet('media'); + $atRuleSet2 = new AtRuleSet('media'); + $subject->setContents([$atRuleSet1, $atRuleSet2]); + + $result = $subject->getAllRuleSets(); + + self::assertSame([$atRuleSet1, $atRuleSet2], $result); + } + + /** + * @test + */ + public function getAllRuleSetsReturnsDeclarationBlocksWithinAtRuleBlockList(): void + { + $subject = new ConcreteCSSBlockList(); + + $declarationBlock = new DeclarationBlock(); + $atRuleBlockList = new AtRuleBlockList('media'); + $atRuleBlockList->setContents([$declarationBlock]); + $subject->setContents([$atRuleBlockList]); + + $result = $subject->getAllRuleSets(); + + self::assertSame([$declarationBlock], $result); + } + + /** + * @test + */ + public function getAllRuleSetsReturnsAtRuleSetsWithinAtRuleBlockList(): void + { + $subject = new ConcreteCSSBlockList(); + + $atRule = new AtRuleSet('media'); + $atRuleBlockList = new AtRuleBlockList('media'); + $atRuleBlockList->setContents([$atRule]); + $subject->setContents([$atRuleBlockList]); + + $result = $subject->getAllRuleSets(); + + self::assertSame([$atRule], $result); + } + + /** + * @test + */ + public function getAllRuleSetsIgnoresImport(): void + { + $subject = new ConcreteCSSBlockList(); + + $import = new Import(new URL(new CSSString('https://www.example.com/')), ''); + $subject->setContents([$import]); + + $result = $subject->getAllRuleSets(); + + self::assertSame([], $result); + } + + /** + * @test + */ + public function getAllRuleSetsIgnoresCharset(): void + { + $subject = new ConcreteCSSBlockList(); + + $charset = new Charset(new CSSString('UTF-8')); + $subject->setContents([$charset]); + + $result = $subject->getAllRuleSets(); + + self::assertSame([], $result); + } } diff --git a/tests/Unit/CSSList/DocumentTest.php b/tests/Unit/CSSList/DocumentTest.php index 509b4b9d..77e8aa81 100644 --- a/tests/Unit/CSSList/DocumentTest.php +++ b/tests/Unit/CSSList/DocumentTest.php @@ -6,17 +6,10 @@ use PHPUnit\Framework\TestCase; use Sabberworm\CSS\Comment\Commentable; -use Sabberworm\CSS\CSSList\AtRuleBlockList; use Sabberworm\CSS\CSSList\CSSBlockList; use Sabberworm\CSS\CSSList\CSSList; use Sabberworm\CSS\CSSList\Document; -use Sabberworm\CSS\Property\Charset; -use Sabberworm\CSS\Property\Import; use Sabberworm\CSS\Renderable; -use Sabberworm\CSS\RuleSet\AtRuleSet; -use Sabberworm\CSS\RuleSet\DeclarationBlock; -use Sabberworm\CSS\Value\CSSString; -use Sabberworm\CSS\Value\URL; /** * @covers \Sabberworm\CSS\CSSList\CSSBlockList @@ -61,142 +54,6 @@ public function isCSSList(): void self::assertInstanceOf(CSSList::class, $subject); } - /** - * @test - */ - public function getAllRuleSetsWhenNoContentSetReturnsEmptyArray(): void - { - $subject = new Document(); - - self::assertSame([], $subject->getAllRuleSets()); - } - - /** - * @test - */ - public function getAllRuleSetsReturnsOneDeclarationBlockDirectlySetAsContent(): void - { - $subject = new Document(); - - $declarationBlock = new DeclarationBlock(); - $subject->setContents([$declarationBlock]); - - $result = $subject->getAllRuleSets(); - - self::assertSame([$declarationBlock], $result); - } - - /** - * @test - */ - public function getAllRuleSetsReturnsOneAtRuleSetDirectlySetAsContent(): void - { - $subject = new Document(); - - $atRuleSet = new AtRuleSet('media'); - $subject->setContents([$atRuleSet]); - - $result = $subject->getAllRuleSets(); - - self::assertSame([$atRuleSet], $result); - } - - /** - * @test - */ - public function getAllRuleSetsReturnsMultipleDeclarationBlocksDirectlySetAsContents(): void - { - $subject = new Document(); - - $declarationBlock1 = new DeclarationBlock(); - $declarationBlock2 = new DeclarationBlock(); - $subject->setContents([$declarationBlock1, $declarationBlock2]); - - $result = $subject->getAllRuleSets(); - - self::assertSame([$declarationBlock1, $declarationBlock2], $result); - } - - /** - * @test - */ - public function getAllRuleSetsReturnsMultipleAtRuleSetsDirectlySetAsContents(): void - { - $subject = new Document(); - - $atRuleSet1 = new AtRuleSet('media'); - $atRuleSet2 = new AtRuleSet('media'); - $subject->setContents([$atRuleSet1, $atRuleSet2]); - - $result = $subject->getAllRuleSets(); - - self::assertSame([$atRuleSet1, $atRuleSet2], $result); - } - - /** - * @test - */ - public function getAllRuleSetsReturnsDeclarationBlocksWithinAtRuleBlockList(): void - { - $subject = new Document(); - - $declarationBlock = new DeclarationBlock(); - $atRuleBlockList = new AtRuleBlockList('media'); - $atRuleBlockList->setContents([$declarationBlock]); - $subject->setContents([$atRuleBlockList]); - - $result = $subject->getAllRuleSets(); - - self::assertSame([$declarationBlock], $result); - } - - /** - * @test - */ - public function getAllRuleSetsReturnsAtRuleSetsWithinAtRuleBlockList(): void - { - $subject = new Document(); - - $atRule = new AtRuleSet('media'); - $atRuleBlockList = new AtRuleBlockList('media'); - $atRuleBlockList->setContents([$atRule]); - $subject->setContents([$atRuleBlockList]); - - $result = $subject->getAllRuleSets(); - - self::assertSame([$atRule], $result); - } - - /** - * @test - */ - public function getAllRuleSetsIgnoresImport(): void - { - $subject = new Document(); - - $import = new Import(new URL(new CSSString('https://www.example.com/')), ''); - $subject->setContents([$import]); - - $result = $subject->getAllRuleSets(); - - self::assertSame([], $result); - } - - /** - * @test - */ - public function getAllRuleSetsIgnoresCharset(): void - { - $subject = new Document(); - - $charset = new Charset(new CSSString('UTF-8')); - $subject->setContents([$charset]); - - $result = $subject->getAllRuleSets(); - - self::assertSame([], $result); - } - /** * @test */ From a037425d730098d01cef21cdf213e57a310b7f7d Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 2 Mar 2025 20:01:04 +0100 Subject: [PATCH 275/555] [FEATURE] Add a utility class to calculate selector specificity (#1049) Calculating and caching the specificity of a selector is a different concern than representing a selector, and it deserves to be in its own class. This also helps solve the problem of selectors having keep their specificity cached and in sync with the selector itself. (We'll have a later change that changes `Selector` to use the new class.) --- .../Selector/SpecificityCalculator.php | 85 +++++++++++++++++ .../Selector/SpecificityCalculatorTest.php | 94 +++++++++++++++++++ 2 files changed, 179 insertions(+) create mode 100644 src/Property/Selector/SpecificityCalculator.php create mode 100644 tests/Unit/Property/Selector/SpecificityCalculatorTest.php diff --git a/src/Property/Selector/SpecificityCalculator.php b/src/Property/Selector/SpecificityCalculator.php new file mode 100644 index 00000000..56327c87 --- /dev/null +++ b/src/Property/Selector/SpecificityCalculator.php @@ -0,0 +1,85 @@ +\\~]+)[\\w]+ # elements + | + \\:{1,2}( # pseudo-elements + after|before|first-letter|first-line|selection + )) + /ix'; + + /** + * @var array> + */ + private static $cache = []; + + /** + * Calculates the specificity of the given CSS selector. + * + * @return int<0, max> + * + * @internal + */ + public static function calculate(string $selector): int + { + if (!isset(self::$cache[$selector])) { + $a = 0; + /// @todo should exclude \# as well as "#" + $aMatches = null; + $b = \substr_count($selector, '#'); + $c = \preg_match_all(self::NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX, $selector, $aMatches); + $d = \preg_match_all(self::ELEMENTS_AND_PSEUDO_ELEMENTS_RX, $selector, $aMatches); + self::$cache[$selector] = ($a * 1000) + ($b * 100) + ($c * 10) + $d; + } + + return self::$cache[$selector]; + } + + /** + * Clears the cache in order to lower memory usage. + */ + public static function clearCache(): void + { + self::$cache = []; + } +} diff --git a/tests/Unit/Property/Selector/SpecificityCalculatorTest.php b/tests/Unit/Property/Selector/SpecificityCalculatorTest.php new file mode 100644 index 00000000..088bd517 --- /dev/null +++ b/tests/Unit/Property/Selector/SpecificityCalculatorTest.php @@ -0,0 +1,94 @@ +}> + */ + public static function provideSelectorsAndSpecificities(): array + { + return [ + 'element' => ['a', 1], + 'element and descendant with pseudo-selector' => ['ol li::before', 3], + 'class' => ['.highlighted', 10], + 'element with class' => ['li.green', 11], + 'class with pseudo-selector' => ['.help:hover', 20], + 'ID' => ['#file', 100], + 'ID and descendant class' => ['#test .help', 110], + ]; + } + + /** + * @test + * + * @param non-empty-string $selector + * @param int<0, max> $expectedSpecificity + * + * @dataProvider provideSelectorsAndSpecificities + */ + public function calculateReturnsSpecificityForProvidedSelector( + string $selector, + int $expectedSpecificity + ): void { + self::assertSame($expectedSpecificity, SpecificityCalculator::calculate($selector)); + } + + /** + * @test + * + * @param non-empty-string $selector + * @param int<0, max> $expectedSpecificity + * + * @dataProvider provideSelectorsAndSpecificities + */ + public function calculateAfterClearingCacheReturnsSpecificityForProvidedSelector( + string $selector, + int $expectedSpecificity + ): void { + SpecificityCalculator::clearCache(); + + self::assertSame($expectedSpecificity, SpecificityCalculator::calculate($selector)); + } + + /** + * @test + */ + public function calculateCalledTwoTimesReturnsSameSpecificityForProvidedSelector(): void + { + $selector = '#test .help'; + + $firstResult = SpecificityCalculator::calculate($selector); + $secondResult = SpecificityCalculator::calculate($selector); + + self::assertSame($firstResult, $secondResult); + } + + /** + * @test + */ + public function calculateCalledReturnsSameSpecificityForProvidedSelectorBeforeAndAfterClearingCache(): void + { + $selector = '#test .help'; + + $firstResult = SpecificityCalculator::calculate($selector); + SpecificityCalculator::clearCache(); + $secondResult = SpecificityCalculator::calculate($selector); + + self::assertSame($firstResult, $secondResult); + } +} From 54ca4426225770d8113cf8f20bc82198ee788b09 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 2 Mar 2025 20:06:41 +0100 Subject: [PATCH 276/555] [CLEANUP] Refactor `CSSBlockList::getAllRuleSets()` (#1050) Also remove the now-unused method `allRuleSets()`. Part of #994. --- src/CSSList/CSSBlockList.php | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/CSSList/CSSBlockList.php b/src/CSSList/CSSBlockList.php index 0f8bbe36..10a7778c 100644 --- a/src/CSSList/CSSBlockList.php +++ b/src/CSSList/CSSBlockList.php @@ -57,28 +57,21 @@ protected function allDeclarationBlocks(array &$result): void /** * Returns all `RuleSet` objects recursively found in the tree, no matter how deeply nested the rule sets are. * - * @return array + * @return list */ public function getAllRuleSets(): array { - /** @var array $result */ $result = []; - $this->allRuleSets($result); - return $result; - } - /** - * @param array $result - */ - protected function allRuleSets(array &$result): void - { foreach ($this->contents as $item) { if ($item instanceof RuleSet) { $result[] = $item; } elseif ($item instanceof CSSBlockList) { - $item->allRuleSets($result); + $result = \array_merge($result, $item->getAllRuleSets()); } } + + return $result; } /** From 08010a202d0dfa4d34908d255bf6d4a322fc451f Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 3 Mar 2025 00:38:00 +0100 Subject: [PATCH 277/555] [TASK] Remove the IE hack in `Rule` (#995) --- CHANGELOG.md | 1 + config/phpstan-baseline.neon | 12 ----------- src/Parsing/ParserState.php | 7 ------- src/Rule/Rule.php | 39 ------------------------------------ src/Value/Value.php | 1 - tests/ParserTest.php | 22 -------------------- tests/fixtures/ie-hacks.css | 9 --------- 7 files changed, 1 insertion(+), 90 deletions(-) delete mode 100644 tests/fixtures/ie-hacks.css diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bfcf0fa..46fc107a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ Please also have a look at our ### Removed +- Remove the IE hack in `Rule` (#995) - Drop `getLineNo()` from the `Renderable` interface (#1038) - Remove `OutputFormat::level()` (#874) - Remove expansion of shorthand properties (#838) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index f2969599..e17e1b3f 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -234,12 +234,6 @@ parameters: count: 1 path: ../src/Rule/Rule.php - - - message: '#^Construct empty\(\) is not allowed\. Use more strict comparison\.$#' - identifier: empty.notAllowed - count: 1 - path: ../src/Rule/Rule.php - - message: '#^Only booleans are allowed in an if condition, Sabberworm\\CSS\\Value\\RuleValueList\|string\|null given\.$#' identifier: if.condNotBoolean @@ -258,12 +252,6 @@ parameters: count: 1 path: ../src/Rule/Rule.php - - - message: '#^Parameters should have "string" types as the only types passed to this method$#' - identifier: typePerfect.narrowPublicClassMethodParamType - count: 1 - path: ../src/Rule/Rule.php - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:comments\(\)\.$#' identifier: method.notFound diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index b4885c17..2100ed35 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -150,13 +150,6 @@ public function parseIdentifier($ignoreCase = true) public function parseCharacter($isForIdentifier) { if ($this->peek() === '\\') { - if ( - $isForIdentifier && $this->parserSettings->usesLenientParsing() - && ($this->comes('\\0') || $this->comes('\\9')) - ) { - // Non-strings can contain \0 or \9 which is an IE hack supported in lenient parsing. - return null; - } $this->consume('\\'); if ($this->comes('\\n') || $this->comes('\\r')) { return ''; diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index 45cf24d0..0ec26902 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -36,11 +36,6 @@ class Rule implements Renderable, Commentable */ private $isImportant = false; - /** - * @var array - */ - private $ieHack = []; - /** * @var int */ @@ -91,13 +86,6 @@ public static function parse(ParserState $parserState): Rule $parserState->consume(':'); $value = Value::parseValue($parserState, self::listDelimiterForRule($rule->getRule())); $rule->setValue($value); - if ($parserState->getSettings()->usesLenientParsing()) { - while ($parserState->comes('\\')) { - $parserState->consume('\\'); - $rule->addIeHack($parserState->consume()); - $parserState->consumeWhiteSpace(); - } - } $parserState->consumeWhiteSpace(); if ($parserState->comes('!')) { $parserState->consume('!'); @@ -220,30 +208,6 @@ public function addValue($value, $type = ' '): void } } - /** - * @param int $modifier - */ - public function addIeHack($modifier): void - { - $this->ieHack[] = $modifier; - } - - /** - * @param array $modifiers - */ - public function setIeHack(array $modifiers): void - { - $this->ieHack = $modifiers; - } - - /** - * @return array - */ - public function getIeHack() - { - return $this->ieHack; - } - /** * @param bool $isImportant */ @@ -276,9 +240,6 @@ public function render(OutputFormat $outputFormat): string } else { $result .= $this->value; } - if (!empty($this->ieHack)) { - $result .= ' \\' . \implode('\\', $this->ieHack); - } if ($this->isImportant) { $result .= ' !important'; } diff --git a/src/Value/Value.php b/src/Value/Value.php index dcd8686a..c14d74fa 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -50,7 +50,6 @@ public static function parseValue(ParserState $parserState, array $aListDelimite while ( !($parserState->comes('}') || $parserState->comes(';') || $parserState->comes('!') || $parserState->comes(')') - || $parserState->comes('\\') || $parserState->isEnd()) ) { if (\count($aStack) > 0) { diff --git a/tests/ParserTest.php b/tests/ParserTest.php index 7a66f852..040011b7 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -1036,28 +1036,6 @@ public function unexpectedTokenExceptionLineNo(): void } } - /** - * @test - */ - public function ieHacksStrictParsing(): void - { - $this->expectException(UnexpectedTokenException::class); - - // We can't strictly parse IE hacks. - self::parsedStructureForFile('ie-hacks', Settings::create()->beStrict()); - } - - /** - * @test - */ - public function ieHacksParsing(): void - { - $document = self::parsedStructureForFile('ie-hacks', Settings::create()->withLenientParsing(true)); - $expected = 'p {padding-right: .75rem \\9;background-image: none \\9;color: red \\9\\0;' - . 'background-color: red \\9\\0;background-color: red \\9\\0 !important;content: "red \\0";content: "red઼";}'; - self::assertSame($expected, $document->render()); - } - /** * @depends files * diff --git a/tests/fixtures/ie-hacks.css b/tests/fixtures/ie-hacks.css deleted file mode 100644 index 3f5f215e..00000000 --- a/tests/fixtures/ie-hacks.css +++ /dev/null @@ -1,9 +0,0 @@ -p { - padding-right: .75rem \9; - background-image: none \9; - color:red\9\0; - background-color:red \9 \0; - background-color:red \9 \0 !important; - content: "red \9\0"; - content: "red\0abc"; -} From 86aeaa79172f4aa94c044161b743c4e0a84cc477 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 3 Mar 2025 02:25:12 +0100 Subject: [PATCH 278/555] [TASK] Always calculate selector specificity on demand (#1028) This avoids additional state that makes it hard to compare selectors for equality. Also, this will improve performance for cases where the specificity is not needed. Closes #1021 --- src/Property/Selector.php | 63 ++------------------------------------- tests/ParserTest.php | 26 ++++++++-------- 2 files changed, 16 insertions(+), 73 deletions(-) diff --git a/src/Property/Selector.php b/src/Property/Selector.php index ceed44a6..f1bbbcef 100644 --- a/src/Property/Selector.php +++ b/src/Property/Selector.php @@ -5,6 +5,7 @@ namespace Sabberworm\CSS\Property; use Sabberworm\CSS\OutputFormat; +use Sabberworm\CSS\Property\Selector\SpecificityCalculator; use Sabberworm\CSS\Renderable; /** @@ -13,43 +14,6 @@ */ class Selector implements Renderable { - /** - * regexp for specificity calculations - * - * @var string - */ - private const NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX = '/ - (\\.[\\w]+) # classes - | - \\[(\\w+) # attributes - | - (\\:( # pseudo classes - link|visited|active - |hover|focus - |lang - |target - |enabled|disabled|checked|indeterminate - |root - |nth-child|nth-last-child|nth-of-type|nth-last-of-type - |first-child|last-child|first-of-type|last-of-type - |only-child|only-of-type - |empty|contains - )) - /ix'; - - /** - * regexp for specificity calculations - * - * @var string - */ - private const ELEMENTS_AND_PSEUDO_ELEMENTS_RX = '/ - ((^|[\\s\\+\\>\\~]+)[\\w]+ # elements - | - \\:{1,2}( # pseudo-elements - after|before|first-letter|first-line|selection - )) - /ix'; - /** * regexp for specificity calculations * @@ -72,11 +36,6 @@ class Selector implements Renderable */ private $selector; - /** - * @var int|null - */ - private $specificity; - /** * @return bool * @@ -87,15 +46,9 @@ public static function isValid(string $selector) return \preg_match(static::SELECTOR_VALIDATION_RX, $selector); } - /** - * @param bool $calculateSpecificity @deprecated since V8.8.0, will be removed in V9.0.0 - */ - public function __construct(string $selector, bool $calculateSpecificity = false) + public function __construct(string $selector) { $this->setSelector($selector); - if ($calculateSpecificity) { - $this->getSpecificity(); - } } public function getSelector(): string @@ -106,7 +59,6 @@ public function getSelector(): string public function setSelector(string $selector): void { $this->selector = \trim($selector); - $this->specificity = null; } /** @@ -122,16 +74,7 @@ public function __toString(): string */ public function getSpecificity(): int { - if ($this->specificity === null) { - $a = 0; - /// @todo should exclude \# as well as "#" - $aMatches = null; - $b = \substr_count($this->selector, '#'); - $c = \preg_match_all(self::NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX, $this->selector, $aMatches); - $d = \preg_match_all(self::ELEMENTS_AND_PSEUDO_ELEMENTS_RX, $this->selector, $aMatches); - $this->specificity = ($a * 1000) + ($b * 100) + ($c * 10) + $d; - } - return $this->specificity; + return SpecificityCalculator::calculate($this->selector); } public function render(OutputFormat $outputFormat): string diff --git a/tests/ParserTest.php b/tests/ParserTest.php index 040011b7..81271a14 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -246,26 +246,26 @@ public function unicodeRangeParsing(): void public function specificity(): void { $document = self::parsedStructureForFile('specificity'); - self::assertEquals([new Selector('#test .help', true)], $document->getSelectorsBySpecificity('> 100')); + self::assertEquals([new Selector('#test .help')], $document->getSelectorsBySpecificity('> 100')); self::assertEquals( - [new Selector('#test .help', true), new Selector('#file', true)], + [new Selector('#test .help'), new Selector('#file')], $document->getSelectorsBySpecificity('>= 100') ); - self::assertEquals([new Selector('#file', true)], $document->getSelectorsBySpecificity('=== 100')); - self::assertEquals([new Selector('#file', true)], $document->getSelectorsBySpecificity('== 100')); + self::assertEquals([new Selector('#file')], $document->getSelectorsBySpecificity('=== 100')); + self::assertEquals([new Selector('#file')], $document->getSelectorsBySpecificity('== 100')); self::assertEquals([ - new Selector('#file', true), - new Selector('.help:hover', true), - new Selector('li.green', true), - new Selector('ol li::before', true), + new Selector('#file'), + new Selector('.help:hover'), + new Selector('li.green'), + new Selector('ol li::before'), ], $document->getSelectorsBySpecificity('<= 100')); self::assertEquals([ - new Selector('.help:hover', true), - new Selector('li.green', true), - new Selector('ol li::before', true), + new Selector('.help:hover'), + new Selector('li.green'), + new Selector('ol li::before'), ], $document->getSelectorsBySpecificity('< 100')); - self::assertEquals([new Selector('li.green', true)], $document->getSelectorsBySpecificity('11')); - self::assertEquals([new Selector('ol li::before', true)], $document->getSelectorsBySpecificity('3')); + self::assertEquals([new Selector('li.green')], $document->getSelectorsBySpecificity('11')); + self::assertEquals([new Selector('ol li::before')], $document->getSelectorsBySpecificity('3')); } /** From 335a89064cbc8d74895d18e27520e0c7f0de59ab Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 3 Mar 2025 13:26:17 +0100 Subject: [PATCH 279/555] [CLEANUP] Improve some variable names in `RuleSet` (#1039) Part of #756. --- src/RuleSet/RuleSet.php | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 42f7878e..c2ba1d02 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -103,15 +103,15 @@ public function getLineNo(): int public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void { - $sRule = $ruleToAdd->getRule(); - if (!isset($this->rules[$sRule])) { - $this->rules[$sRule] = []; + $propertyName = $ruleToAdd->getRule(); + if (!isset($this->rules[$propertyName])) { + $this->rules[$propertyName] = []; } - $position = \count($this->rules[$sRule]); + $position = \count($this->rules[$propertyName]); if ($sibling !== null) { - $iSiblingPos = \array_search($sibling, $this->rules[$sRule], true); + $iSiblingPos = \array_search($sibling, $this->rules[$propertyName], true); if ($iSiblingPos !== false) { $position = $iSiblingPos; $ruleToAdd->setPosition($sibling->getLineNo(), $sibling->getColNo() - 1); @@ -127,7 +127,7 @@ public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void } } - \array_splice($this->rules[$sRule], $position, 0, [$ruleToAdd]); + \array_splice($this->rules[$propertyName], $position, 0, [$ruleToAdd]); } /** @@ -153,14 +153,15 @@ public function getRules($searchPattern = null) } /** @var array $result */ $result = []; - foreach ($this->rules as $sName => $rules) { + foreach ($this->rules as $propertyName => $rules) { // 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. if ( - !$searchPattern || $sName === $searchPattern + !$searchPattern || $propertyName === $searchPattern || ( \strrpos($searchPattern, '-') === \strlen($searchPattern) - \strlen('-') - && (\strpos($sName, $searchPattern) === 0 || $sName === \substr($searchPattern, 0, -1)) + && (\strpos($propertyName, $searchPattern) === 0 + || $propertyName === \substr($searchPattern, 0, -1)) ) ) { $result = \array_merge($result, $rules); @@ -230,26 +231,27 @@ public function getRulesAssoc($searchPattern = null) public function removeRule($searchPattern): void { if ($searchPattern instanceof Rule) { - $sRule = $searchPattern->getRule(); - if (!isset($this->rules[$sRule])) { + $propertyName = $searchPattern->getRule(); + if (!isset($this->rules[$propertyName])) { return; } - foreach ($this->rules[$sRule] as $key => $rule) { + foreach ($this->rules[$propertyName] as $key => $rule) { if ($rule === $searchPattern) { - unset($this->rules[$sRule][$key]); + unset($this->rules[$propertyName][$key]); } } } else { - foreach ($this->rules as $sName => $rules) { + foreach ($this->rules as $propertyName => $rules) { // 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 ( - !$searchPattern || $sName === $searchPattern + !$searchPattern || $propertyName === $searchPattern || (\strrpos($searchPattern, '-') === \strlen($searchPattern) - \strlen('-') - && (\strpos($sName, $searchPattern) === 0 || $sName === \substr($searchPattern, 0, -1))) + && (\strpos($propertyName, $searchPattern) === 0 + || $propertyName === \substr($searchPattern, 0, -1))) ) { - unset($this->rules[$sName]); + unset($this->rules[$propertyName]); } } } From abcbb303a3cc05c652ba47ff54e0060c53bf38f4 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 3 Mar 2025 13:28:32 +0100 Subject: [PATCH 280/555] [BUGFIX] Fix a type annotation in `RuleSet` (#1051) --- config/phpstan-baseline.neon | 12 ------------ src/RuleSet/RuleSet.php | 5 ++++- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index e17e1b3f..6c802937 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -318,12 +318,6 @@ parameters: count: 1 path: ../src/RuleSet/DeclarationBlock.php - - - message: '#^Argument of an invalid type Sabberworm\\CSS\\Rule\\Rule supplied for foreach, only iterables are supported\.$#' - identifier: foreach.nonIterable - count: 2 - path: ../src/RuleSet/RuleSet.php - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:removeLastSemicolon\(\)\.$#' identifier: method.notFound @@ -384,12 +378,6 @@ parameters: count: 1 path: ../src/RuleSet/RuleSet.php - - - message: '#^Use explicit methods over array access on object$#' - identifier: typePerfect.noArrayAccessOnObject - count: 1 - path: ../src/RuleSet/RuleSet.php - - message: '#^Parameters should have "string" types as the only types passed to this method$#' identifier: typePerfect.narrowPublicClassMethodParamType diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index c2ba1d02..f0de01d8 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -25,7 +25,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 $rules = []; From cafd121d4ef80a6f8948e0987f37f8dfa5bbe012 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 3 Mar 2025 13:41:38 +0100 Subject: [PATCH 281/555] [CLEANUP] Simplify a data provider (#1054) Remove logic to make the data provider easier to grok. --- tests/Unit/Value/ValueTest.php | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/tests/Unit/Value/ValueTest.php b/tests/Unit/Value/ValueTest.php index 380a2ebb..ad7f5765 100644 --- a/tests/Unit/Value/ValueTest.php +++ b/tests/Unit/Value/ValueTest.php @@ -28,17 +28,12 @@ final class ValueTest extends TestCase */ public static function provideArithmeticOperator(): array { - $units = ['+', '-', '*', '/']; - - return \array_combine( - $units, - \array_map( - static function (string $unit): array { - return [$unit]; - }, - $units - ) - ); + return [ + '+' => ['+'], + '-' => ['-'], + '*' => ['*'], + '/' => ['/'], + ]; } /** From 766aaa098366e5dc9532b5c5505261bf80d61556 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 3 Mar 2025 13:42:36 +0100 Subject: [PATCH 282/555] [CLEANUP] Make a type annotation in a test more specific (#1055) --- tests/Unit/Value/ValueTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/Value/ValueTest.php b/tests/Unit/Value/ValueTest.php index ad7f5765..d6dd21af 100644 --- a/tests/Unit/Value/ValueTest.php +++ b/tests/Unit/Value/ValueTest.php @@ -19,7 +19,7 @@ final class ValueTest extends TestCase * * @see \Sabberworm\CSS\Rule\Rule::listDelimiterForRule * - * @var array + * @var list */ private const DEFAULT_DELIMITERS = [',', ' ', '/']; From b52532712e76014329f13fcbd33931d049a41adf Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 3 Mar 2025 14:01:07 +0100 Subject: [PATCH 283/555] [CLEANUP] Avoid Hungarian notation in `RuleSet` (#1004) Part of #756 --- src/RuleSet/RuleSet.php | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index f0de01d8..d1e790ad 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -114,18 +114,18 @@ public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void $position = \count($this->rules[$propertyName]); if ($sibling !== null) { - $iSiblingPos = \array_search($sibling, $this->rules[$propertyName], true); - if ($iSiblingPos !== false) { - $position = $iSiblingPos; + $siblingPosition = \array_search($sibling, $this->rules[$propertyName], true); + if ($siblingPosition !== false) { + $position = $siblingPosition; $ruleToAdd->setPosition($sibling->getLineNo(), $sibling->getColNo() - 1); } } if ($ruleToAdd->getLineNo() === 0 && $ruleToAdd->getColNo() === 0) { //this node is added manually, give it the next best line $rules = $this->getRules(); - $pos = \count($rules); - if ($pos > 0) { - $last = $rules[$pos - 1]; + $rulesCount = \count($rules); + if ($rulesCount > 0) { + $last = $rules[$rulesCount - 1]; $ruleToAdd->setPosition($last->getLineNo() + 1, 0); } } @@ -176,6 +176,7 @@ public function getRules($searchPattern = null) } return $first->getLineNo() - $second->getLineNo(); }); + return $result; } @@ -234,13 +235,13 @@ public function getRulesAssoc($searchPattern = null) public function removeRule($searchPattern): void { if ($searchPattern instanceof Rule) { - $propertyName = $searchPattern->getRule(); - if (!isset($this->rules[$propertyName])) { + $nameOfPropertyToRemove = $searchPattern->getRule(); + if (!isset($this->rules[$nameOfPropertyToRemove])) { return; } - foreach ($this->rules[$propertyName] as $key => $rule) { + foreach ($this->rules[$nameOfPropertyToRemove] as $key => $rule) { if ($rule === $searchPattern) { - unset($this->rules[$propertyName][$key]); + unset($this->rules[$nameOfPropertyToRemove][$key]); } } } else { From bd4e549491697b4bc06efb152076d08e3f7412dc Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 3 Mar 2025 14:54:00 +0100 Subject: [PATCH 284/555] [TASK] Avoid the deprecated `__toString()` in `ParserTest` (#1056) Part of #998 --- tests/ParserTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ParserTest.php b/tests/ParserTest.php index 81271a14..2c233c46 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -193,7 +193,7 @@ public function unicodeParsing(): void continue; } $contentRules = $ruleSet->getRules('content'); - $firstContentRuleAsString = $contentRules[0]->getValue()->__toString(); + $firstContentRuleAsString = $contentRules[0]->getValue()->render(OutputFormat::create()); if ($selector === '.test-1') { self::assertSame('" "', $firstContentRuleAsString); } From 44f1b25915db31fedb42d85a4ce4e5bd45408282 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 4 Mar 2025 00:16:32 +0100 Subject: [PATCH 285/555] [BUGFIX] Render rules in line and column number order (#1059) Fixes #1052 Closes #1058 Co-authored-by: Jake Hotson --- 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 46fc107a..b60f6e61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,7 @@ Please also have a look at our ### Fixed +- Render rules in line and column number order (#1059) - Don't render `rgb` colors with percentage values using hex notation (#803) - Parse `@font-face` `src` property as comma-delimited list (#790) diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index d1e790ad..2ef59d70 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -277,22 +277,20 @@ protected function renderRules(OutputFormat $outputFormat) $result = ''; $isFirst = true; $nextLevelFormat = $outputFormat->nextLevel(); - foreach ($this->rules as $rules) { - foreach ($rules as $rule) { - $renderedRule = $nextLevelFormat->safely(static function () use ($rule, $nextLevelFormat): string { - return $rule->render($nextLevelFormat); - }); - if ($renderedRule === null) { - continue; - } - if ($isFirst) { - $isFirst = false; - $result .= $nextLevelFormat->spaceBeforeRules(); - } else { - $result .= $nextLevelFormat->spaceBetweenRules(); - } - $result .= $renderedRule; + foreach ($this->getRules() as $rule) { + $renderedRule = $nextLevelFormat->safely(static function () use ($rule, $nextLevelFormat): string { + return $rule->render($nextLevelFormat); + }); + if ($renderedRule === null) { + continue; + } + if ($isFirst) { + $isFirst = false; + $result .= $nextLevelFormat->spaceBeforeRules(); + } else { + $result .= $nextLevelFormat->spaceBetweenRules(); } + $result .= $renderedRule; } if (!$isFirst) { diff --git a/tests/Functional/RuleSet/DeclarationBlockTest.php b/tests/Functional/RuleSet/DeclarationBlockTest.php new file mode 100644 index 00000000..ca3d3f87 --- /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::assertStringContainsString($expectedRendering, $declarationBlock->render(new OutputFormat())); + } +} From 4a12d1f8626000114e136b8bb2d44cb470b0991c Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 4 Mar 2025 09:11:08 +0100 Subject: [PATCH 286/555] [TASK] Deprecate method forwarding `OutputFormat` to `OutputFormatter` (#894) Instead, the corresponding method on the formatter should be called directly. This increases type safety and helps static code analysis. Also, it makes the code easier to understand. --- CHANGELOG.md | 2 ++ src/OutputFormat.php | 1 + 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b60f6e61..362a6546 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,8 @@ Please also have a look at our ### Deprecated +- Deprecate magic method forwarding from `OutputFormat` to `OutputFormatter` + (#894) - Deprecate greedy calculation of selector specificity (#1018) - Deprecate `__toString()` (#1006) - `OutputFormat` properties for space around list separators as an array (#880) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 151e698c..1aa410a6 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -244,6 +244,7 @@ public function set($aNames, $mValue) public function __call(string $sMethodName, array $aArguments) { if (\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 f54a5b6185a3919b39a1177911e763153377140f Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 4 Mar 2025 11:35:40 +0100 Subject: [PATCH 287/555] [CLEANUP] Avoid Hungarian notation for `value` (#1063) Part of #756 --- src/Value/Value.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Value/Value.php b/src/Value/Value.php index c14d74fa..a93d0859 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -152,7 +152,7 @@ public static function parseIdentifierOrFunction(ParserState $parserState, $igno */ public static function parsePrimitiveValue(ParserState $parserState) { - $oValue = null; + $value = null; $parserState->consumeWhiteSpace(); if ( \is_numeric($parserState->peek()) @@ -160,31 +160,31 @@ public static function parsePrimitiveValue(ParserState $parserState) && \is_numeric($parserState->peek(1, 2))) || (($parserState->comes('-') || $parserState->comes('.')) && \is_numeric($parserState->peek(1, 1))) ) { - $oValue = Size::parse($parserState); + $value = Size::parse($parserState); } elseif ($parserState->comes('#') || $parserState->comes('rgb', true) || $parserState->comes('hsl', true)) { - $oValue = Color::parse($parserState); + $value = Color::parse($parserState); } elseif ($parserState->comes("'") || $parserState->comes('"')) { - $oValue = CSSString::parse($parserState); + $value = CSSString::parse($parserState); } elseif ($parserState->comes('progid:') && $parserState->getSettings()->usesLenientParsing()) { - $oValue = self::parseMicrosoftFilter($parserState); + $value = self::parseMicrosoftFilter($parserState); } elseif ($parserState->comes('[')) { - $oValue = LineName::parse($parserState); + $value = LineName::parse($parserState); } elseif ($parserState->comes('U+')) { - $oValue = self::parseUnicodeRangeValue($parserState); + $value = self::parseUnicodeRangeValue($parserState); } else { $sNextChar = $parserState->peek(1); try { - $oValue = self::parseIdentifierOrFunction($parserState); + $value = self::parseIdentifierOrFunction($parserState); } catch (UnexpectedTokenException $e) { if (\in_array($sNextChar, ['+', '-', '*', '/'], true)) { - $oValue = $parserState->consume(1); + $value = $parserState->consume(1); } else { throw $e; } } } $parserState->consumeWhiteSpace(); - return $oValue; + return $value; } /** From c19a9554b225ae5260752a088bcfdff485cb9dd4 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 4 Mar 2025 12:27:21 +0100 Subject: [PATCH 288/555] [CLEANUP] Avoid Hungarian notation for `*stack*` (#1064) Part of #756 --- src/Value/Value.php | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Value/Value.php b/src/Value/Value.php index a93d0859..7f452fb3 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -43,8 +43,8 @@ public function __construct($lineNumber = 0) */ public static function parseValue(ParserState $parserState, array $aListDelimiters = []) { - /** @var array $aStack */ - $aStack = []; + /** @var array $stack */ + $stack = []; $parserState->consumeWhiteSpace(); //Build a list of delimiters and parsed values while ( @@ -52,11 +52,11 @@ public static function parseValue(ParserState $parserState, array $aListDelimite || $parserState->comes(')') || $parserState->isEnd()) ) { - if (\count($aStack) > 0) { + if (\count($stack) > 0) { $bFoundDelimiter = false; foreach ($aListDelimiters as $sDelimiter) { if ($parserState->comes($sDelimiter)) { - \array_push($aStack, $parserState->consume($sDelimiter)); + \array_push($stack, $parserState->consume($sDelimiter)); $parserState->consumeWhiteSpace(); $bFoundDelimiter = true; break; @@ -64,40 +64,40 @@ public static function parseValue(ParserState $parserState, array $aListDelimite } if (!$bFoundDelimiter) { //Whitespace was the list delimiter - \array_push($aStack, ' '); + \array_push($stack, ' '); } } - \array_push($aStack, self::parsePrimitiveValue($parserState)); + \array_push($stack, self::parsePrimitiveValue($parserState)); $parserState->consumeWhiteSpace(); } // Convert the list to list objects foreach ($aListDelimiters as $sDelimiter) { - $iStackLength = \count($aStack); - if ($iStackLength === 1) { - return $aStack[0]; + $stackSize = \count($stack); + if ($stackSize === 1) { + return $stack[0]; } - $aNewStack = []; - for ($offset = 0; $offset < $iStackLength; ++$offset) { - if ($offset === ($iStackLength - 1) || $sDelimiter !== $aStack[$offset + 1]) { - $aNewStack[] = $aStack[$offset]; + $newStack = []; + for ($offset = 0; $offset < $stackSize; ++$offset) { + if ($offset === ($stackSize - 1) || $sDelimiter !== $stack[$offset + 1]) { + $newStack[] = $stack[$offset]; continue; } $length = 2; //Number of elements to be joined - for ($i = $offset + 3; $i < $iStackLength; $i += 2, ++$length) { - if ($sDelimiter !== $aStack[$i]) { + for ($i = $offset + 3; $i < $stackSize; $i += 2, ++$length) { + if ($sDelimiter !== $stack[$i]) { break; } } $list = new RuleValueList($sDelimiter, $parserState->currentLine()); for ($i = $offset; $i - $offset < $length * 2; $i += 2) { - $list->addListComponent($aStack[$i]); + $list->addListComponent($stack[$i]); } - $aNewStack[] = $list; + $newStack[] = $list; $offset += $length * 2 - 2; } - $aStack = $aNewStack; + $stack = $newStack; } - if (!isset($aStack[0])) { + if (!isset($stack[0])) { throw new UnexpectedTokenException( " {$parserState->peek()} ", $parserState->peek(1, -1) . $parserState->peek(2), @@ -105,7 +105,7 @@ public static function parseValue(ParserState $parserState, array $aListDelimite $parserState->currentLine() ); } - return $aStack[0]; + return $stack[0]; } /** From 08d30eb6903227ded56488c008aa7e89cbcd8aa2 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 4 Mar 2025 14:01:50 +0100 Subject: [PATCH 289/555] [CLEANUP] Avoid Hungarian notation for `arguments` (#1066) Part of #756 --- src/OutputFormat.php | 6 +++--- src/Value/CSSFunction.php | 20 ++++++++++---------- src/Value/Value.php | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 1aa410a6..47242968 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -235,17 +235,17 @@ public function set($aNames, $mValue) /** * @param non-empty-string $sMethodName - * @param array $aArguments + * @param array $arguments * * @return mixed * * @throws \Exception */ - public function __call(string $sMethodName, array $aArguments) + public function __call(string $sMethodName, array $arguments) { if (\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); + return \call_user_func_array([$this->getFormatter(), $sMethodName], $arguments); } else { throw new \Exception('Unknown OutputFormat method called: ' . $sMethodName); } diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index b32c9c2f..e925a8ee 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -25,19 +25,19 @@ class CSSFunction extends ValueList /** * @param string $sName - * @param RuleValueList|array $aArguments + * @param RuleValueList|array $arguments * @param string $sSeparator * @param int<0, max> $lineNumber */ - public function __construct($sName, $aArguments, $sSeparator = ',', $lineNumber = 0) + public function __construct($sName, $arguments, $sSeparator = ',', $lineNumber = 0) { - if ($aArguments instanceof RuleValueList) { - $sSeparator = $aArguments->getListSeparator(); - $aArguments = $aArguments->getListComponents(); + if ($arguments instanceof RuleValueList) { + $sSeparator = $arguments->getListSeparator(); + $arguments = $arguments->getListComponents(); } $this->sName = $sName; $this->lineNumber = $lineNumber; - parent::__construct($aArguments, $sSeparator, $lineNumber); + parent::__construct($arguments, $sSeparator, $lineNumber); } /** @@ -51,9 +51,9 @@ public static function parse(ParserState $parserState, bool $ignoreCase = false) { $sName = self::parseName($parserState, $ignoreCase); $parserState->consume('('); - $mArguments = self::parseArguments($parserState); + $arguments = self::parseArguments($parserState); - $result = new CSSFunction($sName, $mArguments, ',', $parserState->currentLine()); + $result = new CSSFunction($sName, $arguments, ',', $parserState->currentLine()); $parserState->consume(')'); return $result; @@ -115,7 +115,7 @@ public function __toString(): string public function render(OutputFormat $outputFormat): string { - $aArguments = parent::render($outputFormat); - return "{$this->sName}({$aArguments})"; + $arguments = parent::render($outputFormat); + return "{$this->sName}({$arguments})"; } } diff --git a/src/Value/Value.php b/src/Value/Value.php index 7f452fb3..082ecf96 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -194,8 +194,8 @@ public static function parsePrimitiveValue(ParserState $parserState) private static function parseMicrosoftFilter(ParserState $parserState): CSSFunction { $sFunction = $parserState->consumeUntil('(', false, true); - $aArguments = Value::parseValue($parserState, [',', '=']); - return new CSSFunction($sFunction, $aArguments, ',', $parserState->currentLine()); + $arguments = Value::parseValue($parserState, [',', '=']); + return new CSSFunction($sFunction, $arguments, ',', $parserState->currentLine()); } /** From 4edbbca8ee629d7d713f8669325f817d5b74ff97 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 4 Mar 2025 18:02:43 +0100 Subject: [PATCH 290/555] [CLEANUP] Avoid Hungarian notation for `*delimiter*` (#1067) Part of #756 --- src/Value/Value.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Value/Value.php b/src/Value/Value.php index 082ecf96..aee4cb83 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -32,7 +32,7 @@ public function __construct($lineNumber = 0) } /** - * @param array $aListDelimiters + * @param array $listDelimiters * * @return Value|string * @@ -41,7 +41,7 @@ public function __construct($lineNumber = 0) * * @internal since V8.8.0 */ - public static function parseValue(ParserState $parserState, array $aListDelimiters = []) + public static function parseValue(ParserState $parserState, array $listDelimiters = []) { /** @var array $stack */ $stack = []; @@ -53,16 +53,16 @@ public static function parseValue(ParserState $parserState, array $aListDelimite || $parserState->isEnd()) ) { if (\count($stack) > 0) { - $bFoundDelimiter = false; - foreach ($aListDelimiters as $sDelimiter) { - if ($parserState->comes($sDelimiter)) { - \array_push($stack, $parserState->consume($sDelimiter)); + $foundDelimiter = false; + foreach ($listDelimiters as $delimiter) { + if ($parserState->comes($delimiter)) { + \array_push($stack, $parserState->consume($delimiter)); $parserState->consumeWhiteSpace(); - $bFoundDelimiter = true; + $foundDelimiter = true; break; } } - if (!$bFoundDelimiter) { + if (!$foundDelimiter) { //Whitespace was the list delimiter \array_push($stack, ' '); } @@ -71,24 +71,24 @@ public static function parseValue(ParserState $parserState, array $aListDelimite $parserState->consumeWhiteSpace(); } // Convert the list to list objects - foreach ($aListDelimiters as $sDelimiter) { + foreach ($listDelimiters as $delimiter) { $stackSize = \count($stack); if ($stackSize === 1) { return $stack[0]; } $newStack = []; for ($offset = 0; $offset < $stackSize; ++$offset) { - if ($offset === ($stackSize - 1) || $sDelimiter !== $stack[$offset + 1]) { + if ($offset === ($stackSize - 1) || $delimiter !== $stack[$offset + 1]) { $newStack[] = $stack[$offset]; continue; } $length = 2; //Number of elements to be joined for ($i = $offset + 3; $i < $stackSize; $i += 2, ++$length) { - if ($sDelimiter !== $stack[$i]) { + if ($delimiter !== $stack[$i]) { break; } } - $list = new RuleValueList($sDelimiter, $parserState->currentLine()); + $list = new RuleValueList($delimiter, $parserState->currentLine()); for ($i = $offset; $i - $offset < $length * 2; $i += 2) { $list->addListComponent($stack[$i]); } From 8798bf4cd38fe7701712182b524fa5ab80b63430 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 4 Mar 2025 18:50:42 +0100 Subject: [PATCH 291/555] [CLEANUP] Avoid Hungarian notation for `anchor` (#1068) Part of #756 --- src/Value/URL.php | 4 ++-- src/Value/Value.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Value/URL.php b/src/Value/URL.php index 15eb52e0..62eb9c87 100644 --- a/src/Value/URL.php +++ b/src/Value/URL.php @@ -38,7 +38,7 @@ public function __construct(CSSString $oURL, $lineNumber = 0) */ public static function parse(ParserState $parserState): URL { - $oAnchor = $parserState->anchor(); + $anchor = $parserState->anchor(); $identifier = ''; for ($i = 0; $i < 3; $i++) { $sChar = $parserState->parseCharacter(true); @@ -52,7 +52,7 @@ public static function parse(ParserState $parserState): URL $parserState->consumeWhiteSpace(); $parserState->consume('('); } else { - $oAnchor->backtrack(); + $anchor->backtrack(); } $parserState->consumeWhiteSpace(); $result = new URL(CSSString::parse($parserState), $parserState->currentLine()); diff --git a/src/Value/Value.php b/src/Value/Value.php index aee4cb83..304a547e 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -120,11 +120,11 @@ public static function parseValue(ParserState $parserState, array $listDelimiter */ public static function parseIdentifierOrFunction(ParserState $parserState, $ignoreCase = false) { - $oAnchor = $parserState->anchor(); + $anchor = $parserState->anchor(); $result = $parserState->parseIdentifier($ignoreCase); if ($parserState->comes('(')) { - $oAnchor->backtrack(); + $anchor->backtrack(); if ($parserState->streql('url', $result)) { $result = URL::parse($parserState); } elseif ( From 74dd4a72c66775f5f25e9e2b9bf1cf2da71f4aaf Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 4 Mar 2025 19:09:56 +0100 Subject: [PATCH 292/555] [CLEANUP] Avoid Hungarian notation for `function` (#1070) Part of #756 --- src/Value/CalcFunction.php | 8 ++++---- src/Value/Value.php | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Value/CalcFunction.php b/src/Value/CalcFunction.php index 0958803b..790c6924 100644 --- a/src/Value/CalcFunction.php +++ b/src/Value/CalcFunction.php @@ -29,13 +29,13 @@ class CalcFunction extends CSSFunction public static function parse(ParserState $parserState, bool $ignoreCase = false): CSSFunction { $aOperators = ['+', '-', '*', '/']; - $sFunction = $parserState->parseIdentifier(); + $function = $parserState->parseIdentifier(); if ($parserState->peek() != '(') { // Found ; or end of line before an opening bracket throw new UnexpectedTokenException('(', $parserState->peek(), 'literal', $parserState->currentLine()); - } elseif (!\in_array($sFunction, ['calc', '-moz-calc', '-webkit-calc'], true)) { + } elseif (!\in_array($function, ['calc', '-moz-calc', '-webkit-calc'], true)) { // Found invalid calc definition. Example calc (... - throw new UnexpectedTokenException('calc', $sFunction, 'literal', $parserState->currentLine()); + throw new UnexpectedTokenException('calc', $function, 'literal', $parserState->currentLine()); } $parserState->consume('('); $oCalcList = new CalcRuleValueList($parserState->currentLine()); @@ -100,6 +100,6 @@ public static function parse(ParserState $parserState, bool $ignoreCase = false) if (!$parserState->isEnd()) { $parserState->consume(')'); } - return new CalcFunction($sFunction, $list, ',', $parserState->currentLine()); + return new CalcFunction($function, $list, ',', $parserState->currentLine()); } } diff --git a/src/Value/Value.php b/src/Value/Value.php index 304a547e..0e6d9831 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -193,9 +193,9 @@ public static function parsePrimitiveValue(ParserState $parserState) */ private static function parseMicrosoftFilter(ParserState $parserState): CSSFunction { - $sFunction = $parserState->consumeUntil('(', false, true); + $function = $parserState->consumeUntil('(', false, true); $arguments = Value::parseValue($parserState, [',', '=']); - return new CSSFunction($sFunction, $arguments, ',', $parserState->currentLine()); + return new CSSFunction($function, $arguments, ',', $parserState->currentLine()); } /** From 8dcc1a57b6796a020919a9535ace0d74f03831c8 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 4 Mar 2025 19:10:22 +0100 Subject: [PATCH 293/555] [CLEANUP] Avoid Hungarian notation for `nestingLevel` (#1071) Part of #756 --- src/Value/CalcFunction.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Value/CalcFunction.php b/src/Value/CalcFunction.php index 790c6924..1b56b9b4 100644 --- a/src/Value/CalcFunction.php +++ b/src/Value/CalcFunction.php @@ -40,21 +40,21 @@ public static function parse(ParserState $parserState, bool $ignoreCase = false) $parserState->consume('('); $oCalcList = new CalcRuleValueList($parserState->currentLine()); $list = new RuleValueList(',', $parserState->currentLine()); - $iNestingLevel = 0; + $nestingLevel = 0; $iLastComponentType = null; - while (!$parserState->comes(')') || $iNestingLevel > 0) { - if ($parserState->isEnd() && $iNestingLevel === 0) { + while (!$parserState->comes(')') || $nestingLevel > 0) { + if ($parserState->isEnd() && $nestingLevel === 0) { break; } $parserState->consumeWhiteSpace(); if ($parserState->comes('(')) { - $iNestingLevel++; + $nestingLevel++; $oCalcList->addListComponent($parserState->consume(1)); $parserState->consumeWhiteSpace(); continue; } elseif ($parserState->comes(')')) { - $iNestingLevel--; + $nestingLevel--; $oCalcList->addListComponent($parserState->consume(1)); $parserState->consumeWhiteSpace(); continue; From c2d2bb44aced622a0f162f68bc43890393247215 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 4 Mar 2025 19:25:21 +0100 Subject: [PATCH 294/555] [CLEANUP] Avoid Hungarian notation in `Value` (#1069) Part of #756 --- src/Value/Value.php | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Value/Value.php b/src/Value/Value.php index 0e6d9831..d51d9460 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -172,11 +172,11 @@ public static function parsePrimitiveValue(ParserState $parserState) } elseif ($parserState->comes('U+')) { $value = self::parseUnicodeRangeValue($parserState); } else { - $sNextChar = $parserState->peek(1); + $nextCharacter = $parserState->peek(1); try { $value = self::parseIdentifierOrFunction($parserState); } catch (UnexpectedTokenException $e) { - if (\in_array($sNextChar, ['+', '-', '*', '/'], true)) { + if (\in_array($nextCharacter, ['+', '-', '*', '/'], true)) { $value = $parserState->consume(1); } else { throw $e; @@ -184,6 +184,7 @@ public static function parsePrimitiveValue(ParserState $parserState) } } $parserState->consumeWhiteSpace(); + return $value; } @@ -204,16 +205,17 @@ private static function parseMicrosoftFilter(ParserState $parserState): CSSFunct */ private static function parseUnicodeRangeValue(ParserState $parserState): string { - $iCodepointMaxLength = 6; // Code points outside BMP can use up to six digits - $sRange = ''; + $codepointMaxLength = 6; // Code points outside BMP can use up to six digits + $range = ''; $parserState->consume('U+'); do { if ($parserState->comes('-')) { - $iCodepointMaxLength = 13; // Max length is 2 six-digit code points + the dash(-) between them + $codepointMaxLength = 13; // Max length is 2 six-digit code points + the dash(-) between them } - $sRange .= $parserState->consume(1); - } while (\strlen($sRange) < $iCodepointMaxLength && \preg_match('/[A-Fa-f0-9\\?-]/', $parserState->peek())); - return "U+{$sRange}"; + $range .= $parserState->consume(1); + } while (\strlen($range) < $codepointMaxLength && \preg_match('/[A-Fa-f0-9\\?-]/', $parserState->peek())); + + return "U+{$range}"; } /** From 5421a6dc8c7ce51db9c99ee3e3b426c98ddf09e6 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 4 Mar 2025 19:27:45 +0100 Subject: [PATCH 295/555] [TASK] Mark `OutputFormatter` as `@internal` (#896) This class should only be used for formatting CSS from within this library. It is not intended to be called from outside. --- CHANGELOG.md | 1 + src/OutputFormat.php | 4 ++++ src/OutputFormatter.php | 3 +++ 3 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 362a6546..9f34e14b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Please also have a look at our ### Changed +- Mark `OutputFormatter` as `@internal` (#896) - Make `Selector` a `Renderable` (#1017) - Mark `Selector::isValid()` as `@internal` (#1037) - Mark parsing-related methods of most CSS elements as `@internal` (#908) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 47242968..b65986bb 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -751,11 +751,15 @@ public function beLenient(): void $this->bIgnoreExceptions = true; } + /** + * @internal since 8.8.0 + */ public function getFormatter(): OutputFormatter { if ($this->outputFormatter === null) { $this->outputFormatter = new OutputFormatter($this); } + return $this->outputFormatter; } diff --git a/src/OutputFormatter.php b/src/OutputFormatter.php index 7b61b0ab..fb122fe7 100644 --- a/src/OutputFormatter.php +++ b/src/OutputFormatter.php @@ -7,6 +7,9 @@ use Sabberworm\CSS\Comment\Commentable; use Sabberworm\CSS\Parsing\OutputException; +/** + * @internal since 8.8.0 + */ class OutputFormatter { /** From cc548a7a98e95f8dfa7e3ef5f9ba6a495d363d7f Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 4 Mar 2025 19:53:07 +0100 Subject: [PATCH 296/555] [CLEANUP] Avoid Hungarian notation for `calcRuleValueList` (#1072) Part of #756 --- src/Value/CalcFunction.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Value/CalcFunction.php b/src/Value/CalcFunction.php index 1b56b9b4..6088de1a 100644 --- a/src/Value/CalcFunction.php +++ b/src/Value/CalcFunction.php @@ -38,7 +38,7 @@ public static function parse(ParserState $parserState, bool $ignoreCase = false) throw new UnexpectedTokenException('calc', $function, 'literal', $parserState->currentLine()); } $parserState->consume('('); - $oCalcList = new CalcRuleValueList($parserState->currentLine()); + $calcRuleValueList = new CalcRuleValueList($parserState->currentLine()); $list = new RuleValueList(',', $parserState->currentLine()); $nestingLevel = 0; $iLastComponentType = null; @@ -50,18 +50,18 @@ public static function parse(ParserState $parserState, bool $ignoreCase = false) $parserState->consumeWhiteSpace(); if ($parserState->comes('(')) { $nestingLevel++; - $oCalcList->addListComponent($parserState->consume(1)); + $calcRuleValueList->addListComponent($parserState->consume(1)); $parserState->consumeWhiteSpace(); continue; } elseif ($parserState->comes(')')) { $nestingLevel--; - $oCalcList->addListComponent($parserState->consume(1)); + $calcRuleValueList->addListComponent($parserState->consume(1)); $parserState->consumeWhiteSpace(); continue; } if ($iLastComponentType != CalcFunction::T_OPERAND) { $oVal = Value::parsePrimitiveValue($parserState); - $oCalcList->addListComponent($oVal); + $calcRuleValueList->addListComponent($oVal); $iLastComponentType = CalcFunction::T_OPERAND; } else { if (\in_array($parserState->peek(), $aOperators, true)) { @@ -79,7 +79,7 @@ public static function parse(ParserState $parserState, bool $ignoreCase = false) ); } } - $oCalcList->addListComponent($parserState->consume(1)); + $calcRuleValueList->addListComponent($parserState->consume(1)); $iLastComponentType = CalcFunction::T_OPERATOR; } else { throw new UnexpectedTokenException( @@ -96,7 +96,7 @@ public static function parse(ParserState $parserState, bool $ignoreCase = false) } $parserState->consumeWhiteSpace(); } - $list->addListComponent($oCalcList); + $list->addListComponent($calcRuleValueList); if (!$parserState->isEnd()) { $parserState->consume(')'); } From fbe1ece10f28bdea92bae8327e85544aa3800c3e Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 4 Mar 2025 19:57:21 +0100 Subject: [PATCH 297/555] [CLEANUP] Avoid Hungarian notation for `components` (#1074) Part of #756 --- src/Value/CSSFunction.php | 2 +- src/Value/CalcRuleValueList.php | 2 +- src/Value/Color.php | 24 ++++++++++++------------ src/Value/LineName.php | 6 +++--- src/Value/ValueList.php | 24 ++++++++++++------------ 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index e925a8ee..7cf34726 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -102,7 +102,7 @@ public function setName($sName): void */ public function getArguments() { - return $this->aComponents; + return $this->components; } /** diff --git a/src/Value/CalcRuleValueList.php b/src/Value/CalcRuleValueList.php index da1fbd05..172b05e3 100644 --- a/src/Value/CalcRuleValueList.php +++ b/src/Value/CalcRuleValueList.php @@ -18,6 +18,6 @@ public function __construct($lineNumber = 0) public function render(OutputFormat $outputFormat): string { - return $outputFormat->implode(' ', $this->aComponents); + return $outputFormat->implode(' ', $this->components); } } diff --git a/src/Value/Color.php b/src/Value/Color.php index 81cd7554..996111b1 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -204,7 +204,7 @@ private static function mapRange(float $value, float $fromMin, float $fromMax, f */ public function getColor() { - return $this->aComponents; + return $this->components; } /** @@ -213,7 +213,7 @@ public function getColor() public function setColor(array $colorValues): void { $this->setName(\implode('', \array_keys($colorValues))); - $this->aComponents = $colorValues; + $this->components = $colorValues; } /** @@ -260,7 +260,7 @@ private function shouldRenderAsHex(OutputFormat $outputFormat): bool */ private function getRealName(): string { - return \implode('', \array_keys($this->aComponents)); + return \implode('', \array_keys($this->components)); } /** @@ -269,7 +269,7 @@ private function getRealName(): string */ private function allComponentsAreNumbers(): bool { - foreach ($this->aComponents as $component) { + foreach ($this->components as $component) { if (!($component instanceof Size) || $component->getUnit() !== null) { return false; } @@ -280,7 +280,7 @@ private function allComponentsAreNumbers(): bool /** * Note that this method assumes the following: - * - The `aComponents` array has keys for `r`, `g` and `b`; + * - The `components` array has keys for `r`, `g` and `b`; * - The values in the array are all instances of `Size`. * * Errors will be triggered or thrown if this is not the case. @@ -291,9 +291,9 @@ private function renderAsHex(): string { $result = \sprintf( '%02x%02x%02x', - $this->aComponents['r']->getSize(), - $this->aComponents['g']->getSize(), - $this->aComponents['b']->getSize() + $this->components['r']->getSize(), + $this->components['g']->getSize(), + $this->components['b']->getSize() ); $canUseShortVariant = ($result[0] == $result[1]) && ($result[2] == $result[3]) && ($result[4] == $result[5]); @@ -327,7 +327,7 @@ private function shouldRenderInModernSyntax(): bool $hasPercentage = false; $hasNumber = false; - foreach ($this->aComponents as $key => $value) { + foreach ($this->components as $key => $value) { if ($key === 'a') { // Alpha can have units that don't match those of the RGB components in the "legacy" syntax. // So it is not necessary to check it. It's also always last, hence `break` rather than `continue`. @@ -354,7 +354,7 @@ private function shouldRenderInModernSyntax(): bool private function hasNoneAsComponentValue(): bool { - return \in_array('none', $this->aComponents, true); + return \in_array('none', $this->components, true); } /** @@ -376,10 +376,10 @@ private function colorFunctionMayHaveMixedValueTypes(string $function): bool private function renderInModernSyntax(OutputFormat $outputFormat): string { // Maybe not yet without alpha, but will be... - $componentsWithoutAlpha = $this->aComponents; + $componentsWithoutAlpha = $this->components; \end($componentsWithoutAlpha); if (\key($componentsWithoutAlpha) === 'a') { - $alpha = $this->aComponents['a']; + $alpha = $this->components['a']; unset($componentsWithoutAlpha['a']); } diff --git a/src/Value/LineName.php b/src/Value/LineName.php index 379aae87..25a3605b 100644 --- a/src/Value/LineName.php +++ b/src/Value/LineName.php @@ -12,12 +12,12 @@ class LineName extends ValueList { /** - * @param array $aComponents + * @param array $components * @param int<0, max> $lineNumber */ - public function __construct(array $aComponents = [], $lineNumber = 0) + public function __construct(array $components = [], $lineNumber = 0) { - parent::__construct($aComponents, ' ', $lineNumber); + parent::__construct($components, ' ', $lineNumber); } /** diff --git a/src/Value/ValueList.php b/src/Value/ValueList.php index 5c7b8f84..ae9727f5 100644 --- a/src/Value/ValueList.php +++ b/src/Value/ValueList.php @@ -19,7 +19,7 @@ abstract class ValueList extends Value * * @internal since 8.8.0 */ - protected $aComponents; + protected $components; /** * @var string @@ -29,17 +29,17 @@ abstract class ValueList extends Value protected $sSeparator; /** - * @param array|Value|string $aComponents + * @param array|Value|string $components * @param string $sSeparator * @param int<0, max> $lineNumber */ - public function __construct($aComponents = [], $sSeparator = ',', $lineNumber = 0) + public function __construct($components = [], $sSeparator = ',', $lineNumber = 0) { parent::__construct($lineNumber); - if (!\is_array($aComponents)) { - $aComponents = [$aComponents]; + if (!\is_array($components)) { + $components = [$components]; } - $this->aComponents = $aComponents; + $this->components = $components; $this->sSeparator = $sSeparator; } @@ -48,7 +48,7 @@ public function __construct($aComponents = [], $sSeparator = ',', $lineNumber = */ public function addListComponent($component): void { - $this->aComponents[] = $component; + $this->components[] = $component; } /** @@ -56,15 +56,15 @@ public function addListComponent($component): void */ public function getListComponents() { - return $this->aComponents; + return $this->components; } /** - * @param array $aComponents + * @param array $components */ - public function setListComponents(array $aComponents): void + public function setListComponents(array $components): void { - $this->aComponents = $aComponents; + $this->components = $components; } /** @@ -96,7 +96,7 @@ public function render(OutputFormat $outputFormat): string return $outputFormat->implode( $outputFormat->spaceBeforeListArgumentSeparator($this->sSeparator) . $this->sSeparator . $outputFormat->spaceAfterListArgumentSeparator($this->sSeparator), - $this->aComponents + $this->components ); } } From 15c030f7144ba088908d3da60e3860f2eb97dc6f Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 4 Mar 2025 20:10:43 +0100 Subject: [PATCH 298/555] [CLEANUP] Avoid Hungarian notation for `lastComponentType` (#1075) Part of #756 --- src/Value/CalcFunction.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Value/CalcFunction.php b/src/Value/CalcFunction.php index 6088de1a..e84c9ef1 100644 --- a/src/Value/CalcFunction.php +++ b/src/Value/CalcFunction.php @@ -41,7 +41,7 @@ public static function parse(ParserState $parserState, bool $ignoreCase = false) $calcRuleValueList = new CalcRuleValueList($parserState->currentLine()); $list = new RuleValueList(',', $parserState->currentLine()); $nestingLevel = 0; - $iLastComponentType = null; + $lastComponentType = null; while (!$parserState->comes(')') || $nestingLevel > 0) { if ($parserState->isEnd() && $nestingLevel === 0) { break; @@ -59,10 +59,10 @@ public static function parse(ParserState $parserState, bool $ignoreCase = false) $parserState->consumeWhiteSpace(); continue; } - if ($iLastComponentType != CalcFunction::T_OPERAND) { + if ($lastComponentType != CalcFunction::T_OPERAND) { $oVal = Value::parsePrimitiveValue($parserState); $calcRuleValueList->addListComponent($oVal); - $iLastComponentType = CalcFunction::T_OPERAND; + $lastComponentType = CalcFunction::T_OPERAND; } else { if (\in_array($parserState->peek(), $aOperators, true)) { if (($parserState->comes('-') || $parserState->comes('+'))) { @@ -80,7 +80,7 @@ public static function parse(ParserState $parserState, bool $ignoreCase = false) } } $calcRuleValueList->addListComponent($parserState->consume(1)); - $iLastComponentType = CalcFunction::T_OPERATOR; + $lastComponentType = CalcFunction::T_OPERATOR; } else { throw new UnexpectedTokenException( \sprintf( From 4b1e79ddcf8c5714da7324f26fe4de9c05a29206 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 4 Mar 2025 20:11:19 +0100 Subject: [PATCH 299/555] [CLEANUP] Avoid Hungarian notation for `operators` (#1076) Part of #756 --- src/Value/CalcFunction.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Value/CalcFunction.php b/src/Value/CalcFunction.php index e84c9ef1..b2eaa35f 100644 --- a/src/Value/CalcFunction.php +++ b/src/Value/CalcFunction.php @@ -28,7 +28,7 @@ class CalcFunction extends CSSFunction */ public static function parse(ParserState $parserState, bool $ignoreCase = false): CSSFunction { - $aOperators = ['+', '-', '*', '/']; + $operators = ['+', '-', '*', '/']; $function = $parserState->parseIdentifier(); if ($parserState->peek() != '(') { // Found ; or end of line before an opening bracket @@ -64,7 +64,7 @@ public static function parse(ParserState $parserState, bool $ignoreCase = false) $calcRuleValueList->addListComponent($oVal); $lastComponentType = CalcFunction::T_OPERAND; } else { - if (\in_array($parserState->peek(), $aOperators, true)) { + if (\in_array($parserState->peek(), $operators, true)) { if (($parserState->comes('-') || $parserState->comes('+'))) { if ( $parserState->peek(1, -1) != ' ' @@ -85,7 +85,7 @@ public static function parse(ParserState $parserState, bool $ignoreCase = false) throw new UnexpectedTokenException( \sprintf( 'Next token was expected to be an operand of type %s. Instead "%s" was found.', - \implode(', ', $aOperators), + \implode(', ', $operators), $parserState->peek() ), '', From f19e21f4bf2fc36d77a87c65262b6abbcbab75d5 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 4 Mar 2025 23:10:51 +0100 Subject: [PATCH 300/555] [CLEANUP] Avoid Hungarian notation for `value` (#1077) Part of #756 --- src/OutputFormat.php | 6 +++--- src/Parsing/ParserState.php | 26 +++++++++++++------------- src/Rule/Rule.php | 6 +++--- src/Value/CalcFunction.php | 4 ++-- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index b65986bb..d7f1a096 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -199,11 +199,11 @@ public function get(string $sName) /** * @param array|string $aNames - * @param mixed $mValue + * @param mixed $value * * @return self|false */ - public function set($aNames, $mValue) + public function set($aNames, $value) { $aVarPrefixes = ['a', 's', 'm', 'b', 'f', 'o', 'c', 'i']; if (\is_string($aNames) && \strpos($aNames, '*') !== false) { @@ -221,7 +221,7 @@ public function set($aNames, $mValue) foreach ($aNames as $sName) { $sFieldName = $prefix . \ucfirst($sName); if (isset($this->$sFieldName)) { - $this->$sFieldName = $mValue; + $this->$sFieldName = $value; $bDidReplace = true; } } diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 2100ed35..484006ce 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -251,35 +251,35 @@ public function peek($length = 1, $offset = 0): string } /** - * @param int $mValue + * @param int $value * * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ - public function consume($mValue = 1): string + public function consume($value = 1): string { - if (\is_string($mValue)) { - $iLineCount = \substr_count($mValue, "\n"); - $length = $this->strlen($mValue); - if (!$this->streql($this->substr($this->currentPosition, $length), $mValue)) { + if (\is_string($value)) { + $iLineCount = \substr_count($value, "\n"); + $length = $this->strlen($value); + if (!$this->streql($this->substr($this->currentPosition, $length), $value)) { throw new UnexpectedTokenException( - $mValue, + $value, $this->peek(\max($length, 5)), 'literal', $this->lineNumber ); } $this->lineNumber += $iLineCount; - $this->currentPosition += $this->strlen($mValue); - return $mValue; + $this->currentPosition += $this->strlen($value); + return $value; } else { - if ($this->currentPosition + $mValue > \count($this->characters)) { - throw new UnexpectedEOFException((string) $mValue, $this->peek(5), 'count', $this->lineNumber); + if ($this->currentPosition + $value > \count($this->characters)) { + throw new UnexpectedEOFException((string) $value, $this->peek(5), 'count', $this->lineNumber); } - $result = $this->substr($this->currentPosition, $mValue); + $result = $this->substr($this->currentPosition, $value); $iLineCount = \substr_count($result, "\n"); $this->lineNumber += $iLineCount; - $this->currentPosition += $mValue; + $this->currentPosition += $value; return $result; } } diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index 0ec26902..792371a1 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -177,11 +177,11 @@ public function getValue() } /** - * @param RuleValueList|string|null $mValue + * @param RuleValueList|string|null $value */ - public function setValue($mValue): void + public function setValue($value): void { - $this->value = $mValue; + $this->value = $value; } /** diff --git a/src/Value/CalcFunction.php b/src/Value/CalcFunction.php index b2eaa35f..c3ab02ff 100644 --- a/src/Value/CalcFunction.php +++ b/src/Value/CalcFunction.php @@ -60,8 +60,8 @@ public static function parse(ParserState $parserState, bool $ignoreCase = false) continue; } if ($lastComponentType != CalcFunction::T_OPERAND) { - $oVal = Value::parsePrimitiveValue($parserState); - $calcRuleValueList->addListComponent($oVal); + $value = Value::parsePrimitiveValue($parserState); + $calcRuleValueList->addListComponent($value); $lastComponentType = CalcFunction::T_OPERAND; } else { if (\in_array($parserState->peek(), $operators, true)) { From e6f56ba0c02eede87ddaf60b4f5233e8a3fa7f69 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 4 Mar 2025 23:12:27 +0100 Subject: [PATCH 301/555] [CLEANUP] Avoid Hungarian notation for `separator` (#1078) Part of #756 --- src/OutputFormatter.php | 12 ++++++------ src/Value/CSSFunction.php | 8 ++++---- src/Value/RuleValueList.php | 6 +++--- src/Value/ValueList.php | 20 ++++++++++---------- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/OutputFormatter.php b/src/OutputFormatter.php index fb122fe7..44b128b6 100644 --- a/src/OutputFormatter.php +++ b/src/OutputFormatter.php @@ -119,23 +119,23 @@ public function spaceAfterSelectorSeparator(): string } /** - * @param non-empty-string $sSeparator + * @param non-empty-string $separator */ - public function spaceBeforeListArgumentSeparator(string $sSeparator): string + public function spaceBeforeListArgumentSeparator(string $separator): string { $spaceForSeparator = $this->outputFormat->getSpaceBeforeListArgumentSeparators(); - return $spaceForSeparator[$sSeparator] ?? $this->space('BeforeListArgumentSeparator'); + return $spaceForSeparator[$separator] ?? $this->space('BeforeListArgumentSeparator'); } /** - * @param non-empty-string $sSeparator + * @param non-empty-string $separator */ - public function spaceAfterListArgumentSeparator(string $sSeparator): string + public function spaceAfterListArgumentSeparator(string $separator): string { $spaceForSeparator = $this->outputFormat->getSpaceAfterListArgumentSeparators(); - return $spaceForSeparator[$sSeparator] ?? $this->space('AfterListArgumentSeparator'); + return $spaceForSeparator[$separator] ?? $this->space('AfterListArgumentSeparator'); } public function spaceBeforeOpeningBrace(): string diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index 7cf34726..4ef67ac1 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -26,18 +26,18 @@ class CSSFunction extends ValueList /** * @param string $sName * @param RuleValueList|array $arguments - * @param string $sSeparator + * @param string $separator * @param int<0, max> $lineNumber */ - public function __construct($sName, $arguments, $sSeparator = ',', $lineNumber = 0) + public function __construct($sName, $arguments, $separator = ',', $lineNumber = 0) { if ($arguments instanceof RuleValueList) { - $sSeparator = $arguments->getListSeparator(); + $separator = $arguments->getListSeparator(); $arguments = $arguments->getListComponents(); } $this->sName = $sName; $this->lineNumber = $lineNumber; - parent::__construct($arguments, $sSeparator, $lineNumber); + parent::__construct($arguments, $separator, $lineNumber); } /** diff --git a/src/Value/RuleValueList.php b/src/Value/RuleValueList.php index 33f98eb1..a9dab2ea 100644 --- a/src/Value/RuleValueList.php +++ b/src/Value/RuleValueList.php @@ -12,11 +12,11 @@ class RuleValueList extends ValueList { /** - * @param string $sSeparator + * @param string $separator * @param int<0, max> $lineNumber */ - public function __construct($sSeparator = ',', $lineNumber = 0) + public function __construct($separator = ',', $lineNumber = 0) { - parent::__construct([], $sSeparator, $lineNumber); + parent::__construct([], $separator, $lineNumber); } } diff --git a/src/Value/ValueList.php b/src/Value/ValueList.php index ae9727f5..67ad780f 100644 --- a/src/Value/ValueList.php +++ b/src/Value/ValueList.php @@ -26,21 +26,21 @@ abstract class ValueList extends Value * * @internal since 8.8.0 */ - protected $sSeparator; + protected $separator; /** * @param array|Value|string $components - * @param string $sSeparator + * @param string $separator * @param int<0, max> $lineNumber */ - public function __construct($components = [], $sSeparator = ',', $lineNumber = 0) + public function __construct($components = [], $separator = ',', $lineNumber = 0) { parent::__construct($lineNumber); if (!\is_array($components)) { $components = [$components]; } $this->components = $components; - $this->sSeparator = $sSeparator; + $this->separator = $separator; } /** @@ -72,15 +72,15 @@ public function setListComponents(array $components): void */ public function getListSeparator() { - return $this->sSeparator; + return $this->separator; } /** - * @param string $sSeparator + * @param string $separator */ - public function setListSeparator($sSeparator): void + public function setListSeparator($separator): void { - $this->sSeparator = $sSeparator; + $this->separator = $separator; } /** @@ -94,8 +94,8 @@ public function __toString(): string public function render(OutputFormat $outputFormat): string { return $outputFormat->implode( - $outputFormat->spaceBeforeListArgumentSeparator($this->sSeparator) . $this->sSeparator - . $outputFormat->spaceAfterListArgumentSeparator($this->sSeparator), + $outputFormat->spaceBeforeListArgumentSeparator($this->separator) . $this->separator + . $outputFormat->spaceAfterListArgumentSeparator($this->separator), $this->components ); } From db52ee145227284ba6e70107b5b40cf217151b48 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 4 Mar 2025 23:40:32 +0100 Subject: [PATCH 302/555] [CLEANUP] Avoid Hungarian notation for `name(s)` (#1079) Part of #756 --- src/OutputFormat.php | 26 +++++++++++++------------- src/Value/CSSFunction.php | 22 +++++++++++----------- src/Value/LineName.php | 8 ++++---- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index d7f1a096..58b33637 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -185,11 +185,11 @@ public function __construct() {} /** * @return string|int|bool|null */ - public function get(string $sName) + public function get(string $name) { $aVarPrefixes = ['a', 's', 'm', 'b', 'f', 'o', 'c', 'i']; foreach ($aVarPrefixes as $prefix) { - $sFieldName = $prefix . \ucfirst($sName); + $sFieldName = $prefix . \ucfirst($name); if (isset($this->$sFieldName)) { return $this->$sFieldName; } @@ -198,28 +198,28 @@ public function get(string $sName) } /** - * @param array|string $aNames + * @param array|string $names * @param mixed $value * * @return self|false */ - public function set($aNames, $value) + public function set($names, $value) { $aVarPrefixes = ['a', 's', 'm', 'b', 'f', 'o', 'c', 'i']; - if (\is_string($aNames) && \strpos($aNames, '*') !== false) { - $aNames = + if (\is_string($names) && \strpos($names, '*') !== false) { + $names = [ - \str_replace('*', 'Before', $aNames), - \str_replace('*', 'Between', $aNames), - \str_replace('*', 'After', $aNames), + \str_replace('*', 'Before', $names), + \str_replace('*', 'Between', $names), + \str_replace('*', 'After', $names), ]; - } elseif (!\is_array($aNames)) { - $aNames = [$aNames]; + } elseif (!\is_array($names)) { + $names = [$names]; } foreach ($aVarPrefixes as $prefix) { $bDidReplace = false; - foreach ($aNames as $sName) { - $sFieldName = $prefix . \ucfirst($sName); + foreach ($names as $name) { + $sFieldName = $prefix . \ucfirst($name); if (isset($this->$sFieldName)) { $this->$sFieldName = $value; $bDidReplace = true; diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index 4ef67ac1..985a7a24 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -21,21 +21,21 @@ class CSSFunction extends ValueList * * @internal since 8.8.0 */ - protected $sName; + protected $name; /** - * @param string $sName + * @param string $name * @param RuleValueList|array $arguments * @param string $separator * @param int<0, max> $lineNumber */ - public function __construct($sName, $arguments, $separator = ',', $lineNumber = 0) + public function __construct($name, $arguments, $separator = ',', $lineNumber = 0) { if ($arguments instanceof RuleValueList) { $separator = $arguments->getListSeparator(); $arguments = $arguments->getListComponents(); } - $this->sName = $sName; + $this->name = $name; $this->lineNumber = $lineNumber; parent::__construct($arguments, $separator, $lineNumber); } @@ -49,11 +49,11 @@ public function __construct($sName, $arguments, $separator = ',', $lineNumber = */ public static function parse(ParserState $parserState, bool $ignoreCase = false): CSSFunction { - $sName = self::parseName($parserState, $ignoreCase); + $name = self::parseName($parserState, $ignoreCase); $parserState->consume('('); $arguments = self::parseArguments($parserState); - $result = new CSSFunction($sName, $arguments, ',', $parserState->currentLine()); + $result = new CSSFunction($name, $arguments, ',', $parserState->currentLine()); $parserState->consume(')'); return $result; @@ -86,15 +86,15 @@ private static function parseArguments(ParserState $parserState) */ public function getName() { - return $this->sName; + return $this->name; } /** - * @param string $sName + * @param string $name */ - public function setName($sName): void + public function setName($name): void { - $this->sName = $sName; + $this->name = $name; } /** @@ -116,6 +116,6 @@ public function __toString(): string public function render(OutputFormat $outputFormat): string { $arguments = parent::render($outputFormat); - return "{$this->sName}({$arguments})"; + return "{$this->name}({$arguments})"; } } diff --git a/src/Value/LineName.php b/src/Value/LineName.php index 25a3605b..66b76378 100644 --- a/src/Value/LineName.php +++ b/src/Value/LineName.php @@ -30,23 +30,23 @@ public static function parse(ParserState $parserState): LineName { $parserState->consume('['); $parserState->consumeWhiteSpace(); - $aNames = []; + $names = []; do { if ($parserState->getSettings()->usesLenientParsing()) { try { - $aNames[] = $parserState->parseIdentifier(); + $names[] = $parserState->parseIdentifier(); } catch (UnexpectedTokenException $e) { if (!$parserState->comes(']')) { throw $e; } } } else { - $aNames[] = $parserState->parseIdentifier(); + $names[] = $parserState->parseIdentifier(); } $parserState->consumeWhiteSpace(); } while (!$parserState->comes(']')); $parserState->consume(']'); - return new LineName($aNames, $parserState->currentLine()); + return new LineName($names, $parserState->currentLine()); } /** From f398e3061be5bda2e9564bf0aa0b0c2bce113d46 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 4 Mar 2025 23:59:11 +0100 Subject: [PATCH 303/555] [CLEANUP] Avoid Hungarian notation for `begin` (#1080) Part of #756 --- src/Value/CSSString.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Value/CSSString.php b/src/Value/CSSString.php index 8f87c6e1..169adc26 100644 --- a/src/Value/CSSString.php +++ b/src/Value/CSSString.php @@ -41,11 +41,11 @@ public function __construct($string, $lineNumber = 0) */ public static function parse(ParserState $parserState): CSSString { - $sBegin = $parserState->peek(); + $begin = $parserState->peek(); $sQuote = null; - if ($sBegin === "'") { + if ($begin === "'") { $sQuote = "'"; - } elseif ($sBegin === '"') { + } elseif ($begin === '"') { $sQuote = '"'; } if ($sQuote !== null) { From 13a118a4d59fabd15733ae4960066b40e8e40e1a Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 5 Mar 2025 00:32:34 +0100 Subject: [PATCH 304/555] [CLEANUP] Avoid Hungarian notation for `quote` (#1080) (#1081) Part of #756 --- src/Value/CSSString.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Value/CSSString.php b/src/Value/CSSString.php index 169adc26..162980d7 100644 --- a/src/Value/CSSString.php +++ b/src/Value/CSSString.php @@ -42,24 +42,24 @@ public function __construct($string, $lineNumber = 0) public static function parse(ParserState $parserState): CSSString { $begin = $parserState->peek(); - $sQuote = null; + $quote = null; if ($begin === "'") { - $sQuote = "'"; + $quote = "'"; } elseif ($begin === '"') { - $sQuote = '"'; + $quote = '"'; } - if ($sQuote !== null) { - $parserState->consume($sQuote); + if ($quote !== null) { + $parserState->consume($quote); } $result = ''; $sContent = null; - if ($sQuote === null) { + if ($quote === null) { // Unquoted strings end in whitespace or with braces, brackets, parentheses while (\preg_match('/[\\s{}()<>\\[\\]]/isu', $parserState->peek()) !== 1) { $result .= $parserState->parseCharacter(false); } } else { - while (!$parserState->comes($sQuote)) { + while (!$parserState->comes($quote)) { $sContent = $parserState->parseCharacter(false); if ($sContent === null) { throw new SourceException( @@ -69,7 +69,7 @@ public static function parse(ParserState $parserState): CSSString } $result .= $sContent; } - $parserState->consume($sQuote); + $parserState->consume($quote); } return new CSSString($result, $parserState->currentLine()); } From 7a5933996e50a80f9aace4acdf02d864608d6b58 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 5 Mar 2025 12:04:00 +0100 Subject: [PATCH 305/555] [CLEANUP] Avoid Hungarian notation for `content` (#1082) Part of #756 --- src/Value/CSSString.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Value/CSSString.php b/src/Value/CSSString.php index 162980d7..117bd90f 100644 --- a/src/Value/CSSString.php +++ b/src/Value/CSSString.php @@ -52,7 +52,7 @@ public static function parse(ParserState $parserState): CSSString $parserState->consume($quote); } $result = ''; - $sContent = null; + $content = null; if ($quote === null) { // Unquoted strings end in whitespace or with braces, brackets, parentheses while (\preg_match('/[\\s{}()<>\\[\\]]/isu', $parserState->peek()) !== 1) { @@ -60,14 +60,14 @@ public static function parse(ParserState $parserState): CSSString } } else { while (!$parserState->comes($quote)) { - $sContent = $parserState->parseCharacter(false); - if ($sContent === null) { + $content = $parserState->parseCharacter(false); + if ($content === null) { throw new SourceException( "Non-well-formed quoted string {$parserState->peek(3)}", $parserState->currentLine() ); } - $result .= $sContent; + $result .= $content; } $parserState->consume($quote); } From 2c3fe720c1d8a8584fbf76cdc10cdcb7f6997222 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 5 Mar 2025 12:16:31 +0100 Subject: [PATCH 306/555] [TASK] Extract value parsing functional tests (part 1) (#1084) In the tests, we should test parsing and rendering separately as this makes debugging test failures a lot easier. Part of #1057 --- tests/Functional/Value/ValueTest.php | 45 ++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 tests/Functional/Value/ValueTest.php diff --git a/tests/Functional/Value/ValueTest.php b/tests/Functional/Value/ValueTest.php new file mode 100644 index 00000000..4d65ee2a --- /dev/null +++ b/tests/Functional/Value/ValueTest.php @@ -0,0 +1,45 @@ + + */ + private const DEFAULT_DELIMITERS = [',', ' ', '/']; + + /** + * @test + */ + public function parsesFirstArgumentInMaxFunction(): void + { + $parsedValue = Value::parseValue( + new ParserState('max(300px, 400px);', Settings::create()), + self::DEFAULT_DELIMITERS + ); + + self::assertInstanceOf(CSSFunction::class, $parsedValue); + $size = $parsedValue->getArguments()[0]; + self::assertInstanceOf(Size::class, $size); + self::assertSame(300.0, $size->getSize()); + self::assertSame('px', $size->getUnit()); + self::assertFalse($size->isColorComponent()); + } +} From c5348e9557b3ceb4dd5dc4e0c84a127e117d729b Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 5 Mar 2025 13:33:29 +0100 Subject: [PATCH 307/555] [TASK] Drop special support for vendor prefixes (#1083) In the past, vendor prefixes like `-moz-` or `-webkit-` were used for experimental CSS features in browsers. Nowadays, the browsers use features for this instead. Hence, special support for vendor prefixes is no longer needed. https://developer.mozilla.org/en-US/docs/Glossary/Vendor_Prefix --- CHANGELOG.md | 1 + src/Value/CalcFunction.php | 2 +- src/Value/Value.php | 6 +----- tests/ParserTest.php | 2 +- tests/fixtures/calc.css | 4 ++-- 5 files changed, 6 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f34e14b..15910bbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ Please also have a look at our ### Removed +- Drop special support for vendor prefixes (#1083) - Remove the IE hack in `Rule` (#995) - Drop `getLineNo()` from the `Renderable` interface (#1038) - Remove `OutputFormat::level()` (#874) diff --git a/src/Value/CalcFunction.php b/src/Value/CalcFunction.php index c3ab02ff..12e2638c 100644 --- a/src/Value/CalcFunction.php +++ b/src/Value/CalcFunction.php @@ -33,7 +33,7 @@ public static function parse(ParserState $parserState, bool $ignoreCase = false) if ($parserState->peek() != '(') { // Found ; or end of line before an opening bracket throw new UnexpectedTokenException('(', $parserState->peek(), 'literal', $parserState->currentLine()); - } elseif (!\in_array($function, ['calc', '-moz-calc', '-webkit-calc'], true)) { + } elseif ($function !== 'calc') { // Found invalid calc definition. Example calc (... throw new UnexpectedTokenException('calc', $function, 'literal', $parserState->currentLine()); } diff --git a/src/Value/Value.php b/src/Value/Value.php index d51d9460..49aa5f12 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -127,11 +127,7 @@ public static function parseIdentifierOrFunction(ParserState $parserState, $igno $anchor->backtrack(); if ($parserState->streql('url', $result)) { $result = URL::parse($parserState); - } elseif ( - $parserState->streql('calc', $result) - || $parserState->streql('-webkit-calc', $result) - || $parserState->streql('-moz-calc', $result) - ) { + } elseif ($parserState->streql('calc', $result)) { $result = CalcFunction::parse($parserState); } else { $result = CSSFunction::parse($parserState, $ignoreCase); diff --git a/tests/ParserTest.php b/tests/ParserTest.php index 2c233c46..a5541de7 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -618,7 +618,7 @@ public function calcInFile(): void $document = self::parsedStructureForFile('calc', Settings::create()->withMultibyteSupport(true)); $expected = 'div {width: calc(100% / 4);} div {margin-top: calc(-120% - 4px);} -div {height: -webkit-calc(9 / 16 * 100%) !important;width: -moz-calc(( 50px - 50% ) * 2);} +div {height: calc(9 / 16 * 100%) !important;width: calc(( 50px - 50% ) * 2);} div {width: calc(50% - ( ( 4% ) * .5 ));}'; self::assertSame($expected, $document->render()); } diff --git a/tests/fixtures/calc.css b/tests/fixtures/calc.css index aaaa65ce..e794ade6 100644 --- a/tests/fixtures/calc.css +++ b/tests/fixtures/calc.css @@ -1,7 +1,7 @@ div { width: calc(100% / 4); } div { margin-top: calc(-120% - 4px); } div { - height: -webkit-calc(9/16 * 100%)!important; - width: -moz-calc((50px - 50%)*2); + height: calc(9/16 * 100%)!important; + width: calc((50px - 50%)*2); } div { width: calc(50% - ( ( 4% ) * 0.5 ) ); } From 1ffafd9a5c1c51880018b920362767fb5b432a57 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 5 Mar 2025 13:54:05 +0100 Subject: [PATCH 308/555] [CLEANUP] Avoid Hungarian notation for `size` (#1085) Part of #756 --- src/Value/Size.php | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Value/Size.php b/src/Value/Size.php index 36a2ac6e..f9914633 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -55,7 +55,7 @@ class Size extends PrimitiveValue /** * @var float */ - private $fSize; + private $size; /** * @var string|null @@ -68,15 +68,15 @@ class Size extends PrimitiveValue private $bIsColorComponent; /** - * @param float|int|string $fSize + * @param float|int|string $size * @param string|null $sUnit * @param bool $bIsColorComponent * @param int<0, max> $lineNumber */ - public function __construct($fSize, $sUnit = null, $bIsColorComponent = false, $lineNumber = 0) + public function __construct($size, $sUnit = null, $bIsColorComponent = false, $lineNumber = 0) { parent::__construct($lineNumber); - $this->fSize = (float) $fSize; + $this->size = (float) $size; $this->sUnit = $sUnit; $this->bIsColorComponent = $bIsColorComponent; } @@ -91,22 +91,22 @@ public function __construct($fSize, $sUnit = null, $bIsColorComponent = false, $ */ public static function parse(ParserState $parserState, $bIsColorComponent = false): Size { - $sSize = ''; + $size = ''; if ($parserState->comes('-')) { - $sSize .= $parserState->consume('-'); + $size .= $parserState->consume('-'); } while (\is_numeric($parserState->peek()) || $parserState->comes('.') || $parserState->comes('e', true)) { if ($parserState->comes('.')) { - $sSize .= $parserState->consume('.'); + $size .= $parserState->consume('.'); } elseif ($parserState->comes('e', true)) { $sLookahead = $parserState->peek(1, 1); if (\is_numeric($sLookahead) || $sLookahead === '+' || $sLookahead === '-') { - $sSize .= $parserState->consume(2); + $size .= $parserState->consume(2); } else { break; // Reached the unit part of the number like "em" or "ex" } } else { - $sSize .= $parserState->consume(1); + $size .= $parserState->consume(1); } } @@ -121,7 +121,7 @@ public static function parse(ParserState $parserState, $bIsColorComponent = fals } } } - return new Size((float) $sSize, $sUnit, $bIsColorComponent, $parserState->currentLine()); + return new Size((float) $size, $sUnit, $bIsColorComponent, $parserState->currentLine()); } /** @@ -162,11 +162,11 @@ public function getUnit() } /** - * @param float|int|string $fSize + * @param float|int|string $size */ - public function setSize($fSize): void + public function setSize($size): void { - $this->fSize = (float) $fSize; + $this->size = (float) $size; } /** @@ -174,7 +174,7 @@ public function setSize($fSize): void */ public function getSize() { - return $this->fSize; + return $this->size; } /** @@ -204,7 +204,7 @@ public function isRelative(): bool if (\in_array($this->sUnit, self::RELATIVE_SIZE_UNITS, true)) { return true; } - if ($this->sUnit === null && $this->fSize != 0) { + if ($this->sUnit === null && $this->size != 0) { return true; } return false; @@ -222,9 +222,9 @@ public function render(OutputFormat $outputFormat): string { $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)) : (string) $this->fSize; - return \preg_replace(["/$sPoint/", '/^(-?)0\\./'], ['.', '$1.'], $sSize) + $size = \preg_match('/[\\d\\.]+e[+-]?\\d+/i', (string) $this->size) + ? \preg_replace("/$sPoint?0+$/", '', \sprintf('%f', $this->size)) : (string) $this->size; + return \preg_replace(["/$sPoint/", '/^(-?)0\\./'], ['.', '$1.'], $size) . ($this->sUnit ?? ''); } } From 1d2d5f31684d271a6c3380cb54722c025814338f Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 5 Mar 2025 15:00:36 +0100 Subject: [PATCH 309/555] [CLEANUP] Avoid Hungarian notation for `unit` and `sizeUnits` (#1088) Part of #756 --- src/Value/Size.php | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Value/Size.php b/src/Value/Size.php index f9914633..8a7d0d42 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -60,7 +60,7 @@ class Size extends PrimitiveValue /** * @var string|null */ - private $sUnit; + private $unit; /** * @var bool @@ -69,15 +69,15 @@ class Size extends PrimitiveValue /** * @param float|int|string $size - * @param string|null $sUnit + * @param string|null $unit * @param bool $bIsColorComponent * @param int<0, max> $lineNumber */ - public function __construct($size, $sUnit = null, $bIsColorComponent = false, $lineNumber = 0) + public function __construct($size, $unit = null, $bIsColorComponent = false, $lineNumber = 0) { parent::__construct($lineNumber); $this->size = (float) $size; - $this->sUnit = $sUnit; + $this->unit = $unit; $this->bIsColorComponent = $bIsColorComponent; } @@ -110,18 +110,18 @@ public static function parse(ParserState $parserState, $bIsColorComponent = fals } } - $sUnit = null; - $aSizeUnits = self::getSizeUnits(); - foreach ($aSizeUnits as $length => &$aValues) { + $unit = null; + $sizeUnits = self::getSizeUnits(); + foreach ($sizeUnits as $length => &$aValues) { $sKey = \strtolower($parserState->peek($length)); if (\array_key_exists($sKey, $aValues)) { - if (($sUnit = $aValues[$sKey]) !== null) { + if (($unit = $aValues[$sKey]) !== null) { $parserState->consume($length); break; } } } - return new Size((float) $size, $sUnit, $bIsColorComponent, $parserState->currentLine()); + return new Size((float) $size, $unit, $bIsColorComponent, $parserState->currentLine()); } /** @@ -146,11 +146,11 @@ private static function getSizeUnits() } /** - * @param string $sUnit + * @param string $unit */ - public function setUnit($sUnit): void + public function setUnit($unit): void { - $this->sUnit = $sUnit; + $this->unit = $unit; } /** @@ -158,7 +158,7 @@ public function setUnit($sUnit): void */ public function getUnit() { - return $this->sUnit; + return $this->unit; } /** @@ -193,7 +193,7 @@ public function isColorComponent() */ public function isSize(): bool { - if (\in_array($this->sUnit, self::NON_SIZE_UNITS, true)) { + if (\in_array($this->unit, self::NON_SIZE_UNITS, true)) { return false; } return !$this->isColorComponent(); @@ -201,10 +201,10 @@ public function isSize(): bool public function isRelative(): bool { - if (\in_array($this->sUnit, self::RELATIVE_SIZE_UNITS, true)) { + if (\in_array($this->unit, self::RELATIVE_SIZE_UNITS, true)) { return true; } - if ($this->sUnit === null && $this->size != 0) { + if ($this->unit === null && $this->size != 0) { return true; } return false; @@ -225,6 +225,6 @@ public function render(OutputFormat $outputFormat): string $size = \preg_match('/[\\d\\.]+e[+-]?\\d+/i', (string) $this->size) ? \preg_replace("/$sPoint?0+$/", '', \sprintf('%f', $this->size)) : (string) $this->size; return \preg_replace(["/$sPoint/", '/^(-?)0\\./'], ['.', '$1.'], $size) - . ($this->sUnit ?? ''); + . ($this->unit ?? ''); } } From a548ded7de62e561f13054ff4bd6eb8b414b9b91 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 6 Mar 2025 00:15:18 +0100 Subject: [PATCH 310/555] [CLEANUP] Avoid Hungarian notation for `isColorComponent` (#1090) Part of #756 --- src/Value/Size.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Value/Size.php b/src/Value/Size.php index 8a7d0d42..887ad795 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -65,31 +65,31 @@ class Size extends PrimitiveValue /** * @var bool */ - private $bIsColorComponent; + private $isColorComponent; /** * @param float|int|string $size * @param string|null $unit - * @param bool $bIsColorComponent + * @param bool $isColorComponent * @param int<0, max> $lineNumber */ - public function __construct($size, $unit = null, $bIsColorComponent = false, $lineNumber = 0) + public function __construct($size, $unit = null, $isColorComponent = false, $lineNumber = 0) { parent::__construct($lineNumber); $this->size = (float) $size; $this->unit = $unit; - $this->bIsColorComponent = $bIsColorComponent; + $this->isColorComponent = $isColorComponent; } /** - * @param bool $bIsColorComponent + * @param bool $isColorComponent * * @throws UnexpectedEOFException * @throws UnexpectedTokenException * * @internal since V8.8.0 */ - public static function parse(ParserState $parserState, $bIsColorComponent = false): Size + public static function parse(ParserState $parserState, $isColorComponent = false): Size { $size = ''; if ($parserState->comes('-')) { @@ -121,7 +121,7 @@ public static function parse(ParserState $parserState, $bIsColorComponent = fals } } } - return new Size((float) $size, $unit, $bIsColorComponent, $parserState->currentLine()); + return new Size((float) $size, $unit, $isColorComponent, $parserState->currentLine()); } /** @@ -182,7 +182,7 @@ public function getSize() */ public function isColorComponent() { - return $this->bIsColorComponent; + return $this->isColorComponent; } /** From 0ae4c9aed92b2ba94db538abc8784773039f3183 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 6 Mar 2025 13:55:02 +0100 Subject: [PATCH 311/555] [CLEANUP] Avoid Hungarian notation in `Size::render()` (#1092) Part of #756 --- src/Value/Size.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Value/Size.php b/src/Value/Size.php index 887ad795..b3b8bec7 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -220,11 +220,11 @@ public function __toString(): string public function render(OutputFormat $outputFormat): string { - $l = \localeconv(); - $sPoint = \preg_quote($l['decimal_point'], '/'); + $locale = \localeconv(); + $decimalPoint = \preg_quote($locale['decimal_point'], '/'); $size = \preg_match('/[\\d\\.]+e[+-]?\\d+/i', (string) $this->size) - ? \preg_replace("/$sPoint?0+$/", '', \sprintf('%f', $this->size)) : (string) $this->size; - return \preg_replace(["/$sPoint/", '/^(-?)0\\./'], ['.', '$1.'], $size) - . ($this->unit ?? ''); + ? \preg_replace("/$decimalPoint?0+$/", '', \sprintf('%f', $this->size)) : (string) $this->size; + + return \preg_replace(["/$decimalPoint/", '/^(-?)0\\./'], ['.', '$1.'], $size) . ($this->unit ?? ''); } } From 0fce02b3c11187a8c8cbd00da5a2e3707c465834 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 6 Mar 2025 13:55:57 +0100 Subject: [PATCH 312/555] [CLEANUP] Avoid Hungarian notation for `url` (#1093) Part of #756 --- src/Value/URL.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Value/URL.php b/src/Value/URL.php index 62eb9c87..bf43f0e8 100644 --- a/src/Value/URL.php +++ b/src/Value/URL.php @@ -18,15 +18,15 @@ class URL extends PrimitiveValue /** * @var CSSString */ - private $oURL; + private $url; /** * @param int<0, max> $lineNumber */ - public function __construct(CSSString $oURL, $lineNumber = 0) + public function __construct(CSSString $url, $lineNumber = 0) { parent::__construct($lineNumber); - $this->oURL = $oURL; + $this->url = $url; } /** @@ -63,9 +63,9 @@ public static function parse(ParserState $parserState): URL return $result; } - public function setURL(CSSString $oURL): void + public function setURL(CSSString $url): void { - $this->oURL = $oURL; + $this->url = $url; } /** @@ -73,7 +73,7 @@ public function setURL(CSSString $oURL): void */ public function getURL() { - return $this->oURL; + return $this->url; } /** @@ -86,6 +86,6 @@ public function __toString(): string public function render(OutputFormat $outputFormat): string { - return "url({$this->oURL->render($outputFormat)})"; + return "url({$this->url->render($outputFormat)})"; } } From afdb02331419ae509238bfb908345fa9409983bd Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 6 Mar 2025 14:39:20 +0100 Subject: [PATCH 313/555] [CLEANUP] Avoid Hungarian notation for `tokenLength` (#1089) Part of #756 --- src/Value/Size.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Value/Size.php b/src/Value/Size.php index b3b8bec7..889eba42 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -132,11 +132,11 @@ private static function getSizeUnits() if (!\is_array(self::$SIZE_UNITS)) { self::$SIZE_UNITS = []; foreach (\array_merge(self::ABSOLUTE_SIZE_UNITS, self::RELATIVE_SIZE_UNITS, self::NON_SIZE_UNITS) as $val) { - $iSize = \strlen($val); - if (!isset(self::$SIZE_UNITS[$iSize])) { - self::$SIZE_UNITS[$iSize] = []; + $tokenLength = \strlen($val); + if (!isset(self::$SIZE_UNITS[$tokenLength])) { + self::$SIZE_UNITS[$tokenLength] = []; } - self::$SIZE_UNITS[$iSize][\strtolower($val)] = $val; + self::$SIZE_UNITS[$tokenLength][\strtolower($val)] = $val; } \krsort(self::$SIZE_UNITS, SORT_NUMERIC); From 386d51034cdad290cf2670565e96c0fc8d4a7865 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 6 Mar 2025 14:39:48 +0100 Subject: [PATCH 314/555] [CLEANUP] Avoid Hungarian notation for `character` (#1094) Part of #756 --- src/Value/URL.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Value/URL.php b/src/Value/URL.php index bf43f0e8..cf765034 100644 --- a/src/Value/URL.php +++ b/src/Value/URL.php @@ -41,11 +41,11 @@ public static function parse(ParserState $parserState): URL $anchor = $parserState->anchor(); $identifier = ''; for ($i = 0; $i < 3; $i++) { - $sChar = $parserState->parseCharacter(true); - if ($sChar === null) { + $character = $parserState->parseCharacter(true); + if ($character === null) { break; } - $identifier .= $sChar; + $identifier .= $character; } $bUseUrl = $parserState->streql($identifier, 'url'); if ($bUseUrl) { From b24c4f8a55425330f3840a6ffbfe3897d0f02104 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 6 Mar 2025 15:18:11 +0100 Subject: [PATCH 315/555] [CLEANUP] Avoid Hungarian notation for `sizeUnit` (#1095) Part of #756 --- src/Value/Size.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Value/Size.php b/src/Value/Size.php index 889eba42..0f4fc4bb 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -131,12 +131,13 @@ private static function getSizeUnits() { if (!\is_array(self::$SIZE_UNITS)) { self::$SIZE_UNITS = []; - foreach (\array_merge(self::ABSOLUTE_SIZE_UNITS, self::RELATIVE_SIZE_UNITS, self::NON_SIZE_UNITS) as $val) { - $tokenLength = \strlen($val); + $sizeUnits = \array_merge(self::ABSOLUTE_SIZE_UNITS, self::RELATIVE_SIZE_UNITS, self::NON_SIZE_UNITS); + foreach ($sizeUnits as $sizeUnit) { + $tokenLength = \strlen($sizeUnit); if (!isset(self::$SIZE_UNITS[$tokenLength])) { self::$SIZE_UNITS[$tokenLength] = []; } - self::$SIZE_UNITS[$tokenLength][\strtolower($val)] = $val; + self::$SIZE_UNITS[$tokenLength][\strtolower($sizeUnit)] = $sizeUnit; } \krsort(self::$SIZE_UNITS, SORT_NUMERIC); From f1f3fba974b506813c3ebb6e7741a7faeba49718 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 6 Mar 2025 15:18:54 +0100 Subject: [PATCH 316/555] [CLEANUP] Avoid Hungarian notation for `useUrl` (#1096) Part of #756 --- src/Value/URL.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Value/URL.php b/src/Value/URL.php index cf765034..58edb257 100644 --- a/src/Value/URL.php +++ b/src/Value/URL.php @@ -47,8 +47,8 @@ public static function parse(ParserState $parserState): URL } $identifier .= $character; } - $bUseUrl = $parserState->streql($identifier, 'url'); - if ($bUseUrl) { + $useUrl = $parserState->streql($identifier, 'url'); + if ($useUrl) { $parserState->consumeWhiteSpace(); $parserState->consume('('); } else { @@ -56,7 +56,7 @@ public static function parse(ParserState $parserState): URL } $parserState->consumeWhiteSpace(); $result = new URL(CSSString::parse($parserState), $parserState->currentLine()); - if ($bUseUrl) { + if ($useUrl) { $parserState->consumeWhiteSpace(); $parserState->consume(')'); } From a8f26beba96b774d42e660100d3c195ec9aead2a Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 6 Mar 2025 15:29:37 +0100 Subject: [PATCH 317/555] [CLEANUP] Avoid Hungarian notation in `OutputFormatter` (#1097) Part of #756 --- src/OutputFormatter.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/OutputFormatter.php b/src/OutputFormatter.php index 44b128b6..2eca21fe 100644 --- a/src/OutputFormatter.php +++ b/src/OutputFormatter.php @@ -194,13 +194,15 @@ public function removeLastSemicolon(string $string): string if ($this->outputFormat->getSemicolonAfterLastRule()) { return $string; } + $parts = \explode(';', $string); if (\count($parts) < 2) { return $parts[0]; } - $sLast = \array_pop($parts); - $sNextToLast = \array_pop($parts); - \array_push($parts, $sNextToLast . $sLast); + $lastPart = \array_pop($parts); + $nextToLastPart = \array_pop($parts); + \array_push($parts, $nextToLastPart . $lastPart); + return \implode(';', $parts); } From b4d691c1a9995a78fc4aa88e21bf905413abb081 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 6 Mar 2025 16:49:26 +0100 Subject: [PATCH 318/555] [CLEANUP] Avoid Hungarian notation in `OutputFormat` (part 1) (#1098) Part of #756 --- src/OutputFormat.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 58b33637..e70db43f 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -18,7 +18,7 @@ class OutputFormat * * @var bool */ - private $bRGBHashNotation = true; + private $rgbHashNotation = true; /** * Declaration format @@ -27,7 +27,7 @@ class OutputFormat * * @var bool */ - private $bSemicolonAfterLastRule = true; + private $semicolonAfterLastRule = true; /** * Spacing @@ -38,7 +38,7 @@ class OutputFormat * * @var string */ - private $sSpaceAfterRuleName = ' '; + private $spaceAfterRuleName = ' '; /** * @var string @@ -274,7 +274,7 @@ public function setStringQuotingType(string $quotingType): self */ public function getRGBHashNotation(): bool { - return $this->bRGBHashNotation; + return $this->rgbHashNotation; } /** @@ -282,7 +282,7 @@ public function getRGBHashNotation(): bool */ public function setRGBHashNotation(bool $rgbHashNotation): self { - $this->bRGBHashNotation = $rgbHashNotation; + $this->rgbHashNotation = $rgbHashNotation; return $this; } @@ -292,7 +292,7 @@ public function setRGBHashNotation(bool $rgbHashNotation): self */ public function getSemicolonAfterLastRule(): bool { - return $this->bSemicolonAfterLastRule; + return $this->semicolonAfterLastRule; } /** @@ -300,7 +300,7 @@ public function getSemicolonAfterLastRule(): bool */ public function setSemicolonAfterLastRule(bool $semicolonAfterLastRule): self { - $this->bSemicolonAfterLastRule = $semicolonAfterLastRule; + $this->semicolonAfterLastRule = $semicolonAfterLastRule; return $this; } @@ -310,7 +310,7 @@ public function setSemicolonAfterLastRule(bool $semicolonAfterLastRule): self */ public function getSpaceAfterRuleName(): string { - return $this->sSpaceAfterRuleName; + return $this->spaceAfterRuleName; } /** @@ -318,7 +318,7 @@ public function getSpaceAfterRuleName(): string */ public function setSpaceAfterRuleName(string $whitespace): self { - $this->sSpaceAfterRuleName = $whitespace; + $this->spaceAfterRuleName = $whitespace; return $this; } From 9f7bbbe7fa7974b4543f280ac6b4209a2e3cce98 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 6 Mar 2025 17:57:59 +0100 Subject: [PATCH 319/555] [CLEANUP] Improve some `OutputFormat` property and getter names (#1099) As suggested in #1098. The setters are not changed as those are part of the public API. --- src/OutputFormat.php | 20 ++++++++++---------- src/OutputFormatter.php | 2 +- src/Value/Color.php | 2 +- tests/Unit/OutputFormatTest.php | 12 ++++++------ 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index e70db43f..e8efba52 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -18,7 +18,7 @@ class OutputFormat * * @var bool */ - private $rgbHashNotation = true; + private $usesRgbHashNotation = true; /** * Declaration format @@ -27,7 +27,7 @@ class OutputFormat * * @var bool */ - private $semicolonAfterLastRule = true; + private $renderSemicolonAfterLastRule = true; /** * Spacing @@ -272,17 +272,17 @@ public function setStringQuotingType(string $quotingType): self /** * @internal */ - public function getRGBHashNotation(): bool + public function usesRgbHashNotation(): bool { - return $this->rgbHashNotation; + return $this->usesRgbHashNotation; } /** * @return $this fluent interface */ - public function setRGBHashNotation(bool $rgbHashNotation): self + public function setRGBHashNotation(bool $usesRgbHashNotation): self { - $this->rgbHashNotation = $rgbHashNotation; + $this->usesRgbHashNotation = $usesRgbHashNotation; return $this; } @@ -290,17 +290,17 @@ public function setRGBHashNotation(bool $rgbHashNotation): self /** * @internal */ - public function getSemicolonAfterLastRule(): bool + public function shouldRenderSemicolonAfterLastRule(): bool { - return $this->semicolonAfterLastRule; + return $this->renderSemicolonAfterLastRule; } /** * @return $this fluent interface */ - public function setSemicolonAfterLastRule(bool $semicolonAfterLastRule): self + public function setSemicolonAfterLastRule(bool $renderSemicolonAfterLastRule): self { - $this->semicolonAfterLastRule = $semicolonAfterLastRule; + $this->renderSemicolonAfterLastRule = $renderSemicolonAfterLastRule; return $this; } diff --git a/src/OutputFormatter.php b/src/OutputFormatter.php index 2eca21fe..f7fb17d6 100644 --- a/src/OutputFormatter.php +++ b/src/OutputFormatter.php @@ -191,7 +191,7 @@ public function implode(string $separator, array $values, bool $increaseLevel = public function removeLastSemicolon(string $string): string { - if ($this->outputFormat->getSemicolonAfterLastRule()) { + if ($this->outputFormat->shouldRenderSemicolonAfterLastRule()) { return $string; } diff --git a/src/Value/Color.php b/src/Value/Color.php index 996111b1..d12af070 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -248,7 +248,7 @@ public function render(OutputFormat $outputFormat): string private function shouldRenderAsHex(OutputFormat $outputFormat): bool { return - $outputFormat->getRGBHashNotation() + $outputFormat->usesRgbHashNotation() && $this->getRealName() === 'rgb' && $this->allComponentsAreNumbers(); } diff --git a/tests/Unit/OutputFormatTest.php b/tests/Unit/OutputFormatTest.php index 8792892d..226dd009 100644 --- a/tests/Unit/OutputFormatTest.php +++ b/tests/Unit/OutputFormatTest.php @@ -53,9 +53,9 @@ public function setStringQuotingTypeProvidesFluentInterface(): void /** * @test */ - public function getRGBHashNotationInitiallyReturnsTrue(): void + public function usesRgbHashNotationInitiallyReturnsTrue(): void { - self::assertTrue($this->subject->getRGBHashNotation()); + self::assertTrue($this->subject->usesRgbHashNotation()); } /** @@ -78,7 +78,7 @@ public function setRGBHashNotationSetsRGBHashNotation(bool $value): void { $this->subject->setRGBHashNotation($value); - self::assertSame($value, $this->subject->getRGBHashNotation()); + self::assertSame($value, $this->subject->usesRgbHashNotation()); } /** @@ -92,9 +92,9 @@ public function setRGBHashNotationProvidesFluentInterface(): void /** * @test */ - public function getSemicolonAfterLastRuleInitiallyReturnsTrue(): void + public function shouldRenderSemicolonAfterLastRuleInitiallyReturnsTrue(): void { - self::assertTrue($this->subject->getSemicolonAfterLastRule()); + self::assertTrue($this->subject->shouldRenderSemicolonAfterLastRule()); } /** @@ -106,7 +106,7 @@ public function setSemicolonAfterLastRuleSetsSemicolonAfterLastRule(bool $value) { $this->subject->setSemicolonAfterLastRule($value); - self::assertSame($value, $this->subject->getSemicolonAfterLastRule()); + self::assertSame($value, $this->subject->shouldRenderSemicolonAfterLastRule()); } /** From a074ff572fcde95c066701cd4996f5121854784d Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 6 Mar 2025 20:05:31 +0100 Subject: [PATCH 320/555] [CLEANUP] Avoid Hungarian notation in the tests (#1101) Part of #756 Co-authored-by: JakeQZ --- tests/Comment/CommentTest.php | 6 +++--- tests/ParserTest.php | 30 +++++++++++++------------- tests/RuleSet/DeclarationBlockTest.php | 8 +++---- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/tests/Comment/CommentTest.php b/tests/Comment/CommentTest.php index d80c65f9..7befcc27 100644 --- a/tests/Comment/CommentTest.php +++ b/tests/Comment/CommentTest.php @@ -20,7 +20,7 @@ final class CommentTest extends TestCase */ public function keepCommentsInOutput(): void { - $oCss = TestsParserTest::parsedStructureForFile('comments'); + $cssDocument = TestsParserTest::parsedStructureForFile('comments'); self::assertSame('/** Number 11 **/ /** @@ -45,7 +45,7 @@ public function keepCommentsInOutput(): void position: absolute; } } -', $oCss->render(OutputFormat::createPretty())); +', $cssDocument->render(OutputFormat::createPretty())); self::assertSame( '/** Number 11 **//**' . "\n" . ' * Comments' . "\n" @@ -53,7 +53,7 @@ public function keepCommentsInOutput(): void . '/* 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)) + $cssDocument->render(OutputFormat::createCompact()->setRenderComments(true)) ); } diff --git a/tests/ParserTest.php b/tests/ParserTest.php index a5541de7..47ec1ffa 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -161,8 +161,8 @@ public function colorParsing(): void self::assertEmpty($colorRules); } } - foreach ($document->getAllValues('color') as $sColor) { - self::assertSame('red', $sColor); + foreach ($document->getAllValues('color') as $colorValue) { + self::assertSame('red', $colorValue); } self::assertSame( '#mine {color: red;border-color: #0a64e6;border-color: rgba(10,100,231,.3);outline-color: #222;' @@ -463,9 +463,9 @@ public function functionSyntax(): void . '.collapser.expanded + * {height: auto;}'; self::assertSame($expected, $document->render()); - foreach ($document->getAllValues(null, true) as $mValue) { - if ($mValue instanceof Size && $mValue->isSize()) { - $mValue->setSize($mValue->getSize() * 3); + foreach ($document->getAllValues(null, true) as $value) { + if ($value instanceof Size && $value->isSize()) { + $value->setSize($value->getSize() * 3); } } $expected = \str_replace(['1.2em', '.2em', '60%'], ['3.6em', '.6em', '180%'], $expected); @@ -952,12 +952,12 @@ public function missingPropertyValueLenient(): void * Parses structure for file. * * @param string $filename - * @param Settings|null $oSettings + * @param Settings|null $settings */ - public static function parsedStructureForFile($filename, $oSettings = null): Document + public static function parsedStructureForFile($filename, $settings = null): Document { $filename = __DIR__ . "/fixtures/$filename.css"; - $parser = new Parser(\file_get_contents($filename), $oSettings); + $parser = new Parser(\file_get_contents($filename), $settings); return $parser->parse(); } @@ -1011,8 +1011,8 @@ public function lineNumbersParsing(): void self::assertSame(27, $rules[1]->getLineNo()); $actualColorLineNumbers = []; - foreach ($valueOfSecondRule->getColor() as $oSize) { - $actualColorLineNumbers[] = $oSize->getLineNo(); + foreach ($valueOfSecondRule->getColor() as $size) { + $actualColorLineNumbers[] = $size->getLineNo(); } self::assertSame($expectedColorLineNumbers, $actualColorLineNumbers); @@ -1044,16 +1044,16 @@ public function unexpectedTokenExceptionLineNo(): void public function commentExtracting(): void { $document = self::parsedStructureForFile('comments'); - $aNodes = $document->getContents(); + $nodes = $document->getContents(); // Import property. - $importComments = $aNodes[0]->getComments(); + $importComments = $nodes[0]->getComments(); self::assertCount(2, $importComments); self::assertSame("*\n * Comments\n ", $importComments[0]->getComment()); self::assertSame(' Hell ', $importComments[1]->getComment()); // Declaration block. - $fooBarBlock = $aNodes[1]; + $fooBarBlock = $nodes[1]; $fooBarBlockComments = $fooBarBlock->getComments(); // TODO Support comments in selectors. // $this->assertCount(2, $fooBarBlockComments); @@ -1068,11 +1068,11 @@ public function commentExtracting(): void self::assertSame(' Number 6 ', $fooBarRuleComments[0]->getComment()); // Media property. - $mediaComments = $aNodes[2]->getComments(); + $mediaComments = $nodes[2]->getComments(); self::assertCount(0, $mediaComments); // Media children. - $mediaRules = $aNodes[2]->getContents(); + $mediaRules = $nodes[2]->getContents(); $fooBarComments = $mediaRules[0]->getComments(); self::assertCount(1, $fooBarComments); self::assertSame('* Number 10 *', $fooBarComments[0]->getComment()); diff --git a/tests/RuleSet/DeclarationBlockTest.php b/tests/RuleSet/DeclarationBlockTest.php index 2de33b96..2f5dc9f2 100644 --- a/tests/RuleSet/DeclarationBlockTest.php +++ b/tests/RuleSet/DeclarationBlockTest.php @@ -21,8 +21,8 @@ final class DeclarationBlockTest extends TestCase */ public function overrideRules(): void { - $sCss = '.wrapper { left: 10px; text-align: left; }'; - $parser = new Parser($sCss); + $css = '.wrapper { left: 10px; text-align: left; }'; + $parser = new Parser($css); $document = $parser->parse(); $rule = new Rule('right'); $rule->setValue('-10px'); @@ -43,8 +43,8 @@ public function overrideRules(): void */ public function ruleInsertion(): void { - $sCss = '.wrapper { left: 10px; text-align: left; }'; - $parser = new Parser($sCss); + $css = '.wrapper { left: 10px; text-align: left; }'; + $parser = new Parser($css); $document = $parser->parse(); $contents = $document->getContents(); $wrapper = $contents[0]; From bf6bd0c527bf57dfee408afc97cdd4a6873ca5af Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 6 Mar 2025 22:49:03 +0100 Subject: [PATCH 321/555] [CLEANUP] Avoid Hungarian notation in `SpecificityCalculator` (#1102) Part of #756 --- src/Property/Selector/SpecificityCalculator.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Property/Selector/SpecificityCalculator.php b/src/Property/Selector/SpecificityCalculator.php index 56327c87..745f229d 100644 --- a/src/Property/Selector/SpecificityCalculator.php +++ b/src/Property/Selector/SpecificityCalculator.php @@ -65,10 +65,10 @@ public static function calculate(string $selector): int if (!isset(self::$cache[$selector])) { $a = 0; /// @todo should exclude \# as well as "#" - $aMatches = null; + $matches = null; $b = \substr_count($selector, '#'); - $c = \preg_match_all(self::NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX, $selector, $aMatches); - $d = \preg_match_all(self::ELEMENTS_AND_PSEUDO_ELEMENTS_RX, $selector, $aMatches); + $c = \preg_match_all(self::NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX, $selector, $matches); + $d = \preg_match_all(self::ELEMENTS_AND_PSEUDO_ELEMENTS_RX, $selector, $matches); self::$cache[$selector] = ($a * 1000) + ($b * 100) + ($c * 10) + $d; } From 4f45adbf354c19f906580868ec25c05c12e8c6a4 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 6 Mar 2025 22:53:21 +0100 Subject: [PATCH 322/555] [CLEANUP] Avoid Hungarian notation in `CSSBlockList` (#1104) Part of #756 --- src/CSSList/CSSBlockList.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/CSSList/CSSBlockList.php b/src/CSSList/CSSBlockList.php index 10a7778c..217a6276 100644 --- a/src/CSSList/CSSBlockList.php +++ b/src/CSSList/CSSBlockList.php @@ -87,8 +87,8 @@ protected function allValues( $searchInFunctionArguments = false ): void { if ($element instanceof CSSBlockList) { - foreach ($element->getContents() as $oContent) { - $this->allValues($oContent, $result, $searchString, $searchInFunctionArguments); + foreach ($element->getContents() as $content) { + $this->allValues($content, $result, $searchString, $searchInFunctionArguments); } } elseif ($element instanceof RuleSet) { foreach ($element->getRules($searchString) as $rule) { @@ -117,8 +117,8 @@ protected function allSelectors(array &$result, $specificitySearch = null): void /** @var array $declarationBlocks */ $declarationBlocks = []; $this->allDeclarationBlocks($declarationBlocks); - foreach ($declarationBlocks as $oBlock) { - foreach ($oBlock->getSelectors() as $selector) { + foreach ($declarationBlocks as $declarationBlock) { + foreach ($declarationBlock->getSelectors() as $selector) { if ($specificitySearch === null) { $result[] = $selector; } else { From 07b4db0b4e7b6f02b1c2e3b260c76b2bcd845e77 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 7 Mar 2025 14:31:41 +0100 Subject: [PATCH 323/555] [CLEANUP] Avoid Hungarian notation in `DeclarationBlock` (#1105) Part of #756 --- src/RuleSet/DeclarationBlock.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 5f914d1d..8701f0fa 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -44,11 +44,11 @@ public static function parse(ParserState $parserState, $list = null) $comments = []; $result = new DeclarationBlock($parserState->currentLine()); try { - $aSelectorParts = []; + $selectorParts = []; do { - $aSelectorParts[] = $parserState->consume(1) + $selectorParts[] = $parserState->consume(1) . $parserState->consumeUntil(['{', '}', '\'', '"'], false, false, $comments); - if (\in_array($parserState->peek(), ['\'', '"'], true) && \substr(\end($aSelectorParts), -1) != '\\') { + if (\in_array($parserState->peek(), ['\'', '"'], true) && \substr(\end($selectorParts), -1) != '\\') { if (!isset($stringWrapperCharacter)) { $stringWrapperCharacter = $parserState->peek(); } elseif ($stringWrapperCharacter === $parserState->peek()) { @@ -56,7 +56,7 @@ public static function parse(ParserState $parserState, $list = null) } } } while (!\in_array($parserState->peek(), ['{', '}'], true) || isset($stringWrapperCharacter)); - $result->setSelectors(\implode('', $aSelectorParts), $list); + $result->setSelectors(\implode('', $selectorParts), $list); if ($parserState->comes('{')) { $parserState->consume(1); } From 9e087c5c28c6bda7e3326a1efc664c1f477b682a Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 7 Mar 2025 17:43:20 +0100 Subject: [PATCH 324/555] [CLEANUP] Use the explicit `OutputFormat` setters in the tests (#1106) The `set()` method will be removed soon. Also unify the tests a bit. Part of #1103 --- config/phpstan-baseline.neon | 6 ---- tests/OutputFormatTest.php | 58 ++++++++++++++++++++++++++++-------- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 6c802937..43f97d19 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -102,12 +102,6 @@ parameters: count: 2 path: ../src/CSSList/KeyFrame.php - - - message: '#^Parameters should have "string\|string" types as the only types passed to this method$#' - identifier: typePerfect.narrowPublicClassMethodParamType - count: 1 - path: ../src/OutputFormat.php - - message: '#^Returning false in non return bool class method\. Use null with type\|null instead or add bool return type$#' identifier: typePerfect.nullOverFalse diff --git a/tests/OutputFormatTest.php b/tests/OutputFormatTest.php index c9d7b068..3679245d 100644 --- a/tests/OutputFormatTest.php +++ b/tests/OutputFormatTest.php @@ -182,6 +182,11 @@ public function spaceAfterRuleName(): void */ public function spaceRules(): void { + $outputFormat = OutputFormat::create() + ->setSpaceBeforeRules("\n") + ->setSpaceBetweenRules("\n") + ->setSpaceAfterRules("\n"); + self::assertSame('.main, .test { font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif; background: white; @@ -190,7 +195,7 @@ public function spaceRules(): void background-size: 100% 100%; font-size: 1.3em; background-color: #fff; - }}', $this->document->render(OutputFormat::create()->set('Space*Rules', "\n"))); + }}', $this->document->render($outputFormat)); } /** @@ -198,12 +203,17 @@ public function spaceRules(): void */ public function spaceBlocks(): void { + $outputFormat = OutputFormat::create() + ->setSpaceBeforeBlocks("\n") + ->setSpaceBetweenBlocks("\n") + ->setSpaceAfterBlocks("\n"); + 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;} } -', $this->document->render(OutputFormat::create()->set('Space*Blocks', "\n"))); +', $this->document->render($outputFormat)); } /** @@ -211,6 +221,14 @@ public function spaceBlocks(): void */ public function spaceBoth(): void { + $outputFormat = OutputFormat::create() + ->setSpaceBeforeRules("\n") + ->setSpaceBetweenRules("\n") + ->setSpaceAfterRules("\n") + ->setSpaceBeforeBlocks("\n") + ->setSpaceBetweenBlocks("\n") + ->setSpaceAfterBlocks("\n"); + self::assertSame(' .main, .test { font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif; @@ -223,7 +241,7 @@ public function spaceBoth(): void background-color: #fff; } } -', $this->document->render(OutputFormat::create()->set('Space*Rules', "\n")->set('Space*Blocks', "\n"))); +', $this->document->render($outputFormat)); } /** @@ -231,10 +249,13 @@ public function spaceBoth(): void */ public function spaceBetweenBlocks(): void { + $outputFormat = OutputFormat::create() + ->setSpaceBetweenBlocks(''); + 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;}}', - $this->document->render(OutputFormat::create()->setSpaceBetweenBlocks('')) + $this->document->render($outputFormat) ); } @@ -243,6 +264,15 @@ public function spaceBetweenBlocks(): void */ public function indentation(): void { + $outputFormat = OutputFormat::create() + ->setSpaceBeforeRules("\n") + ->setSpaceBetweenRules("\n") + ->setSpaceAfterRules("\n") + ->setSpaceBeforeBlocks("\n") + ->setSpaceBetweenBlocks("\n") + ->setSpaceAfterBlocks("\n") + ->setIndentation(''); + self::assertSame(' .main, .test { font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif; @@ -255,10 +285,7 @@ public function indentation(): void background-color: #fff; } } -', $this->document->render(OutputFormat::create() - ->set('Space*Rules', "\n") - ->set('Space*Blocks', "\n") - ->setIndentation(''))); +', $this->document->render($outputFormat)); } /** @@ -266,10 +293,13 @@ public function indentation(): void */ public function spaceBeforeBraces(): void { + $outputFormat = OutputFormat::create() + ->setSpaceBeforeOpeningBrace(''); + 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;}}', - $this->document->render(OutputFormat::create()->setSpaceBeforeOpeningBrace('')) + $this->document->render($outputFormat) ); } @@ -280,16 +310,18 @@ public function ignoreExceptionsOff(): void { $this->expectException(OutputException::class); + $outputFormat = OutputFormat::create()->setIgnoreExceptions(false); + $declarationBlocks = $this->document->getAllDeclarationBlocks(); $firstDeclarationBlock = $declarationBlocks[0]; $firstDeclarationBlock->removeSelector('.main'); self::assertSame( '.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;}}', - $this->document->render(OutputFormat::create()->setIgnoreExceptions(false)) + $this->document->render($outputFormat) ); $firstDeclarationBlock->removeSelector('.test'); - $this->document->render(OutputFormat::create()->setIgnoreExceptions(false)); + $this->document->render($outputFormat); } /** @@ -297,13 +329,15 @@ public function ignoreExceptionsOff(): void */ public function ignoreExceptionsOn(): void { + $outputFormat = OutputFormat::create()->setIgnoreExceptions(true); + $declarationBlocks = $this->document->getAllDeclarationBlocks(); $firstDeclarationBlock = $declarationBlocks[0]; $firstDeclarationBlock->removeSelector('.main'); $firstDeclarationBlock->removeSelector('.test'); self::assertSame( '@media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}', - $this->document->render(OutputFormat::create()->setIgnoreExceptions(true)) + $this->document->render($outputFormat) ); } } From b7367c81fd3d5c95c84a62645a712f53cc13d361 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 7 Mar 2025 17:47:22 +0100 Subject: [PATCH 325/555] [TASK] Remove `OutputFormat::get()` (#1108) We now have beautiful, cleanly-typed getters to use instead. Part of #1103 --- CHANGELOG.md | 1 + config/phpstan-baseline.neon | 2 +- src/OutputFormat.php | 15 --------------- 3 files changed, 2 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15910bbf..89caf5a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ Please also have a look at our ### Removed +- Remove `OutputFormat::get()` (#1108) - Drop special support for vendor prefixes (#1083) - Remove the IE hack in `Rule` (#995) - Drop `getLineNo()` from the `Renderable` interface (#1038) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 43f97d19..ecced88c 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -111,7 +111,7 @@ parameters: - message: '#^Variable property access on \$this\(Sabberworm\\CSS\\OutputFormat\)\.$#' identifier: property.dynamicName - count: 4 + count: 2 path: ../src/OutputFormat.php - diff --git a/src/OutputFormat.php b/src/OutputFormat.php index e8efba52..0d83c4ee 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -182,21 +182,6 @@ class OutputFormat public function __construct() {} - /** - * @return string|int|bool|null - */ - public function get(string $name) - { - $aVarPrefixes = ['a', 's', 'm', 'b', 'f', 'o', 'c', 'i']; - foreach ($aVarPrefixes as $prefix) { - $sFieldName = $prefix . \ucfirst($name); - if (isset($this->$sFieldName)) { - return $this->$sFieldName; - } - } - return null; - } - /** * @param array|string $names * @param mixed $value From fff77eba95003f7807c5291f76ef3c67640845d8 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 7 Mar 2025 17:48:55 +0100 Subject: [PATCH 326/555] [CLEANUP] Avoid Hungarian notation in comments (#1109) Part of #756 --- src/RuleSet/RuleSet.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 2ef59d70..081492aa 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -145,7 +145,7 @@ public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void * 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` behaves like calling `getRules($rule->getRule())`. * * @return array */ @@ -204,7 +204,7 @@ public function setRules(array $rules): void * @param Rule|string|null $searchPattern * 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` behaves like calling `getRules($rule->getRule())`. * * @return array */ From a031106137f709114fe661cf1eee4ceb78260c0d Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 7 Mar 2025 18:03:31 +0100 Subject: [PATCH 327/555] [TASK ] Remove `OutputFormat::set()` (#1110) Part of #1103 Closes #1103 --- CHANGELOG.md | 2 +- config/phpstan-baseline.neon | 12 ------------ src/OutputFormat.php | 36 ------------------------------------ 3 files changed, 1 insertion(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89caf5a7..495f8d3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,7 +43,7 @@ Please also have a look at our ### Removed -- Remove `OutputFormat::get()` (#1108) +- Remove `OutputFormat::get()` and `::set()` (#1108, #1110) - Drop special support for vendor prefixes (#1083) - Remove the IE hack in `Rule` (#995) - Drop `getLineNo()` from the `Renderable` interface (#1038) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index ecced88c..df371d85 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -102,18 +102,6 @@ parameters: count: 2 path: ../src/CSSList/KeyFrame.php - - - message: '#^Returning false in non return bool class method\. Use null with type\|null instead or add bool return type$#' - identifier: typePerfect.nullOverFalse - count: 1 - path: ../src/OutputFormat.php - - - - message: '#^Variable property access on \$this\(Sabberworm\\CSS\\OutputFormat\)\.$#' - identifier: property.dynamicName - count: 2 - path: ../src/OutputFormat.php - - message: '#^Default value of the parameter \#2 \$bIncludeEnd \(false\) of method Sabberworm\\CSS\\Parsing\\ParserState\:\:consumeUntil\(\) is incompatible with type string\.$#' identifier: parameter.defaultValue diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 0d83c4ee..c3e5f996 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -182,42 +182,6 @@ class OutputFormat public function __construct() {} - /** - * @param array|string $names - * @param mixed $value - * - * @return self|false - */ - public function set($names, $value) - { - $aVarPrefixes = ['a', 's', 'm', 'b', 'f', 'o', 'c', 'i']; - if (\is_string($names) && \strpos($names, '*') !== false) { - $names = - [ - \str_replace('*', 'Before', $names), - \str_replace('*', 'Between', $names), - \str_replace('*', 'After', $names), - ]; - } elseif (!\is_array($names)) { - $names = [$names]; - } - foreach ($aVarPrefixes as $prefix) { - $bDidReplace = false; - foreach ($names as $name) { - $sFieldName = $prefix . \ucfirst($name); - if (isset($this->$sFieldName)) { - $this->$sFieldName = $value; - $bDidReplace = true; - } - } - if ($bDidReplace) { - return $this; - } - } - // Break the chain so the user knows this option is invalid - return false; - } - /** * @param non-empty-string $sMethodName * @param array $arguments From 67eb1057b34440deaf19a4444e84a418b98d96b7 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 7 Mar 2025 18:04:27 +0100 Subject: [PATCH 328/555] [CLEANUP] Avoid Hungarian notation in `OutputFormat` (part 2) (#1100) Part of #756 --- src/OutputFormat.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index c3e5f996..bd24f83d 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -43,17 +43,17 @@ class OutputFormat /** * @var string */ - private $sSpaceBeforeRules = ''; + private $spaceBeforeRules = ''; /** * @var string */ - private $sSpaceAfterRules = ''; + private $spaceAfterRules = ''; /** * @var string */ - private $sSpaceBetweenRules = ''; + private $spaceBetweenRules = ''; /** * @var string @@ -277,7 +277,7 @@ public function setSpaceAfterRuleName(string $whitespace): self */ public function getSpaceBeforeRules(): string { - return $this->sSpaceBeforeRules; + return $this->spaceBeforeRules; } /** @@ -285,7 +285,7 @@ public function getSpaceBeforeRules(): string */ public function setSpaceBeforeRules(string $whitespace): self { - $this->sSpaceBeforeRules = $whitespace; + $this->spaceBeforeRules = $whitespace; return $this; } @@ -295,7 +295,7 @@ public function setSpaceBeforeRules(string $whitespace): self */ public function getSpaceAfterRules(): string { - return $this->sSpaceAfterRules; + return $this->spaceAfterRules; } /** @@ -303,7 +303,7 @@ public function getSpaceAfterRules(): string */ public function setSpaceAfterRules(string $whitespace): self { - $this->sSpaceAfterRules = $whitespace; + $this->spaceAfterRules = $whitespace; return $this; } @@ -313,7 +313,7 @@ public function setSpaceAfterRules(string $whitespace): self */ public function getSpaceBetweenRules(): string { - return $this->sSpaceBetweenRules; + return $this->spaceBetweenRules; } /** @@ -321,7 +321,7 @@ public function getSpaceBetweenRules(): string */ public function setSpaceBetweenRules(string $whitespace): self { - $this->sSpaceBetweenRules = $whitespace; + $this->spaceBetweenRules = $whitespace; return $this; } From 150007cfd3ebcb5b2cd383e9a7c7272033504d98 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 7 Mar 2025 18:28:00 +0100 Subject: [PATCH 329/555] [CLEANUP] Avoid Hungarian notation in `Size` (#1111) Part of #756 --- src/Value/Size.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Value/Size.php b/src/Value/Size.php index 0f4fc4bb..53723bed 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -99,8 +99,8 @@ public static function parse(ParserState $parserState, $isColorComponent = false if ($parserState->comes('.')) { $size .= $parserState->consume('.'); } elseif ($parserState->comes('e', true)) { - $sLookahead = $parserState->peek(1, 1); - if (\is_numeric($sLookahead) || $sLookahead === '+' || $sLookahead === '-') { + $lookahead = $parserState->peek(1, 1); + if (\is_numeric($lookahead) || $lookahead === '+' || $lookahead === '-') { $size .= $parserState->consume(2); } else { break; // Reached the unit part of the number like "em" or "ex" @@ -112,10 +112,10 @@ public static function parse(ParserState $parserState, $isColorComponent = false $unit = null; $sizeUnits = self::getSizeUnits(); - foreach ($sizeUnits as $length => &$aValues) { - $sKey = \strtolower($parserState->peek($length)); - if (\array_key_exists($sKey, $aValues)) { - if (($unit = $aValues[$sKey]) !== null) { + foreach ($sizeUnits as $length => &$values) { + $key = \strtolower($parserState->peek($length)); + if (\array_key_exists($key, $values)) { + if (($unit = $values[$key]) !== null) { $parserState->consume($length); break; } From c2276200da7bfeee79260dcead0591730392ebf4 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 8 Mar 2025 13:58:58 +0100 Subject: [PATCH 330/555] [CLEANUP] Avoid Hungarian notation for `numberOfLines` (#1112) Part of #756 --- src/Parsing/ParserState.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 484006ce..b6ede7af 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -259,7 +259,7 @@ public function peek($length = 1, $offset = 0): string public function consume($value = 1): string { if (\is_string($value)) { - $iLineCount = \substr_count($value, "\n"); + $numberOfLines = \substr_count($value, "\n"); $length = $this->strlen($value); if (!$this->streql($this->substr($this->currentPosition, $length), $value)) { throw new UnexpectedTokenException( @@ -269,7 +269,7 @@ public function consume($value = 1): string $this->lineNumber ); } - $this->lineNumber += $iLineCount; + $this->lineNumber += $numberOfLines; $this->currentPosition += $this->strlen($value); return $value; } else { @@ -277,8 +277,8 @@ public function consume($value = 1): string throw new UnexpectedEOFException((string) $value, $this->peek(5), 'count', $this->lineNumber); } $result = $this->substr($this->currentPosition, $value); - $iLineCount = \substr_count($result, "\n"); - $this->lineNumber += $iLineCount; + $numberOfLines = \substr_count($result, "\n"); + $this->lineNumber += $numberOfLines; $this->currentPosition += $value; return $result; } From 8dcfa915e11443c9642f2a49bd2fe807e4f3b444 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 8 Mar 2025 13:59:42 +0100 Subject: [PATCH 331/555] [CLEANUP] Avoid Hungarian notation for `matches` (#1113) Part of #756 --- src/Parsing/ParserState.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index b6ede7af..5814f814 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -293,10 +293,10 @@ public function consume($value = 1): string */ public function consumeExpression($mExpression, $iMaxLength = null): string { - $aMatches = null; + $matches = null; $sInput = $iMaxLength !== null ? $this->peek($iMaxLength) : $this->inputLeft(); - if (\preg_match($mExpression, $sInput, $aMatches, PREG_OFFSET_CAPTURE) === 1) { - return $this->consume($aMatches[0][0]); + if (\preg_match($mExpression, $sInput, $matches, PREG_OFFSET_CAPTURE) === 1) { + return $this->consume($matches[0][0]); } throw new UnexpectedTokenException($mExpression, $this->peek(5), 'expression', $this->lineNumber); } From 7d45a7f0891a46ad1923dd7bd13c9060710576dd Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 8 Mar 2025 14:02:15 +0100 Subject: [PATCH 332/555] [CLEANUP] Avoid Hungarian notation in `OutputFormat` (part 3) (#1114) Part of #756 --- src/OutputFormat.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index bd24f83d..2216b0a5 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -58,17 +58,17 @@ class OutputFormat /** * @var string */ - private $sSpaceBeforeBlocks = ''; + private $spaceBeforeBlocks = ''; /** * @var string */ - private $sSpaceAfterBlocks = ''; + private $spaceAfterBlocks = ''; /** * @var string */ - private $sSpaceBetweenBlocks = "\n"; + private $spaceBetweenBlocks = "\n"; /** * Content injected in and around at-rule blocks. @@ -331,7 +331,7 @@ public function setSpaceBetweenRules(string $whitespace): self */ public function getSpaceBeforeBlocks(): string { - return $this->sSpaceBeforeBlocks; + return $this->spaceBeforeBlocks; } /** @@ -339,7 +339,7 @@ public function getSpaceBeforeBlocks(): string */ public function setSpaceBeforeBlocks(string $whitespace): self { - $this->sSpaceBeforeBlocks = $whitespace; + $this->spaceBeforeBlocks = $whitespace; return $this; } @@ -349,7 +349,7 @@ public function setSpaceBeforeBlocks(string $whitespace): self */ public function getSpaceAfterBlocks(): string { - return $this->sSpaceAfterBlocks; + return $this->spaceAfterBlocks; } /** @@ -357,7 +357,7 @@ public function getSpaceAfterBlocks(): string */ public function setSpaceAfterBlocks(string $whitespace): self { - $this->sSpaceAfterBlocks = $whitespace; + $this->spaceAfterBlocks = $whitespace; return $this; } @@ -367,7 +367,7 @@ public function setSpaceAfterBlocks(string $whitespace): self */ public function getSpaceBetweenBlocks(): string { - return $this->sSpaceBetweenBlocks; + return $this->spaceBetweenBlocks; } /** @@ -375,7 +375,7 @@ public function getSpaceBetweenBlocks(): string */ public function setSpaceBetweenBlocks(string $whitespace): self { - $this->sSpaceBetweenBlocks = $whitespace; + $this->spaceBetweenBlocks = $whitespace; return $this; } From 1229b3b07f8d3fe5ab875f167a56bd576cbfe16e Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 8 Mar 2025 15:07:56 +0100 Subject: [PATCH 333/555] [CLEANUP] Avoid Hungarian notation in `OutputFormat` (part 4) (#1115) Also rename the (internal) getters to match the changed property names. Part of #756 --- src/CSSList/AtRuleBlockList.php | 4 ++-- src/OutputFormat.php | 16 ++++++++-------- tests/Unit/OutputFormatTest.php | 12 ++++++------ 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/CSSList/AtRuleBlockList.php b/src/CSSList/AtRuleBlockList.php index 577c6b8f..2fbe755c 100644 --- a/src/CSSList/AtRuleBlockList.php +++ b/src/CSSList/AtRuleBlockList.php @@ -53,7 +53,7 @@ public function __toString(): string public function render(OutputFormat $outputFormat): string { $result = $outputFormat->comments($this); - $result .= $outputFormat->getBeforeAtRuleBlock(); + $result .= $outputFormat->getContentBeforeAtRuleBlock(); $arguments = $this->arguments; if ($arguments) { $arguments = ' ' . $arguments; @@ -61,7 +61,7 @@ public function render(OutputFormat $outputFormat): string $result .= "@{$this->type}$arguments{$outputFormat->spaceBeforeOpeningBrace()}{"; $result .= $this->renderListContents($outputFormat); $result .= '}'; - $result .= $outputFormat->getAfterAtRuleBlock(); + $result .= $outputFormat->getContentAfterAtRuleBlock(); return $result; } diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 2216b0a5..df7a4d85 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -75,12 +75,12 @@ class OutputFormat * * @var string */ - private $sBeforeAtRuleBlock = ''; + private $contentBeforeAtRuleBlock = ''; /** * @var string */ - private $sAfterAtRuleBlock = ''; + private $contentAfterAtRuleBlock = ''; /** * This is what’s printed before and after the comma if a declaration block contains multiple selectors. @@ -383,9 +383,9 @@ public function setSpaceBetweenBlocks(string $whitespace): self /** * @internal */ - public function getBeforeAtRuleBlock(): string + public function getContentBeforeAtRuleBlock(): string { - return $this->sBeforeAtRuleBlock; + return $this->contentBeforeAtRuleBlock; } /** @@ -393,7 +393,7 @@ public function getBeforeAtRuleBlock(): string */ public function setBeforeAtRuleBlock(string $content): self { - $this->sBeforeAtRuleBlock = $content; + $this->contentBeforeAtRuleBlock = $content; return $this; } @@ -401,9 +401,9 @@ public function setBeforeAtRuleBlock(string $content): self /** * @internal */ - public function getAfterAtRuleBlock(): string + public function getContentAfterAtRuleBlock(): string { - return $this->sAfterAtRuleBlock; + return $this->contentAfterAtRuleBlock; } /** @@ -411,7 +411,7 @@ public function getAfterAtRuleBlock(): string */ public function setAfterAtRuleBlock(string $content): self { - $this->sAfterAtRuleBlock = $content; + $this->contentAfterAtRuleBlock = $content; return $this; } diff --git a/tests/Unit/OutputFormatTest.php b/tests/Unit/OutputFormatTest.php index 226dd009..cc401e52 100644 --- a/tests/Unit/OutputFormatTest.php +++ b/tests/Unit/OutputFormatTest.php @@ -309,9 +309,9 @@ public function setSpaceBetweenBlocksProvidesFluentInterface(): void /** * @test */ - public function getBeforeAtRuleBlockInitiallyReturnsEmptyString(): void + public function getContentBeforeAtRuleBlockInitiallyReturnsEmptyString(): void { - self::assertSame('', $this->subject->getBeforeAtRuleBlock()); + self::assertSame('', $this->subject->getContentBeforeAtRuleBlock()); } /** @@ -322,7 +322,7 @@ public function setBeforeAtRuleBlockSetsBeforeAtRuleBlock(): void $value = ' '; $this->subject->setBeforeAtRuleBlock($value); - self::assertSame($value, $this->subject->getBeforeAtRuleBlock()); + self::assertSame($value, $this->subject->getContentBeforeAtRuleBlock()); } /** @@ -336,9 +336,9 @@ public function setBeforeAtRuleBlockProvidesFluentInterface(): void /** * @test */ - public function getAfterAtRuleBlockInitiallyReturnsEmptyString(): void + public function getContentAfterAtRuleBlockInitiallyReturnsEmptyString(): void { - self::assertSame('', $this->subject->getAfterAtRuleBlock()); + self::assertSame('', $this->subject->getContentAfterAtRuleBlock()); } /** @@ -349,7 +349,7 @@ public function setAfterAtRuleBlockSetsAfterAtRuleBlock(): void $value = ' '; $this->subject->setAfterAtRuleBlock($value); - self::assertSame($value, $this->subject->getAfterAtRuleBlock()); + self::assertSame($value, $this->subject->getContentAfterAtRuleBlock()); } /** From b226bb49a7ae50cf6e5bad437a8903a0652e40a9 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 8 Mar 2025 17:47:45 +0100 Subject: [PATCH 334/555] [CLEANUP] Avoid Hungarian notation for `input` (#1116) Part of #756 --- src/Parsing/ParserState.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 5814f814..e1fe93a8 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -294,8 +294,8 @@ public function consume($value = 1): string public function consumeExpression($mExpression, $iMaxLength = null): string { $matches = null; - $sInput = $iMaxLength !== null ? $this->peek($iMaxLength) : $this->inputLeft(); - if (\preg_match($mExpression, $sInput, $matches, PREG_OFFSET_CAPTURE) === 1) { + $input = $iMaxLength !== null ? $this->peek($iMaxLength) : $this->inputLeft(); + if (\preg_match($mExpression, $input, $matches, PREG_OFFSET_CAPTURE) === 1) { return $this->consume($matches[0][0]); } throw new UnexpectedTokenException($mExpression, $this->peek(5), 'expression', $this->lineNumber); From c8501c8439dad44671b8ced19aebb358f607c66e Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 8 Mar 2025 17:48:42 +0100 Subject: [PATCH 335/555] [CLEANUP] Avoid Hungarian notation in `OutputFormat` (part 5) (#1117) Part of #756 --- src/OutputFormat.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index df7a4d85..9828273e 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -87,12 +87,12 @@ class OutputFormat * * @var string */ - private $sSpaceBeforeSelectorSeparator = ''; + private $spaceBeforeSelectorSeparator = ''; /** * @var string */ - private $sSpaceAfterSelectorSeparator = ' '; + private $spaceAfterSelectorSeparator = ' '; /** * This is what’s inserted before the separator in value lists, by default. @@ -421,7 +421,7 @@ public function setAfterAtRuleBlock(string $content): self */ public function getSpaceBeforeSelectorSeparator(): string { - return $this->sSpaceBeforeSelectorSeparator; + return $this->spaceBeforeSelectorSeparator; } /** @@ -429,7 +429,7 @@ public function getSpaceBeforeSelectorSeparator(): string */ public function setSpaceBeforeSelectorSeparator(string $whitespace): self { - $this->sSpaceBeforeSelectorSeparator = $whitespace; + $this->spaceBeforeSelectorSeparator = $whitespace; return $this; } @@ -439,7 +439,7 @@ public function setSpaceBeforeSelectorSeparator(string $whitespace): self */ public function getSpaceAfterSelectorSeparator(): string { - return $this->sSpaceAfterSelectorSeparator; + return $this->spaceAfterSelectorSeparator; } /** @@ -447,7 +447,7 @@ public function getSpaceAfterSelectorSeparator(): string */ public function setSpaceAfterSelectorSeparator(string $whitespace): self { - $this->sSpaceAfterSelectorSeparator = $whitespace; + $this->spaceAfterSelectorSeparator = $whitespace; return $this; } From 4435934a53fbf46958a18620a407adf95f67d297 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 8 Mar 2025 18:47:20 +0100 Subject: [PATCH 336/555] [CLEANUP] Avoid Hungarian notation in `OutputFormat` (part 6) (#1118) Part of #756 --- src/OutputFormat.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 9828273e..d63e8e3e 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -99,28 +99,28 @@ class OutputFormat * * @var string */ - private $sSpaceBeforeListArgumentSeparator = ''; + private $spaceBeforeListArgumentSeparator = ''; /** * Keys are separators (e.g. `,`). Values are the space sequence to insert, or an empty string. * * @var array */ - private $aSpaceBeforeListArgumentSeparators = []; + private $spaceBeforeListArgumentSeparators = []; /** * This is what’s inserted after the separator in value lists, by default. * * @var string */ - private $sSpaceAfterListArgumentSeparator = ''; + private $spaceAfterListArgumentSeparator = ''; /** * Keys are separators (e.g. `,`). Values are the space sequence to insert, or an empty string. * * @var array */ - private $aSpaceAfterListArgumentSeparators = []; + private $spaceAfterListArgumentSeparators = []; /** * @var string @@ -457,7 +457,7 @@ public function setSpaceAfterSelectorSeparator(string $whitespace): self */ public function getSpaceBeforeListArgumentSeparator(): string { - return $this->sSpaceBeforeListArgumentSeparator; + return $this->spaceBeforeListArgumentSeparator; } /** @@ -465,7 +465,7 @@ public function getSpaceBeforeListArgumentSeparator(): string */ public function setSpaceBeforeListArgumentSeparator(string $whitespace): self { - $this->sSpaceBeforeListArgumentSeparator = $whitespace; + $this->spaceBeforeListArgumentSeparator = $whitespace; return $this; } @@ -477,7 +477,7 @@ public function setSpaceBeforeListArgumentSeparator(string $whitespace): self */ public function getSpaceBeforeListArgumentSeparators(): array { - return $this->aSpaceBeforeListArgumentSeparators; + return $this->spaceBeforeListArgumentSeparators; } /** @@ -487,7 +487,7 @@ public function getSpaceBeforeListArgumentSeparators(): array */ public function setSpaceBeforeListArgumentSeparators(array $separatorSpaces): self { - $this->aSpaceBeforeListArgumentSeparators = $separatorSpaces; + $this->spaceBeforeListArgumentSeparators = $separatorSpaces; return $this; } @@ -497,7 +497,7 @@ public function setSpaceBeforeListArgumentSeparators(array $separatorSpaces): se */ public function getSpaceAfterListArgumentSeparator(): string { - return $this->sSpaceAfterListArgumentSeparator; + return $this->spaceAfterListArgumentSeparator; } /** @@ -505,7 +505,7 @@ public function getSpaceAfterListArgumentSeparator(): string */ public function setSpaceAfterListArgumentSeparator(string $whitespace): self { - $this->sSpaceAfterListArgumentSeparator = $whitespace; + $this->spaceAfterListArgumentSeparator = $whitespace; return $this; } @@ -517,7 +517,7 @@ public function setSpaceAfterListArgumentSeparator(string $whitespace): self */ public function getSpaceAfterListArgumentSeparators(): array { - return $this->aSpaceAfterListArgumentSeparators; + return $this->spaceAfterListArgumentSeparators; } /** @@ -527,7 +527,7 @@ public function getSpaceAfterListArgumentSeparators(): array */ public function setSpaceAfterListArgumentSeparators(array $separatorSpaces): self { - $this->aSpaceAfterListArgumentSeparators = $separatorSpaces; + $this->spaceAfterListArgumentSeparators = $separatorSpaces; return $this; } From 9998f1886b9a01b6cb3cc2e4759bc0cec4a6750e Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 8 Mar 2025 18:48:01 +0100 Subject: [PATCH 337/555] [CLEANUP] Avoid Hungarian notation for `expression` (#1119) Part of #756 --- src/Parsing/ParserState.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index e1fe93a8..8865235e 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -285,20 +285,20 @@ public function consume($value = 1): string } /** - * @param string $mExpression + * @param string $expression * @param int|null $iMaxLength * * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ - public function consumeExpression($mExpression, $iMaxLength = null): string + public function consumeExpression($expression, $iMaxLength = null): string { $matches = null; $input = $iMaxLength !== null ? $this->peek($iMaxLength) : $this->inputLeft(); - if (\preg_match($mExpression, $input, $matches, PREG_OFFSET_CAPTURE) === 1) { + if (\preg_match($expression, $input, $matches, PREG_OFFSET_CAPTURE) === 1) { return $this->consume($matches[0][0]); } - throw new UnexpectedTokenException($mExpression, $this->peek(5), 'expression', $this->lineNumber); + throw new UnexpectedTokenException($expression, $this->peek(5), 'expression', $this->lineNumber); } /** From 008d2fbf0d15c7cc205b1d1729bed15c37f6d558 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 8 Mar 2025 18:49:49 +0100 Subject: [PATCH 338/555] [CLEANUP] Avoid Hungarian notation for `peek` (#1120) Part of #756 --- src/Parsing/ParserState.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 8865235e..6e6d33f6 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -231,10 +231,10 @@ public function consumeWhiteSpace(): array */ public function comes($string, $caseInsensitive = false): bool { - $sPeek = $this->peek(\strlen($string)); - return ($sPeek == '') + $peek = $this->peek(\strlen($string)); + return ($peek == '') ? false - : $this->streql($sPeek, $string, $caseInsensitive); + : $this->streql($peek, $string, $caseInsensitive); } /** From 5907d2500207448d3722b9dcf96e8626ba1bfa32 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 8 Mar 2025 19:42:34 +0100 Subject: [PATCH 339/555] [CLEANUP] Avoid Hungarian notation in `OutputFormat` (part 7) (#1121) Also rename the getters to match the new property names. Part of #756 --- src/OutputFormat.php | 30 +++++++++++++++--------------- src/RuleSet/DeclarationBlock.php | 6 +++--- tests/Unit/OutputFormatTest.php | 18 +++++++++--------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index d63e8e3e..e866620c 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -125,24 +125,24 @@ class OutputFormat /** * @var string */ - private $sSpaceBeforeOpeningBrace = ' '; + private $spaceBeforeOpeningBrace = ' '; /** * Content injected in and around declaration blocks. * * @var string */ - private $sBeforeDeclarationBlock = ''; + private $contentBeforeDeclarationBlock = ''; /** * @var string */ - private $sAfterDeclarationBlockSelectors = ''; + private $contentAfterDeclarationBlockSelectors = ''; /** * @var string */ - private $sAfterDeclarationBlock = ''; + private $contentAfterDeclarationBlock = ''; /** * Indentation character(s) per level. Only applicable if newlines are used in any of the spacing settings. @@ -537,7 +537,7 @@ public function setSpaceAfterListArgumentSeparators(array $separatorSpaces): sel */ public function getSpaceBeforeOpeningBrace(): string { - return $this->sSpaceBeforeOpeningBrace; + return $this->spaceBeforeOpeningBrace; } /** @@ -545,7 +545,7 @@ public function getSpaceBeforeOpeningBrace(): string */ public function setSpaceBeforeOpeningBrace(string $whitespace): self { - $this->sSpaceBeforeOpeningBrace = $whitespace; + $this->spaceBeforeOpeningBrace = $whitespace; return $this; } @@ -553,9 +553,9 @@ public function setSpaceBeforeOpeningBrace(string $whitespace): self /** * @internal */ - public function getBeforeDeclarationBlock(): string + public function getContentBeforeDeclarationBlock(): string { - return $this->sBeforeDeclarationBlock; + return $this->contentBeforeDeclarationBlock; } /** @@ -563,7 +563,7 @@ public function getBeforeDeclarationBlock(): string */ public function setBeforeDeclarationBlock(string $content): self { - $this->sBeforeDeclarationBlock = $content; + $this->contentBeforeDeclarationBlock = $content; return $this; } @@ -571,9 +571,9 @@ public function setBeforeDeclarationBlock(string $content): self /** * @internal */ - public function getAfterDeclarationBlockSelectors(): string + public function getContentAfterDeclarationBlockSelectors(): string { - return $this->sAfterDeclarationBlockSelectors; + return $this->contentAfterDeclarationBlockSelectors; } /** @@ -581,7 +581,7 @@ public function getAfterDeclarationBlockSelectors(): string */ public function setAfterDeclarationBlockSelectors(string $content): self { - $this->sAfterDeclarationBlockSelectors = $content; + $this->contentAfterDeclarationBlockSelectors = $content; return $this; } @@ -589,9 +589,9 @@ public function setAfterDeclarationBlockSelectors(string $content): self /** * @internal */ - public function getAfterDeclarationBlock(): string + public function getContentAfterDeclarationBlock(): string { - return $this->sAfterDeclarationBlock; + return $this->contentAfterDeclarationBlock; } /** @@ -599,7 +599,7 @@ public function getAfterDeclarationBlock(): string */ public function setAfterDeclarationBlock(string $content): self { - $this->sAfterDeclarationBlock = $content; + $this->contentAfterDeclarationBlock = $content; return $this; } diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 8701f0fa..4bd45d68 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -160,16 +160,16 @@ public function render(OutputFormat $outputFormat): string // If all the selectors have been removed, this declaration block becomes invalid throw new OutputException('Attempt to print declaration block with missing selector', $this->lineNumber); } - $result .= $outputFormat->getBeforeDeclarationBlock(); + $result .= $outputFormat->getContentBeforeDeclarationBlock(); $result .= $outputFormat->implode( $outputFormat->spaceBeforeSelectorSeparator() . ',' . $outputFormat->spaceAfterSelectorSeparator(), $this->selectors ); - $result .= $outputFormat->getAfterDeclarationBlockSelectors(); + $result .= $outputFormat->getContentAfterDeclarationBlockSelectors(); $result .= $outputFormat->spaceBeforeOpeningBrace() . '{'; $result .= $this->renderRules($outputFormat); $result .= '}'; - $result .= $outputFormat->getAfterDeclarationBlock(); + $result .= $outputFormat->getContentAfterDeclarationBlock(); return $result; } } diff --git a/tests/Unit/OutputFormatTest.php b/tests/Unit/OutputFormatTest.php index cc401e52..c8784eb0 100644 --- a/tests/Unit/OutputFormatTest.php +++ b/tests/Unit/OutputFormatTest.php @@ -552,9 +552,9 @@ public function setSpaceBeforeOpeningBraceProvidesFluentInterface(): void /** * @test */ - public function getBeforeDeclarationBlockInitiallyReturnsEmptyString(): void + public function getContentBeforeDeclarationBlockInitiallyReturnsEmptyString(): void { - self::assertSame('', $this->subject->getBeforeDeclarationBlock()); + self::assertSame('', $this->subject->getContentBeforeDeclarationBlock()); } /** @@ -565,7 +565,7 @@ public function setBeforeDeclarationBlockSetsBeforeDeclarationBlock(): void $value = ' '; $this->subject->setBeforeDeclarationBlock($value); - self::assertSame($value, $this->subject->getBeforeDeclarationBlock()); + self::assertSame($value, $this->subject->getContentBeforeDeclarationBlock()); } /** @@ -579,9 +579,9 @@ public function setBeforeDeclarationBlockProvidesFluentInterface(): void /** * @test */ - public function getAfterDeclarationBlockSelectorsInitiallyReturnsEmptyString(): void + public function getContentAfterDeclarationBlockSelectorsInitiallyReturnsEmptyString(): void { - self::assertSame('', $this->subject->getAfterDeclarationBlockSelectors()); + self::assertSame('', $this->subject->getContentAfterDeclarationBlockSelectors()); } /** @@ -592,7 +592,7 @@ public function setAfterDeclarationBlockSelectorsSetsAfterDeclarationBlockSelect $value = ' '; $this->subject->setAfterDeclarationBlockSelectors($value); - self::assertSame($value, $this->subject->getAfterDeclarationBlockSelectors()); + self::assertSame($value, $this->subject->getContentAfterDeclarationBlockSelectors()); } /** @@ -606,9 +606,9 @@ public function setAfterDeclarationBlockSelectorsProvidesFluentInterface(): void /** * @test */ - public function getAfterDeclarationBlockInitiallyReturnsEmptyString(): void + public function getContentAfterDeclarationBlockInitiallyReturnsEmptyString(): void { - self::assertSame('', $this->subject->getAfterDeclarationBlock()); + self::assertSame('', $this->subject->getContentAfterDeclarationBlock()); } /** @@ -619,7 +619,7 @@ public function setAfterDeclarationBlockSetsAfterDeclarationBlock(): void $value = ' '; $this->subject->setAfterDeclarationBlock($value); - self::assertSame($value, $this->subject->getAfterDeclarationBlock()); + self::assertSame($value, $this->subject->getContentAfterDeclarationBlock()); } /** From 337f861e4afdbbb54833e4784391a7fd4aed9818 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 8 Mar 2025 19:43:30 +0100 Subject: [PATCH 340/555] [CLEANUP] Avoid Hungarian notation for `maximumLength` (#1122) Part of #756 --- src/Parsing/ParserState.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 6e6d33f6..52ce025c 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -286,15 +286,15 @@ public function consume($value = 1): string /** * @param string $expression - * @param int|null $iMaxLength + * @param int|null $maximumLength * * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ - public function consumeExpression($expression, $iMaxLength = null): string + public function consumeExpression($expression, $maximumLength = null): string { $matches = null; - $input = $iMaxLength !== null ? $this->peek($iMaxLength) : $this->inputLeft(); + $input = $maximumLength !== null ? $this->peek($maximumLength) : $this->inputLeft(); if (\preg_match($expression, $input, $matches, PREG_OFFSET_CAPTURE) === 1) { return $this->consume($matches[0][0]); } From 9eb6c43f3d4aa59e341ce687762f1bd72d45e43c Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 8 Mar 2025 19:44:37 +0100 Subject: [PATCH 341/555] [CLEANUP] Avoid Hungarian notation for `comment` (#1123) Part of #756 --- src/Parsing/ParserState.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 52ce025c..d0969f35 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -306,13 +306,13 @@ public function consumeExpression($expression, $maximumLength = null): string */ public function consumeComment() { - $mComment = false; + $comment = false; if ($this->comes('/*')) { $lineNumber = $this->lineNumber; $this->consume(1); - $mComment = ''; + $comment = ''; while (($char = $this->consume(1)) !== '') { - $mComment .= $char; + $comment .= $char; if ($this->comes('*/')) { $this->consume(2); break; @@ -320,12 +320,12 @@ public function consumeComment() } } - if ($mComment !== false) { + if ($comment !== false) { // We skip the * which was included in the comment. - return new Comment(\substr($mComment, 1), $lineNumber); + return new Comment(\substr($comment, 1), $lineNumber); } - return $mComment; + return $comment; } public function isEnd(): bool From 262ac24c42b6920d255bdbee79aff945175c6906 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 8 Mar 2025 20:07:36 +0100 Subject: [PATCH 342/555] [CLEANUP] Avoid Hungarian notation for `stopCharacters` (#1124) Part of #756 --- src/Parsing/ParserState.php | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index d0969f35..37b66376 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -334,7 +334,7 @@ public function isEnd(): bool } /** - * @param array|string $aEnd + * @param array|string $stopCharacters * @param string $bIncludeEnd * @param string $consumeEnd * @param array $comments @@ -342,15 +342,19 @@ public function isEnd(): bool * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ - public function consumeUntil($aEnd, $bIncludeEnd = false, $consumeEnd = false, array &$comments = []): string - { - $aEnd = \is_array($aEnd) ? $aEnd : [$aEnd]; + public function consumeUntil( + $stopCharacters, + $bIncludeEnd = false, + $consumeEnd = false, + array &$comments = [] + ): string { + $stopCharacters = \is_array($stopCharacters) ? $stopCharacters : [$stopCharacters]; $out = ''; $start = $this->currentPosition; while (!$this->isEnd()) { $char = $this->consume(1); - if (\in_array($char, $aEnd, true)) { + if (\in_array($char, $stopCharacters, true)) { if ($bIncludeEnd) { $out .= $char; } elseif (!$consumeEnd) { @@ -364,13 +368,13 @@ public function consumeUntil($aEnd, $bIncludeEnd = false, $consumeEnd = false, a } } - if (\in_array(self::EOF, $aEnd, true)) { + if (\in_array(self::EOF, $stopCharacters, true)) { return $out; } $this->currentPosition = $start; throw new UnexpectedEOFException( - 'One of ("' . \implode('","', $aEnd) . '")', + 'One of ("' . \implode('","', $stopCharacters) . '")', $this->peek(5), 'search', $this->lineNumber From 1de81af1879baa623c455087c2175e90136182ad Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 8 Mar 2025 20:09:03 +0100 Subject: [PATCH 343/555] [CLEANUP] Avoid Hungarian notation in `OutputFormat` (part 8) (#1125) Also rename the getters to match the new property names. Part of #756 --- src/OutputFormat.php | 16 ++++++++-------- src/OutputFormatter.php | 2 +- tests/Unit/OutputFormatTest.php | 8 ++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index e866620c..17faf7b2 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -149,14 +149,14 @@ class OutputFormat * * @var string */ - private $sIndentation = "\t"; + private $indentation = "\t"; /** * Output exceptions. * * @var bool */ - private $bIgnoreExceptions = false; + private $shouldIgnoreExceptions = false; /** * Render comments for lists and RuleSets @@ -609,7 +609,7 @@ public function setAfterDeclarationBlock(string $content): self */ public function getIndentation(): string { - return $this->sIndentation; + return $this->indentation; } /** @@ -617,7 +617,7 @@ public function getIndentation(): string */ public function setIndentation(string $indentation): self { - $this->sIndentation = $indentation; + $this->indentation = $indentation; return $this; } @@ -625,9 +625,9 @@ public function setIndentation(string $indentation): self /** * @internal */ - public function getIgnoreExceptions(): bool + public function shouldIgnoreExceptions(): bool { - return $this->bIgnoreExceptions; + return $this->shouldIgnoreExceptions; } /** @@ -635,7 +635,7 @@ public function getIgnoreExceptions(): bool */ public function setIgnoreExceptions(bool $ignoreExceptions): self { - $this->bIgnoreExceptions = $ignoreExceptions; + $this->shouldIgnoreExceptions = $ignoreExceptions; return $this; } @@ -697,7 +697,7 @@ public function nextLevel(): self public function beLenient(): void { - $this->bIgnoreExceptions = true; + $this->shouldIgnoreExceptions = true; } /** diff --git a/src/OutputFormatter.php b/src/OutputFormatter.php index f7fb17d6..b5799d49 100644 --- a/src/OutputFormatter.php +++ b/src/OutputFormatter.php @@ -148,7 +148,7 @@ public function spaceBeforeOpeningBrace(): string */ public function safely(callable $callable): ?string { - if ($this->outputFormat->getIgnoreExceptions()) { + if ($this->outputFormat->shouldIgnoreExceptions()) { // If output exceptions are ignored, run the code with exception guards try { return $callable(); diff --git a/tests/Unit/OutputFormatTest.php b/tests/Unit/OutputFormatTest.php index c8784eb0..fd1d3ead 100644 --- a/tests/Unit/OutputFormatTest.php +++ b/tests/Unit/OutputFormatTest.php @@ -660,9 +660,9 @@ public function setIndentationProvidesFluentInterface(): void /** * @test */ - public function getIgnoreExceptionsInitiallyReturnsFalse(): void + public function shouldIgnoreExceptionsInitiallyReturnsFalse(): void { - self::assertFalse($this->subject->getIgnoreExceptions()); + self::assertFalse($this->subject->shouldIgnoreExceptions()); } /** @@ -674,7 +674,7 @@ public function setIgnoreExceptionsSetsIgnoreExceptions(bool $value): void { $this->subject->setIgnoreExceptions($value); - self::assertSame($value, $this->subject->getIgnoreExceptions()); + self::assertSame($value, $this->subject->shouldIgnoreExceptions()); } /** @@ -866,7 +866,7 @@ public function beLenientSetsIgnoreExceptionsToTrue(): void $this->subject->beLenient(); - self::assertTrue($this->subject->getIgnoreExceptions()); + self::assertTrue($this->subject->shouldIgnoreExceptions()); } /** From cd5128179f6accce51e4ed210bc685fd0bab6862 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 9 Mar 2025 14:58:30 +0100 Subject: [PATCH 344/555] [CLEANUP] Avoid Hungarian notation in `OutputFormat` (part 9) (#1126) Also rename the getters to match the new property names. Part of #756 --- src/OutputFormat.php | 24 ++++++++++++------------ src/OutputFormatter.php | 2 +- tests/Unit/OutputFormatTest.php | 10 +++++----- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 17faf7b2..9b5b3395 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -163,7 +163,7 @@ class OutputFormat * * @var bool */ - private $bRenderComments = false; + private $shouldRenderComments = false; /** * @var OutputFormatter|null @@ -178,25 +178,25 @@ class OutputFormat /** * @var int */ - private $iIndentationLevel = 0; + private $indendationLevel = 0; public function __construct() {} /** - * @param non-empty-string $sMethodName + * @param non-empty-string $methodName * @param array $arguments * * @return mixed * * @throws \Exception */ - public function __call(string $sMethodName, array $arguments) + public function __call(string $methodName, array $arguments) { - if (\method_exists(OutputFormatter::class, $sMethodName)) { + if (\method_exists(OutputFormatter::class, $methodName)) { // @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], $arguments); + return \call_user_func_array([$this->getFormatter(), $methodName], $arguments); } else { - throw new \Exception('Unknown OutputFormat method called: ' . $sMethodName); + throw new \Exception('Unknown OutputFormat method called: ' . $methodName); } } @@ -643,9 +643,9 @@ public function setIgnoreExceptions(bool $ignoreExceptions): self /** * @internal */ - public function getRenderComments(): bool + public function shouldRenderComments(): bool { - return $this->bRenderComments; + return $this->shouldRenderComments; } /** @@ -653,7 +653,7 @@ public function getRenderComments(): bool */ public function setRenderComments(bool $renderComments): self { - $this->bRenderComments = $renderComments; + $this->shouldRenderComments = $renderComments; return $this; } @@ -663,7 +663,7 @@ public function setRenderComments(bool $renderComments): self */ public function getIndentationLevel(): int { - return $this->iIndentationLevel; + return $this->indendationLevel; } /** @@ -689,7 +689,7 @@ public function nextLevel(): self { if ($this->nextLevelFormat === null) { $this->nextLevelFormat = clone $this; - $this->nextLevelFormat->iIndentationLevel++; + $this->nextLevelFormat->indendationLevel++; $this->nextLevelFormat->outputFormatter = null; } return $this->nextLevelFormat; diff --git a/src/OutputFormatter.php b/src/OutputFormatter.php index b5799d49..09918c38 100644 --- a/src/OutputFormatter.php +++ b/src/OutputFormatter.php @@ -208,7 +208,7 @@ public function removeLastSemicolon(string $string): string public function comments(Commentable $commentable): string { - if (!$this->outputFormat->getRenderComments()) { + if (!$this->outputFormat->shouldRenderComments()) { return ''; } diff --git a/tests/Unit/OutputFormatTest.php b/tests/Unit/OutputFormatTest.php index fd1d3ead..781ad2f4 100644 --- a/tests/Unit/OutputFormatTest.php +++ b/tests/Unit/OutputFormatTest.php @@ -688,9 +688,9 @@ public function setIgnoreExceptionsProvidesFluentInterface(): void /** * @test */ - public function getRenderCommentsInitiallyReturnsFalse(): void + public function shouldRenderCommentsInitiallyReturnsFalse(): void { - self::assertFalse($this->subject->getRenderComments()); + self::assertFalse($this->subject->shouldRenderComments()); } /** @@ -702,7 +702,7 @@ public function setRenderCommentsSetsRenderComments(bool $value): void { $this->subject->setRenderComments($value); - self::assertSame($value, $this->subject->getRenderComments()); + self::assertSame($value, $this->subject->shouldRenderComments()); } /** @@ -1041,7 +1041,7 @@ public function createCompactReturnsInstanceWithRenderCommentsDisabled(): void { $newInstance = OutputFormat::createCompact(); - self::assertFalse($newInstance->getRenderComments()); + self::assertFalse($newInstance->shouldRenderComments()); } /** @@ -1170,6 +1170,6 @@ public function createPrettyReturnsInstanceWithRenderCommentsEnabled(): void { $newInstance = OutputFormat::createPretty(); - self::assertTrue($newInstance->getRenderComments()); + self::assertTrue($newInstance->shouldRenderComments()); } } From b43388aa116e66fe95a1a1c2d2fd9fd2ba1587f0 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 9 Mar 2025 15:10:54 +0100 Subject: [PATCH 345/555] [CLEANUP] Improve some variable names in `ParserState` (#1129) --- src/Parsing/ParserState.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 37b66376..ca18bd4d 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -349,27 +349,27 @@ public function consumeUntil( array &$comments = [] ): string { $stopCharacters = \is_array($stopCharacters) ? $stopCharacters : [$stopCharacters]; - $out = ''; + $consumedCharacters = ''; $start = $this->currentPosition; while (!$this->isEnd()) { - $char = $this->consume(1); - if (\in_array($char, $stopCharacters, true)) { + $character = $this->consume(1); + if (\in_array($character, $stopCharacters, true)) { if ($bIncludeEnd) { - $out .= $char; + $consumedCharacters .= $character; } elseif (!$consumeEnd) { - $this->currentPosition -= $this->strlen($char); + $this->currentPosition -= $this->strlen($character); } - return $out; + return $consumedCharacters; } - $out .= $char; + $consumedCharacters .= $character; if ($comment = $this->consumeComment()) { $comments[] = $comment; } } if (\in_array(self::EOF, $stopCharacters, true)) { - return $out; + return $consumedCharacters; } $this->currentPosition = $start; From a8709e49accd23e2370e3cd1bf405c36518952e9 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 9 Mar 2025 15:15:30 +0100 Subject: [PATCH 346/555] [CLEANUP] Fix some type annotations in `ParserState` (#1130) Also make the parameter names non-Hungarian. Part of #756 Co-authored-by: JakeQZ --- config/phpstan-baseline.neon | 24 ------------------------ src/Parsing/ParserState.php | 8 ++++---- 2 files changed, 4 insertions(+), 28 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index df371d85..3949389b 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -102,36 +102,12 @@ parameters: count: 2 path: ../src/CSSList/KeyFrame.php - - - message: '#^Default value of the parameter \#2 \$bIncludeEnd \(false\) of method Sabberworm\\CSS\\Parsing\\ParserState\:\:consumeUntil\(\) is incompatible with type string\.$#' - identifier: parameter.defaultValue - count: 1 - path: ../src/Parsing/ParserState.php - - - - message: '#^Default value of the parameter \#3 \$consumeEnd \(false\) of method Sabberworm\\CSS\\Parsing\\ParserState\:\:consumeUntil\(\) is incompatible with type string\.$#' - identifier: parameter.defaultValue - count: 1 - path: ../src/Parsing/ParserState.php - - message: '#^Loose comparison via "\=\=" is not allowed\.$#' identifier: equal.notAllowed count: 1 path: ../src/Parsing/ParserState.php - - - message: '#^Only booleans are allowed in a negated boolean, string given\.$#' - identifier: booleanNot.exprNotBoolean - count: 1 - path: ../src/Parsing/ParserState.php - - - - message: '#^Only booleans are allowed in an if condition, string given\.$#' - identifier: if.condNotBoolean - count: 1 - path: ../src/Parsing/ParserState.php - - message: '#^PHPDoc tag @return with type array\\|void is not subtype of native type array\.$#' identifier: return.phpDocType diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index ca18bd4d..e4be94ce 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -335,8 +335,8 @@ public function isEnd(): bool /** * @param array|string $stopCharacters - * @param string $bIncludeEnd - * @param string $consumeEnd + * @param bool $includeEnd + * @param bool $consumeEnd * @param array $comments * * @throws UnexpectedEOFException @@ -344,7 +344,7 @@ public function isEnd(): bool */ public function consumeUntil( $stopCharacters, - $bIncludeEnd = false, + $includeEnd = false, $consumeEnd = false, array &$comments = [] ): string { @@ -355,7 +355,7 @@ public function consumeUntil( while (!$this->isEnd()) { $character = $this->consume(1); if (\in_array($character, $stopCharacters, true)) { - if ($bIncludeEnd) { + if ($includeEnd) { $consumedCharacters .= $character; } elseif (!$consumeEnd) { $this->currentPosition -= $this->strlen($character); From 6801d263ed54fb55c26c94e41cab942c5479457e Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 10 Mar 2025 00:50:35 +0100 Subject: [PATCH 347/555] [BUGFIX] Fix a typo in `indentationLevel` (#1132) --- src/OutputFormat.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 9b5b3395..a6643dcf 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -178,7 +178,7 @@ class OutputFormat /** * @var int */ - private $indendationLevel = 0; + private $indentationLevel = 0; public function __construct() {} @@ -663,7 +663,7 @@ public function setRenderComments(bool $renderComments): self */ public function getIndentationLevel(): int { - return $this->indendationLevel; + return $this->indentationLevel; } /** @@ -689,7 +689,7 @@ public function nextLevel(): self { if ($this->nextLevelFormat === null) { $this->nextLevelFormat = clone $this; - $this->nextLevelFormat->indendationLevel++; + $this->nextLevelFormat->indentationLevel++; $this->nextLevelFormat->outputFormatter = null; } return $this->nextLevelFormat; From 9d2fd8e40ae20fdef1521212bb3804a79e1573ec Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 10 Mar 2025 14:37:25 +0100 Subject: [PATCH 348/555] [TASK] Mark `OutputFormat` as not extendable (#1131) Also mark the constructor as `@internal`. Co-authored-by: JakeQZ --- CHANGELOG.md | 2 ++ src/OutputFormat.php | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 495f8d3c..a78f18fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Please also have a look at our ### Changed +- Mark the `OutputFormat` constructor as `@internal` (#1131) - Mark `OutputFormatter` as `@internal` (#896) - Make `Selector` a `Renderable` (#1017) - Mark `Selector::isValid()` as `@internal` (#1037) @@ -35,6 +36,7 @@ Please also have a look at our ### Deprecated +- Deprecate extending `OutputFormat` (#1131) - Deprecate magic method forwarding from `OutputFormat` to `OutputFormatter` (#894) - Deprecate greedy calculation of selector specificity (#1018) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index a6643dcf..dce28abd 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -4,6 +4,9 @@ namespace Sabberworm\CSS; +/** + * Extending this class is deprecated in version 8.8.0; it will be made `final` in version 9.0.0. + */ class OutputFormat { /** @@ -180,6 +183,9 @@ class OutputFormat */ private $indentationLevel = 0; + /** + * @internal since V8.8.0. Use the factory methods `create()`, `createCompact()`, or `createPretty()` instead. + */ public function __construct() {} /** From ef732a59a87a6788d96e7c6141bc35baf6eb089b Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 10 Mar 2025 17:52:16 +0100 Subject: [PATCH 349/555] [TASK] Make `OutputFormat` `final` (#1128) This class is not intended to be extended, and it's intended to be created using the factory methods anyway. (Also, we most probably won't need to have mocks of this class in the tests as it's mostly a data object.) --- CHANGELOG.md | 1 + src/OutputFormat.php | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a78f18fe..0643d726 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Please also have a look at our ### Changed +- Make `OutputFormat` `final` (#1128) - Mark the `OutputFormat` constructor as `@internal` (#1131) - Mark `OutputFormatter` as `@internal` (#896) - Make `Selector` a `Renderable` (#1017) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index dce28abd..5f9abcb4 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -4,10 +4,7 @@ namespace Sabberworm\CSS; -/** - * Extending this class is deprecated in version 8.8.0; it will be made `final` in version 9.0.0. - */ -class OutputFormat +final class OutputFormat { /** * Value format: `"` means double-quote, `'` means single-quote From 6607f94341bdd8f04d12520594f4bbe1e2ee6c33 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 10 Mar 2025 17:53:29 +0100 Subject: [PATCH 350/555] [CLEANUP] Drop empty `OutputFormat` constructor (#1127) --- src/OutputFormat.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 5f9abcb4..225dab9c 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -180,11 +180,6 @@ final class OutputFormat */ private $indentationLevel = 0; - /** - * @internal since V8.8.0. Use the factory methods `create()`, `createCompact()`, or `createPretty()` instead. - */ - public function __construct() {} - /** * @param non-empty-string $methodName * @param array $arguments From 54939828af36553855933476a3f92a1dda830b40 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 11 Mar 2025 16:03:31 +0100 Subject: [PATCH 351/555] [TASK] Use native type declarations for `$lineNumber` (#1134) Part of #811 Co-authored-by: JakeQZ --- CHANGELOG.md | 2 +- src/CSSList/CSSList.php | 2 +- src/Parser.php | 4 ++-- src/Parsing/ParserState.php | 8 ++++---- src/Property/CSSNamespace.php | 4 ++-- src/Property/Charset.php | 2 +- src/Property/Import.php | 2 +- src/Rule/Rule.php | 8 ++++---- src/RuleSet/AtRuleSet.php | 2 +- src/RuleSet/RuleSet.php | 2 +- src/Value/CSSFunction.php | 2 +- src/Value/CSSString.php | 2 +- src/Value/CalcRuleValueList.php | 2 +- src/Value/Color.php | 2 +- src/Value/LineName.php | 2 +- src/Value/RuleValueList.php | 2 +- src/Value/Size.php | 2 +- src/Value/URL.php | 2 +- src/Value/Value.php | 2 +- src/Value/ValueList.php | 2 +- 20 files changed, 28 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0643d726..711be85f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,7 +32,7 @@ Please also have a look at our - Make all non-private properties `@internal` (#886) - Use more native type declarations and strict mode (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933, #958, - #964, #967, #1000, #1044) + #964, #967, #1000, #1044, #1134) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index cfc5d7bb..3c538593 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -57,7 +57,7 @@ abstract class CSSList implements Renderable, Commentable /** * @param int<0, max> $lineNumber */ - public function __construct($lineNumber = 0) + public function __construct(int $lineNumber = 0) { $this->lineNumber = $lineNumber; } diff --git a/src/Parser.php b/src/Parser.php index eae809a5..d3290b1e 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -20,9 +20,9 @@ class Parser /** * @param string $text the complete CSS as text (i.e., usually the contents of a CSS file) - * @param int<0, max> $lineNumber the line number (starting from 1, not from 0) + * @param int<1, max> $lineNumber the line number (starting from 1, not from 0) */ - public function __construct($text, ?Settings $parserSettings = null, $lineNumber = 1) + public function __construct($text, ?Settings $parserSettings = null, int $lineNumber = 1) { if ($parserSettings === null) { $parserSettings = Settings::create(); diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index e4be94ce..76d66b01 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -45,15 +45,15 @@ class ParserState private $charset; /** - * @var int + * @var int<1, max> $lineNumber */ private $lineNumber; /** * @param string $text the complete CSS as text (i.e., usually the contents of a CSS file) - * @param int<0, max> $lineNumber + * @param int<1, max> $lineNumber */ - public function __construct($text, Settings $parserSettings, $lineNumber = 1) + public function __construct($text, Settings $parserSettings, int $lineNumber = 1) { $this->parserSettings = $parserSettings; $this->text = $text; @@ -73,7 +73,7 @@ public function setCharset(string $charset): void } /** - * @return int + * @return int<1, max> */ public function currentLine() { diff --git a/src/Property/CSSNamespace.php b/src/Property/CSSNamespace.php index 7a4f64a9..b7604456 100644 --- a/src/Property/CSSNamespace.php +++ b/src/Property/CSSNamespace.php @@ -23,7 +23,7 @@ class CSSNamespace implements AtRule private $prefix; /** - * @var int + * @var int<0, max> $lineNumber */ private $lineNumber; @@ -39,7 +39,7 @@ class CSSNamespace implements AtRule * @param string|null $prefix * @param int<0, max> $lineNumber */ - public function __construct($url, $prefix = null, $lineNumber = 0) + public function __construct($url, $prefix = null, int $lineNumber = 0) { $this->url = $url; $this->prefix = $prefix; diff --git a/src/Property/Charset.php b/src/Property/Charset.php index dea13e16..59675665 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -40,7 +40,7 @@ class Charset implements AtRule /** * @param int<0, max> $lineNumber */ - public function __construct(CSSString $charset, $lineNumber = 0) + public function __construct(CSSString $charset, int $lineNumber = 0) { $this->charset = $charset; $this->lineNumber = $lineNumber; diff --git a/src/Property/Import.php b/src/Property/Import.php index 520e0120..77ae17cf 100644 --- a/src/Property/Import.php +++ b/src/Property/Import.php @@ -41,7 +41,7 @@ class Import implements AtRule * @param string $mediaQuery * @param int<0, max> $lineNumber */ - public function __construct(URL $location, $mediaQuery, $lineNumber = 0) + public function __construct(URL $location, $mediaQuery, int $lineNumber = 0) { $this->location = $location; $this->mediaQuery = $mediaQuery; diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index 792371a1..ccf070e9 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -37,7 +37,7 @@ class Rule implements Renderable, Commentable private $isImportant = false; /** - * @var int + * @var int<0, max> $lineNumber */ protected $lineNumber; @@ -60,7 +60,7 @@ class Rule implements Renderable, Commentable * @param int<0, max> $lineNumber * @param int $columnNumber */ - public function __construct($rule, $lineNumber = 0, $columnNumber = 0) + public function __construct($rule, int $lineNumber = 0, $columnNumber = 0) { $this->rule = $rule; $this->lineNumber = $lineNumber; @@ -143,10 +143,10 @@ public function getColNo() } /** - * @param int $lineNumber + * @param int<0, max> $lineNumber * @param int $columnNumber */ - public function setPosition($lineNumber, $columnNumber): void + public function setPosition(int $lineNumber, $columnNumber): void { $this->columnNumber = $columnNumber; $this->lineNumber = $lineNumber; diff --git a/src/RuleSet/AtRuleSet.php b/src/RuleSet/AtRuleSet.php index 9f7a1705..96fed182 100644 --- a/src/RuleSet/AtRuleSet.php +++ b/src/RuleSet/AtRuleSet.php @@ -30,7 +30,7 @@ class AtRuleSet extends RuleSet implements AtRule * @param string $arguments * @param int<0, max> $lineNumber */ - public function __construct($type, $arguments = '', $lineNumber = 0) + public function __construct($type, $arguments = '', int $lineNumber = 0) { parent::__construct($lineNumber); $this->type = $type; diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 081492aa..5da5e401 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -49,7 +49,7 @@ abstract class RuleSet implements Renderable, Commentable /** * @param int<0, max> $lineNumber */ - public function __construct($lineNumber = 0) + public function __construct(int $lineNumber = 0) { $this->lineNumber = $lineNumber; } diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index 985a7a24..3c10900e 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -29,7 +29,7 @@ class CSSFunction extends ValueList * @param string $separator * @param int<0, max> $lineNumber */ - public function __construct($name, $arguments, $separator = ',', $lineNumber = 0) + public function __construct($name, $arguments, $separator = ',', int $lineNumber = 0) { if ($arguments instanceof RuleValueList) { $separator = $arguments->getListSeparator(); diff --git a/src/Value/CSSString.php b/src/Value/CSSString.php index 117bd90f..8b3064bd 100644 --- a/src/Value/CSSString.php +++ b/src/Value/CSSString.php @@ -26,7 +26,7 @@ class CSSString extends PrimitiveValue * @param string $string * @param int<0, max> $lineNumber */ - public function __construct($string, $lineNumber = 0) + public function __construct($string, int $lineNumber = 0) { $this->string = $string; parent::__construct($lineNumber); diff --git a/src/Value/CalcRuleValueList.php b/src/Value/CalcRuleValueList.php index 172b05e3..d418f001 100644 --- a/src/Value/CalcRuleValueList.php +++ b/src/Value/CalcRuleValueList.php @@ -11,7 +11,7 @@ class CalcRuleValueList extends RuleValueList /** * @param int<0, max> $lineNumber */ - public function __construct($lineNumber = 0) + public function __construct(int $lineNumber = 0) { parent::__construct(',', $lineNumber); } diff --git a/src/Value/Color.php b/src/Value/Color.php index d12af070..8f1e2a74 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -19,7 +19,7 @@ class Color extends CSSFunction * @param array $colorValues * @param int<0, max> $lineNumber */ - public function __construct(array $colorValues, $lineNumber = 0) + public function __construct(array $colorValues, int $lineNumber = 0) { parent::__construct(\implode('', \array_keys($colorValues)), $colorValues, ',', $lineNumber); } diff --git a/src/Value/LineName.php b/src/Value/LineName.php index 66b76378..aad8b6bd 100644 --- a/src/Value/LineName.php +++ b/src/Value/LineName.php @@ -15,7 +15,7 @@ class LineName extends ValueList * @param array $components * @param int<0, max> $lineNumber */ - public function __construct(array $components = [], $lineNumber = 0) + public function __construct(array $components = [], int $lineNumber = 0) { parent::__construct($components, ' ', $lineNumber); } diff --git a/src/Value/RuleValueList.php b/src/Value/RuleValueList.php index a9dab2ea..9e505979 100644 --- a/src/Value/RuleValueList.php +++ b/src/Value/RuleValueList.php @@ -15,7 +15,7 @@ class RuleValueList extends ValueList * @param string $separator * @param int<0, max> $lineNumber */ - public function __construct($separator = ',', $lineNumber = 0) + public function __construct($separator = ',', int $lineNumber = 0) { parent::__construct([], $separator, $lineNumber); } diff --git a/src/Value/Size.php b/src/Value/Size.php index 53723bed..9939160b 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -73,7 +73,7 @@ class Size extends PrimitiveValue * @param bool $isColorComponent * @param int<0, max> $lineNumber */ - public function __construct($size, $unit = null, $isColorComponent = false, $lineNumber = 0) + public function __construct($size, $unit = null, $isColorComponent = false, int $lineNumber = 0) { parent::__construct($lineNumber); $this->size = (float) $size; diff --git a/src/Value/URL.php b/src/Value/URL.php index 58edb257..5a122ff3 100644 --- a/src/Value/URL.php +++ b/src/Value/URL.php @@ -23,7 +23,7 @@ class URL extends PrimitiveValue /** * @param int<0, max> $lineNumber */ - public function __construct(CSSString $url, $lineNumber = 0) + public function __construct(CSSString $url, int $lineNumber = 0) { parent::__construct($lineNumber); $this->url = $url; diff --git a/src/Value/Value.php b/src/Value/Value.php index 49aa5f12..27840cc3 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -26,7 +26,7 @@ abstract class Value implements Renderable /** * @param int<0, max> $lineNumber */ - public function __construct($lineNumber = 0) + public function __construct(int $lineNumber = 0) { $this->lineNumber = $lineNumber; } diff --git a/src/Value/ValueList.php b/src/Value/ValueList.php index 67ad780f..b58af646 100644 --- a/src/Value/ValueList.php +++ b/src/Value/ValueList.php @@ -33,7 +33,7 @@ abstract class ValueList extends Value * @param string $separator * @param int<0, max> $lineNumber */ - public function __construct($components = [], $separator = ',', $lineNumber = 0) + public function __construct($components = [], $separator = ',', int $lineNumber = 0) { parent::__construct($lineNumber); if (!\is_array($components)) { From 19ecca852140e6444e878df8a79baf4ad9ef102a Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 11 Mar 2025 16:06:23 +0100 Subject: [PATCH 352/555] [TASK] Add some rendering tests for `Document` (#1138) Part of #757 --- tests/Functional/CSSList/DocumentTest.php | 137 ++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 tests/Functional/CSSList/DocumentTest.php diff --git a/tests/Functional/CSSList/DocumentTest.php b/tests/Functional/CSSList/DocumentTest.php new file mode 100644 index 00000000..71334f7f --- /dev/null +++ b/tests/Functional/CSSList/DocumentTest.php @@ -0,0 +1,137 @@ +render()); + } + + /** + * @test + */ + public function renderWithVirginOutputFormatCanRenderEmptyDocument(): void + { + $subject = new Document(); + + self::assertSame('', $subject->render(new OutputFormat())); + } + + /** + * @test + */ + public function renderWithDefaultOutputFormatCanRenderEmptyDocument(): void + { + $subject = new Document(); + + self::assertSame('', $subject->render(OutputFormat::create())); + } + + /** + * @test + */ + public function renderWithCompactOutputFormatCanRenderEmptyDocument(): void + { + $subject = new Document(); + + self::assertSame('', $subject->render(OutputFormat::createCompact())); + } + + /** + * @test + */ + public function renderWithPrettyOutputFormatCanRenderEmptyDocument(): void + { + $subject = new Document(); + + self::assertSame('', $subject->render(OutputFormat::createPretty())); + } + + /** + * Builds a subject with one `@charset` rule and one `@media` rule. + */ + private function buildSubjectWithAtRules(): Document + { + $subject = new Document(); + $charset = new Charset(new CSSString('UTF-8')); + $subject->append($charset); + $mediaQuery = new AtRuleBlockList('media', 'screen'); + $subject->append($mediaQuery); + + return $subject; + } + + /** + * @test + */ + public function renderWithoutOutputFormatCanRenderAtRules(): void + { + $subject = $this->buildSubjectWithAtRules(); + + $expected = '@charset "UTF-8";' . "\n" . '@media screen {}'; + self::assertSame($expected, $subject->render()); + } + + /** + * @test + */ + public function renderWithVirginOutputFormatCanRenderAtRules(): void + { + $subject = $this->buildSubjectWithAtRules(); + + $expected = '@charset "UTF-8";' . "\n" . '@media screen {}'; + self::assertSame($expected, $subject->render(new OutputFormat())); + } + + /** + * @test + */ + public function renderWithDefaultOutputFormatCanRenderAtRules(): void + { + $subject = $this->buildSubjectWithAtRules(); + + $expected = '@charset "UTF-8";' . "\n" . '@media screen {}'; + self::assertSame($expected, $subject->render(OutputFormat::create())); + } + + /** + * @test + */ + public function renderWithCompactOutputFormatCanRenderAtRules(): void + { + $subject = $this->buildSubjectWithAtRules(); + + $expected = '@charset "UTF-8";@media screen{}'; + self::assertSame($expected, $subject->render(OutputFormat::createCompact())); + } + + /** + * @test + */ + public function renderWithPrettyOutputFormatCanRenderAtRules(): void + { + $subject = $this->buildSubjectWithAtRules(); + + $expected = "\n" . '@charset "UTF-8";' . "\n\n" . '@media screen {}' . "\n"; + self::assertSame($expected, $subject->render(OutputFormat::createPretty())); + } +} From 632379dc4b013b63deff24a58535e74585371378 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 11 Mar 2025 18:21:19 +0100 Subject: [PATCH 353/555] [TASK] Add some more type declarations (#1139) These are some random changes the Rector would do as a result of the changes of some open PRs. --- CHANGELOG.md | 2 +- src/CSSList/CSSList.php | 4 +--- src/Value/Value.php | 4 +--- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 711be85f..a8730e08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,7 +32,7 @@ Please also have a look at our - Make all non-private properties `@internal` (#886) - Use more native type declarations and strict mode (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933, #958, - #964, #967, #1000, #1044, #1134) + #964, #967, #1000, #1044, #1134, #1139) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 3c538593..1af21f85 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -245,10 +245,8 @@ private static function parseAtRule(ParserState $parserState) /** * Tests an identifier for a given value. Since identifiers are all keywords, they can be vendor-prefixed. * We need to check for these versions too. - * - * @param string $identifier */ - private static function identifierIs($identifier, string $match): bool + private static function identifierIs(string $identifier, string $match): bool { return (\strcasecmp($identifier, $match) === 0) ?: \preg_match("/^(-\\w+-)?$match$/i", $identifier) === 1; diff --git a/src/Value/Value.php b/src/Value/Value.php index 27840cc3..a81ab472 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -109,8 +109,6 @@ public static function parseValue(ParserState $parserState, array $listDelimiter } /** - * @param bool $ignoreCase - * * @return CSSFunction|string * * @throws UnexpectedEOFException @@ -118,7 +116,7 @@ public static function parseValue(ParserState $parserState, array $listDelimiter * * @internal since V8.8.0 */ - public static function parseIdentifierOrFunction(ParserState $parserState, $ignoreCase = false) + public static function parseIdentifierOrFunction(ParserState $parserState, bool $ignoreCase = false) { $anchor = $parserState->anchor(); $result = $parserState->parseIdentifier($ignoreCase); From db8cad1742726cddf8a4e127a7e16d15dc6669d1 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 11 Mar 2025 19:07:45 +0100 Subject: [PATCH 354/555] [TASK] Use native type declarations in `Document` (#1137) Part of #811 --- CHANGELOG.md | 2 +- src/CSSList/Document.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8730e08..7d0d92f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,7 +32,7 @@ Please also have a look at our - Make all non-private properties `@internal` (#886) - Use more native type declarations and strict mode (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933, #958, - #964, #967, #1000, #1044, #1134, #1139) + #964, #967, #1000, #1044, #1134, #1137, #1139) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/src/CSSList/Document.php b/src/CSSList/Document.php index 2051d3c2..f50c166f 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -42,7 +42,7 @@ public static function parse(ParserState $parserState): Document * * @see RuleSet->getRules() */ - public function getAllValues($element = null, $searchInFunctionArguments = false): array + public function getAllValues($element = null, bool $searchInFunctionArguments = false): array { $searchString = null; if ($element === null) { @@ -70,7 +70,7 @@ public function getAllValues($element = null, $searchInFunctionArguments = false * @return array * @example `getSelectorsBySpecificity('>= 100')` */ - public function getSelectorsBySpecificity($specificitySearch = null): array + public function getSelectorsBySpecificity(?string $specificitySearch = null): array { /** @var array $result */ $result = []; From ff247ef2b41df2f59cbc23159ed120e414aca415 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 11 Mar 2025 21:15:01 +0100 Subject: [PATCH 355/555] [TASK] Use native type declarations in `ParserState` (#1136) Part of #811 Co-authored-by: JakeQZ --- CHANGELOG.md | 2 +- config/phpstan-baseline.neon | 18 -------- src/Parsing/ParserState.php | 90 ++++++++++++------------------------ 3 files changed, 30 insertions(+), 80 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d0d92f4..f24a4096 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,7 +32,7 @@ Please also have a look at our - Make all non-private properties `@internal` (#886) - Use more native type declarations and strict mode (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933, #958, - #964, #967, #1000, #1044, #1134, #1137, #1139) + #964, #967, #1000, #1044, #1134, #1136, #1137, #1139) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 3949389b..7ae723e9 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -114,24 +114,6 @@ parameters: count: 1 path: ../src/Parsing/ParserState.php - - - message: '#^Parameters should have "bool" types as the only types passed to this method$#' - identifier: typePerfect.narrowPublicClassMethodParamType - count: 2 - path: ../src/Parsing/ParserState.php - - - - message: '#^Parameters should have "int" types as the only types passed to this method$#' - identifier: typePerfect.narrowPublicClassMethodParamType - count: 2 - path: ../src/Parsing/ParserState.php - - - - message: '#^Parameters should have "string\|int\|null" types as the only types passed to this method$#' - identifier: typePerfect.narrowPublicClassMethodParamType - count: 1 - path: ../src/Parsing/ParserState.php - - message: '#^Cannot call method render\(\) on string\.$#' identifier: method.nonObject diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 76d66b01..13319973 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -33,7 +33,7 @@ class ParserState private $characters; /** - * @var int + * @var int<0, max> */ private $currentPosition = 0; @@ -53,7 +53,7 @@ class ParserState * @param string $text the complete CSS as text (i.e., usually the contents of a CSS file) * @param int<1, max> $lineNumber */ - public function __construct($text, Settings $parserSettings, int $lineNumber = 1) + public function __construct(string $text, Settings $parserSettings, int $lineNumber = 1) { $this->parserSettings = $parserSettings; $this->text = $text; @@ -75,23 +75,20 @@ public function setCharset(string $charset): void /** * @return int<1, max> */ - public function currentLine() + public function currentLine(): int { return $this->lineNumber; } /** - * @return int + * @return int<0, max> */ - public function currentColumn() + public function currentColumn(): int { return $this->currentPosition; } - /** - * @return Settings - */ - public function getSettings() + public function getSettings(): Settings { return $this->parserSettings; } @@ -102,21 +99,17 @@ public function anchor(): Anchor } /** - * @param int $position + * @param int<0, max> $position */ - public function setPosition($position): void + public function setPosition(int $position): void { $this->currentPosition = $position; } /** - * @param bool $ignoreCase - * - * @return string - * * @throws UnexpectedTokenException */ - public function parseIdentifier($ignoreCase = true) + public function parseIdentifier(bool $ignoreCase = true): string { if ($this->isEnd()) { throw new UnexpectedEOFException('', '', 'identifier', $this->lineNumber); @@ -140,14 +133,10 @@ public function parseIdentifier($ignoreCase = true) } /** - * @param bool $isForIdentifier - * - * @return string|null - * * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ - public function parseCharacter($isForIdentifier) + public function parseCharacter(bool $isForIdentifier): ?string { if ($this->peek() === '\\') { $this->consume('\\'); @@ -225,11 +214,7 @@ public function consumeWhiteSpace(): array return $comments; } - /** - * @param string $string - * @param bool $caseInsensitive - */ - public function comes($string, $caseInsensitive = false): bool + public function comes(string $string, bool $caseInsensitive = false): bool { $peek = $this->peek(\strlen($string)); return ($peek == '') @@ -238,10 +223,10 @@ public function comes($string, $caseInsensitive = false): bool } /** - * @param int $length - * @param int $offset + * @param int<1, max> $length + * @param int<0, max> $offset */ - public function peek($length = 1, $offset = 0): string + public function peek(int $length = 1, int $offset = 0): string { $offset += $this->currentPosition; if ($offset >= \count($this->characters)) { @@ -251,7 +236,7 @@ public function peek($length = 1, $offset = 0): string } /** - * @param int $value + * @param string|int<1, max> $value * * @throws UnexpectedEOFException * @throws UnexpectedTokenException @@ -286,12 +271,12 @@ public function consume($value = 1): string /** * @param string $expression - * @param int|null $maximumLength + * @param int<1, max>|null $maximumLength * * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ - public function consumeExpression($expression, $maximumLength = null): string + public function consumeExpression(string $expression, ?int $maximumLength = null): string { $matches = null; $input = $maximumLength !== null ? $this->peek($maximumLength) : $this->inputLeft(); @@ -335,8 +320,6 @@ public function isEnd(): bool /** * @param array|string $stopCharacters - * @param bool $includeEnd - * @param bool $consumeEnd * @param array $comments * * @throws UnexpectedEOFException @@ -344,8 +327,8 @@ public function isEnd(): bool */ public function consumeUntil( $stopCharacters, - $includeEnd = false, - $consumeEnd = false, + bool $includeEnd = false, + bool $consumeEnd = false, array &$comments = [] ): string { $stopCharacters = \is_array($stopCharacters) ? $stopCharacters : [$stopCharacters]; @@ -386,12 +369,7 @@ private function inputLeft(): string return $this->substr($this->currentPosition, -1); } - /** - * @param string $string1 - * @param string $string2 - * @param bool $caseInsensitive - */ - public function streql($string1, $string2, $caseInsensitive = true): bool + public function streql(string $string1, string $string2, bool $caseInsensitive = true): bool { if ($caseInsensitive) { return $this->strtolower($string1) === $this->strtolower($string2); @@ -401,17 +379,17 @@ public function streql($string1, $string2, $caseInsensitive = true): bool } /** - * @param int $numberOfCharacters + * @param int<1, max> $numberOfCharacters */ - public function backtrack($numberOfCharacters): void + public function backtrack(int $numberOfCharacters): void { $this->currentPosition -= $numberOfCharacters; } /** - * @param string $string + * @return int<0, max> */ - public function strlen($string): int + public function strlen(string $string): int { if ($this->parserSettings->hasMultibyteSupport()) { return \mb_strlen($string, $this->charset); @@ -421,10 +399,9 @@ public function strlen($string): int } /** - * @param int $offset - * @param int $length + * @param int<0, max> $offset */ - private function substr($offset, $length): string + private function substr(int $offset, int $length): string { if ($length < 0) { $length = \count($this->characters) - $offset + $length; @@ -441,10 +418,7 @@ private function substr($offset, $length): string return $result; } - /** - * @param string $string - */ - private function strtolower($string): string + private function strtolower(string $string): string { if ($this->parserSettings->hasMultibyteSupport()) { return \mb_strtolower($string, $this->charset); @@ -454,13 +428,11 @@ private function strtolower($string): string } /** - * @param string $string - * * @return array * * @throws SourceException if the charset is UTF-8 and the string contains invalid byte sequences */ - private function strsplit($string) + private function strsplit(string $string): array { if ($this->parserSettings->hasMultibyteSupport()) { if ($this->streql($this->charset, 'utf-8')) { @@ -487,13 +459,9 @@ private function strsplit($string) } /** - * @param string $haystack - * @param string $needle - * @param int $offset - * * @return int|false */ - private function strpos($haystack, $needle, $offset) + private function strpos(string $haystack, string $needle, int $offset) { if ($this->parserSettings->hasMultibyteSupport()) { return \mb_strpos($haystack, $needle, $offset, $this->charset); From 681fef6993a660f946549910ec03865711faf849 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 11 Mar 2025 23:12:38 +0100 Subject: [PATCH 356/555] [TASK] Use native type declarations in `Parser` (#1140) Part of #811 --- CHANGELOG.md | 2 +- src/Parser.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f24a4096..7059d52d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,7 +32,7 @@ Please also have a look at our - Make all non-private properties `@internal` (#886) - Use more native type declarations and strict mode (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933, #958, - #964, #967, #1000, #1044, #1134, #1136, #1137, #1139) + #964, #967, #1000, #1044, #1134, #1136, #1137, #1139, #1140) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/src/Parser.php b/src/Parser.php index d3290b1e..b34a5107 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -22,7 +22,7 @@ class Parser * @param string $text the complete CSS as text (i.e., usually the contents of a CSS file) * @param int<1, max> $lineNumber the line number (starting from 1, not from 0) */ - public function __construct($text, ?Settings $parserSettings = null, int $lineNumber = 1) + public function __construct(string $text, ?Settings $parserSettings = null, int $lineNumber = 1) { if ($parserSettings === null) { $parserSettings = Settings::create(); From 6e9d010a4bd46644ec74c2e24c6fa7e0156ba11d Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 11 Mar 2025 23:22:27 +0100 Subject: [PATCH 357/555] [TASK] Use native type declarations in `Anchor` (#1141) Part of #811 --- CHANGELOG.md | 2 +- src/Parsing/Anchor.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7059d52d..36ef5ffc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,7 +32,7 @@ Please also have a look at our - Make all non-private properties `@internal` (#886) - Use more native type declarations and strict mode (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933, #958, - #964, #967, #1000, #1044, #1134, #1136, #1137, #1139, #1140) + #964, #967, #1000, #1044, #1134, #1136, #1137, #1139, #1140, #1141) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/src/Parsing/Anchor.php b/src/Parsing/Anchor.php index e7cf9244..c27f436a 100644 --- a/src/Parsing/Anchor.php +++ b/src/Parsing/Anchor.php @@ -10,7 +10,7 @@ class Anchor { /** - * @var int + * @var int<0, max> */ private $position; @@ -20,9 +20,9 @@ class Anchor private $parserState; /** - * @param int $position + * @param int<0, max> $position */ - public function __construct($position, ParserState $parserState) + public function __construct(int $position, ParserState $parserState) { $this->position = $position; $this->parserState = $parserState; From 261eb1b37bf06e78445c7805ce6776b1114ce8c4 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 12 Mar 2025 00:03:02 +0100 Subject: [PATCH 358/555] [CLEANUP] Make a type annotation more specific (#1143) --- src/Property/AtRule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Property/AtRule.php b/src/Property/AtRule.php index 4d8d74ff..5e768af5 100644 --- a/src/Property/AtRule.php +++ b/src/Property/AtRule.php @@ -13,7 +13,7 @@ interface AtRule extends Renderable, Commentable * Since there are more set rules than block rules, * we’re whitelisting the block rules and have anything else be treated as a set rule. * - * @var string + * @var non-empty-string * * @internal since 8.5.2 */ From 66718c1b25f18824430b11c291023296f1da823e Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 12 Mar 2025 00:06:06 +0100 Subject: [PATCH 359/555] [CLEANUP] Drop an unused internal constant (#1144) --- src/Property/AtRule.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/Property/AtRule.php b/src/Property/AtRule.php index 5e768af5..f4b7c724 100644 --- a/src/Property/AtRule.php +++ b/src/Property/AtRule.php @@ -19,15 +19,6 @@ interface AtRule extends Renderable, Commentable */ public const BLOCK_RULES = 'media/document/supports/region-style/font-feature-values'; - /** - * … and more font-specific ones (to be used inside font-feature-values) - * - * @var string - * - * @internal since 8.5.2 - */ - public const SET_RULES = 'font-face/counter-style/page/swash/styleset/annotation'; - /** * @return string|null */ From 46da4b251c6ebe0033cdf064e9b4289f82cf302d Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Wed, 12 Mar 2025 08:07:49 +0000 Subject: [PATCH 360/555] [TASK] Initialize `KeyFrame` properties (#1146) They fortunately have obvious default values. This change means it can be enforced that they are always non-empty strings. Type declarations have been updated to reflect that. --- CHANGELOG.md | 1 + config/phpstan-baseline.neon | 6 ------ src/CSSList/KeyFrame.php | 32 ++++++++++++++--------------- tests/Unit/CSSList/KeyFrameTest.php | 20 ++++++++++++++++++ 4 files changed, 37 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36ef5ffc..d3a0338a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Please also have a look at our ### Changed +- Initialize `KeyFrame` properties to sensible defaults (#1146) - Make `OutputFormat` `final` (#1128) - Mark the `OutputFormat` constructor as `@internal` (#1131) - Mark `OutputFormatter` as `@internal` (#896) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 7ae723e9..6c0a6d88 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -96,12 +96,6 @@ parameters: count: 1 path: ../src/CSSList/KeyFrame.php - - - message: '#^Parameters should have "string" types as the only types passed to this method$#' - identifier: typePerfect.narrowPublicClassMethodParamType - count: 2 - path: ../src/CSSList/KeyFrame.php - - message: '#^Loose comparison via "\=\=" is not allowed\.$#' identifier: equal.notAllowed diff --git a/src/CSSList/KeyFrame.php b/src/CSSList/KeyFrame.php index 3177ac19..40e0823d 100644 --- a/src/CSSList/KeyFrame.php +++ b/src/CSSList/KeyFrame.php @@ -10,43 +10,43 @@ class KeyFrame extends CSSList implements AtRule { /** - * @var string|null + * @var non-empty-string */ - private $vendorKeyFrame; + private $vendorKeyFrame = 'keyframes'; /** - * @var string|null + * @var non-empty-string */ - private $animationName; + private $animationName = 'none'; /** - * @param string $vendorKeyFrame + * @param non-empty-string $vendorKeyFrame */ - public function setVendorKeyFrame($vendorKeyFrame): void + public function setVendorKeyFrame(string $vendorKeyFrame): void { $this->vendorKeyFrame = $vendorKeyFrame; } /** - * @return string|null + * @return non-empty-string */ - public function getVendorKeyFrame() + public function getVendorKeyFrame(): string { return $this->vendorKeyFrame; } /** - * @param string $animationName + * @param non-empty-string $animationName */ - public function setAnimationName($animationName): void + public function setAnimationName(string $animationName): void { $this->animationName = $animationName; } /** - * @return string|null + * @return non-empty-string */ - public function getAnimationName() + public function getAnimationName(): string { return $this->animationName; } @@ -74,17 +74,17 @@ public function isRootList(): bool } /** - * @return string|null + * @return non-empty-string */ - public function atRuleName() + public function atRuleName(): string { return $this->vendorKeyFrame; } /** - * @return string|null + * @return non-empty-string */ - public function atRuleArgs() + public function atRuleArgs(): string { return $this->animationName; } diff --git a/tests/Unit/CSSList/KeyFrameTest.php b/tests/Unit/CSSList/KeyFrameTest.php index 552dcada..20be302c 100644 --- a/tests/Unit/CSSList/KeyFrameTest.php +++ b/tests/Unit/CSSList/KeyFrameTest.php @@ -68,4 +68,24 @@ public function getLineNoReturnsLineNumberProvidedToConstructor(): void self::assertSame($lineNumber, $subject->getLineNo()); } + + /** + * @test + */ + public function getAnimationNameByDefaultReturnsNone(): void + { + $subject = new KeyFrame(); + + self::assertSame('none', $subject->getAnimationName()); + } + + /** + * @test + */ + public function getVendorKeyFrameByDefaultReturnsKeyframes(): void + { + $subject = new KeyFrame(); + + self::assertSame('keyframes', $subject->getVendorKeyFrame()); + } } From 6d934aff744f5fe28b253c6c080e5a0f97bc0dca Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 12 Mar 2025 16:26:18 +0100 Subject: [PATCH 361/555] [TASK] Add native type declarations for `atRuleName()` (#1145) Part of #811 --- CHANGELOG.md | 2 +- src/CSSList/AtRuleBlockList.php | 6 +++++- src/Property/AtRule.php | 4 ++-- src/Property/CSSNamespace.php | 2 +- src/Property/Charset.php | 3 +++ src/Property/Import.php | 3 +++ src/RuleSet/AtRuleSet.php | 8 ++++---- 7 files changed, 19 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3a0338a..c14d3760 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,7 +33,7 @@ Please also have a look at our - Make all non-private properties `@internal` (#886) - Use more native type declarations and strict mode (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933, #958, - #964, #967, #1000, #1044, #1134, #1136, #1137, #1139, #1140, #1141) + #964, #967, #1000, #1044, #1134, #1136, #1137, #1139, #1140, #1141, #1145) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/src/CSSList/AtRuleBlockList.php b/src/CSSList/AtRuleBlockList.php index 2fbe755c..ad91fe81 100644 --- a/src/CSSList/AtRuleBlockList.php +++ b/src/CSSList/AtRuleBlockList.php @@ -13,7 +13,7 @@ class AtRuleBlockList extends CSSBlockList implements AtRule { /** - * @var string + * @var non-empty-string */ private $type; @@ -23,6 +23,7 @@ class AtRuleBlockList extends CSSBlockList implements AtRule private $arguments; /** + * @param non-empty-string $type * @param int<0, max> $lineNumber */ public function __construct(string $type, string $arguments = '', int $lineNumber = 0) @@ -32,6 +33,9 @@ public function __construct(string $type, string $arguments = '', int $lineNumbe $this->arguments = $arguments; } + /** + * @return non-empty-string + */ public function atRuleName(): string { return $this->type; diff --git a/src/Property/AtRule.php b/src/Property/AtRule.php index f4b7c724..f17b4227 100644 --- a/src/Property/AtRule.php +++ b/src/Property/AtRule.php @@ -20,9 +20,9 @@ interface AtRule extends Renderable, Commentable public const BLOCK_RULES = 'media/document/supports/region-style/font-feature-values'; /** - * @return string|null + * @return non-empty-string */ - public function atRuleName(); + public function atRuleName(): string; /** * @return string|null diff --git a/src/Property/CSSNamespace.php b/src/Property/CSSNamespace.php index b7604456..32a0419d 100644 --- a/src/Property/CSSNamespace.php +++ b/src/Property/CSSNamespace.php @@ -101,7 +101,7 @@ public function setPrefix($prefix): void } /** - * @return string + * @return non-empty-string */ public function atRuleName(): string { diff --git a/src/Property/Charset.php b/src/Property/Charset.php index 59675665..97510a1c 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -84,6 +84,9 @@ public function render(OutputFormat $outputFormat): string return "{$outputFormat->comments($this)}@charset {$this->charset->render($outputFormat)};"; } + /** + * @return non-empty-string + */ public function atRuleName(): string { return 'charset'; diff --git a/src/Property/Import.php b/src/Property/Import.php index 77ae17cf..41059738 100644 --- a/src/Property/Import.php +++ b/src/Property/Import.php @@ -86,6 +86,9 @@ public function render(OutputFormat $outputFormat): string . ($this->mediaQuery === null ? '' : ' ' . $this->mediaQuery) . ';'; } + /** + * @return non-empty-string + */ public function atRuleName(): string { return 'import'; diff --git a/src/RuleSet/AtRuleSet.php b/src/RuleSet/AtRuleSet.php index 96fed182..c8fb79e3 100644 --- a/src/RuleSet/AtRuleSet.php +++ b/src/RuleSet/AtRuleSet.php @@ -16,7 +16,7 @@ class AtRuleSet extends RuleSet implements AtRule { /** - * @var string + * @var non-empty-string */ private $type; @@ -26,7 +26,7 @@ class AtRuleSet extends RuleSet implements AtRule private $arguments; /** - * @param string $type + * @param non-empty-string $type * @param string $arguments * @param int<0, max> $lineNumber */ @@ -38,9 +38,9 @@ public function __construct($type, $arguments = '', int $lineNumber = 0) } /** - * @return string + * @return non-empty-string */ - public function atRuleName() + public function atRuleName(): string { return $this->type; } From a389a845727bd92ba3f7a57f9c0775ae7b30aea8 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 12 Mar 2025 16:29:28 +0100 Subject: [PATCH 362/555] [CLEANUP] Avoid magic method forwarding in `AtRuleBlockList` (#1148) Part of #1147 --- config/phpstan-baseline.neon | 12 ------------ src/CSSList/AtRuleBlockList.php | 5 +++-- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 6c0a6d88..7535b3d8 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -1,17 +1,5 @@ parameters: ignoreErrors: - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:comments\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/CSSList/AtRuleBlockList.php - - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceBeforeOpeningBrace\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/CSSList/AtRuleBlockList.php - - message: '#^Only booleans are allowed in an if condition, string given\.$#' identifier: if.condNotBoolean diff --git a/src/CSSList/AtRuleBlockList.php b/src/CSSList/AtRuleBlockList.php index ad91fe81..5d17ee86 100644 --- a/src/CSSList/AtRuleBlockList.php +++ b/src/CSSList/AtRuleBlockList.php @@ -56,13 +56,14 @@ public function __toString(): string public function render(OutputFormat $outputFormat): string { - $result = $outputFormat->comments($this); + $formatter = $outputFormat->getFormatter(); + $result = $formatter->comments($this); $result .= $outputFormat->getContentBeforeAtRuleBlock(); $arguments = $this->arguments; if ($arguments) { $arguments = ' ' . $arguments; } - $result .= "@{$this->type}$arguments{$outputFormat->spaceBeforeOpeningBrace()}{"; + $result .= "@{$this->type}$arguments{$formatter->spaceBeforeOpeningBrace()}{"; $result .= $this->renderListContents($outputFormat); $result .= '}'; $result .= $outputFormat->getContentAfterAtRuleBlock(); From 4e54a8eba3d46cb7f1e6c371135a79a31a60bcd9 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 12 Mar 2025 16:30:40 +0100 Subject: [PATCH 363/555] [CLEANUP] Avoid magic method forwarding in `ValueList` (#1149) Part of #1147 --- config/phpstan-baseline.neon | 18 ------------------ src/Value/ValueList.php | 8 +++++--- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 7535b3d8..92f24b0a 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -371,21 +371,3 @@ parameters: identifier: typePerfect.narrowPublicClassMethodParamType count: 1 path: ../src/Value/Size.php - - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:implode\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/Value/ValueList.php - - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceAfterListArgumentSeparator\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/Value/ValueList.php - - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceBeforeListArgumentSeparator\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/Value/ValueList.php diff --git a/src/Value/ValueList.php b/src/Value/ValueList.php index b58af646..8a50809d 100644 --- a/src/Value/ValueList.php +++ b/src/Value/ValueList.php @@ -93,9 +93,11 @@ public function __toString(): string public function render(OutputFormat $outputFormat): string { - return $outputFormat->implode( - $outputFormat->spaceBeforeListArgumentSeparator($this->separator) . $this->separator - . $outputFormat->spaceAfterListArgumentSeparator($this->separator), + $formatter = $outputFormat->getFormatter(); + + return $formatter->implode( + $formatter->spaceBeforeListArgumentSeparator($this->separator) . $this->separator + . $formatter->spaceAfterListArgumentSeparator($this->separator), $this->components ); } From 13f1ff52819b3abb14222afa6704549010afb614 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 12 Mar 2025 16:31:22 +0100 Subject: [PATCH 364/555] [CLEANUP] Avoid magic method forwarding in `Rule` (#1150) Part of #1147 --- config/phpstan-baseline.neon | 12 ------------ src/Rule/Rule.php | 3 ++- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 92f24b0a..84be213c 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -144,18 +144,6 @@ parameters: count: 1 path: ../src/Property/Import.php - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:comments\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/Rule/Rule.php - - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceAfterRuleName\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/Rule/Rule.php - - message: '#^Only booleans are allowed in an if condition, Sabberworm\\CSS\\Value\\RuleValueList\|string\|null given\.$#' identifier: if.condNotBoolean diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index ccf070e9..42fef09d 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -234,7 +234,8 @@ public function __toString(): string public function render(OutputFormat $outputFormat): string { - $result = "{$outputFormat->comments($this)}{$this->rule}:{$outputFormat->spaceAfterRuleName()}"; + $formatter = $outputFormat->getFormatter(); + $result = "{$formatter->comments($this)}{$this->rule}:{$formatter->spaceAfterRuleName()}"; if ($this->value instanceof Value) { // Can also be a ValueList $result .= $this->value->render($outputFormat); } else { From 898d5cdd74a47cc80398b90a5a82ac4664d63d6a Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 12 Mar 2025 16:44:07 +0100 Subject: [PATCH 365/555] [TASK] Drop `atRuleArgs()` from the `AtRule` interface (#1142) The classes implementing this interface use a wide variaty of inconsistent return types when they implement this method. This is a hard blocker for introducing native return type declaration for this method. Co-authored-by: JakeQZ --- CHANGELOG.md | 1 + config/phpstan-baseline.neon | 12 ------------ src/Property/AtRule.php | 5 ----- 3 files changed, 1 insertion(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c14d3760..4c17a928 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ Please also have a look at our ### Removed +- Drop `atRuleArgs()` from the `AtRule` interface (#1141) - Remove `OutputFormat::get()` and `::set()` (#1108, #1110) - Drop special support for vendor prefixes (#1083) - Remove the IE hack in `Rule` (#995) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 84be213c..435a4cd3 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -108,12 +108,6 @@ parameters: count: 1 path: ../src/Property/CSSNamespace.php - - - message: '#^Return type \(array\\) of method Sabberworm\\CSS\\Property\\CSSNamespace\:\:atRuleArgs\(\) should be compatible with return type \(string\|null\) of method Sabberworm\\CSS\\Property\\AtRule\:\:atRuleArgs\(\)$#' - identifier: method.childReturnType - count: 1 - path: ../src/Property/CSSNamespace.php - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:comments\(\)\.$#' identifier: method.notFound @@ -138,12 +132,6 @@ parameters: count: 1 path: ../src/Property/Import.php - - - message: '#^Return type \(array\\) of method Sabberworm\\CSS\\Property\\Import\:\:atRuleArgs\(\) should be compatible with return type \(string\|null\) of method Sabberworm\\CSS\\Property\\AtRule\:\:atRuleArgs\(\)$#' - identifier: method.childReturnType - count: 1 - path: ../src/Property/Import.php - - message: '#^Only booleans are allowed in an if condition, Sabberworm\\CSS\\Value\\RuleValueList\|string\|null given\.$#' identifier: if.condNotBoolean diff --git a/src/Property/AtRule.php b/src/Property/AtRule.php index f17b4227..a2785213 100644 --- a/src/Property/AtRule.php +++ b/src/Property/AtRule.php @@ -23,9 +23,4 @@ interface AtRule extends Renderable, Commentable * @return non-empty-string */ public function atRuleName(): string; - - /** - * @return string|null - */ - public function atRuleArgs(); } From 92927fe84961dfc62179d22bf6de39d38b86a2da Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 12 Mar 2025 17:01:47 +0100 Subject: [PATCH 366/555] [CLEANUP] Avoid magic method forwarding in `CSSList` (#1151) Part of #1147 --- config/phpstan-baseline.neon | 24 ------------------------ src/CSSList/CSSList.php | 10 ++++++---- 2 files changed, 6 insertions(+), 28 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 435a4cd3..c3bc09da 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -18,30 +18,6 @@ parameters: count: 1 path: ../src/CSSList/CSSBlockList.php - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:safely\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/CSSList/CSSList.php - - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceAfterBlocks\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/CSSList/CSSList.php - - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceBeforeBlocks\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/CSSList/CSSList.php - - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceBetweenBlocks\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/CSSList/CSSList.php - - message: '#^Loose comparison via "\!\=" is not allowed\.$#' identifier: notEqual.notAllowed diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 1af21f85..15a5e87a 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -419,8 +419,10 @@ protected function renderListContents(OutputFormat $outputFormat) if (!$this->isRootList()) { $nextLevelFormat = $outputFormat->nextLevel(); } + $nextLevelFormatter = $nextLevelFormat->getFormatter(); + $formatter = $outputFormat->getFormatter(); foreach ($this->contents as $listItem) { - $renderedCss = $outputFormat->safely(static function () use ($nextLevelFormat, $listItem): string { + $renderedCss = $formatter->safely(static function () use ($nextLevelFormat, $listItem): string { return $listItem->render($nextLevelFormat); }); if ($renderedCss === null) { @@ -428,16 +430,16 @@ protected function renderListContents(OutputFormat $outputFormat) } if ($isFirst) { $isFirst = false; - $result .= $nextLevelFormat->spaceBeforeBlocks(); + $result .= $nextLevelFormatter->spaceBeforeBlocks(); } else { - $result .= $nextLevelFormat->spaceBetweenBlocks(); + $result .= $nextLevelFormatter->spaceBetweenBlocks(); } $result .= $renderedCss; } if (!$isFirst) { // Had some output - $result .= $outputFormat->spaceAfterBlocks(); + $result .= $formatter->spaceAfterBlocks(); } return $result; From 08612de81ba709d8576766cd657498860ba05244 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 12 Mar 2025 17:02:54 +0100 Subject: [PATCH 367/555] [CLEANUP] Avoid magic method forwarding in `Color` (#1152) Part of #1147 --- config/phpstan-baseline.neon | 18 ------------------ src/Value/Color.php | 9 +++++---- 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index c3bc09da..68ad99db 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -270,24 +270,6 @@ parameters: count: 1 path: ../src/Value/CalcRuleValueList.php - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:implode\(\)\.$#' - identifier: method.notFound - count: 2 - path: ../src/Value/Color.php - - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceAfterListArgumentSeparator\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/Value/Color.php - - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceBeforeListArgumentSeparator\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/Value/Color.php - - message: '#^Call to method Sabberworm\\CSS\\Value\\Color\:\:hasNoneAsComponentValue\(\) with incorrect case\: HasNoneAsComponentValue$#' identifier: method.nameCase diff --git a/src/Value/Color.php b/src/Value/Color.php index 8f1e2a74..bb8951dd 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -383,11 +383,12 @@ private function renderInModernSyntax(OutputFormat $outputFormat): string unset($componentsWithoutAlpha['a']); } - $arguments = $outputFormat->implode(' ', $componentsWithoutAlpha); + $formatter = $outputFormat->getFormatter(); + $arguments = $formatter->implode(' ', $componentsWithoutAlpha); if (isset($alpha)) { - $separator = $outputFormat->spaceBeforeListArgumentSeparator('/') - . '/' . $outputFormat->spaceAfterListArgumentSeparator('/'); - $arguments = $outputFormat->implode($separator, [$arguments, $alpha]); + $separator = $formatter->spaceBeforeListArgumentSeparator('/') + . '/' . $formatter->spaceAfterListArgumentSeparator('/'); + $arguments = $formatter->implode($separator, [$arguments, $alpha]); } return $this->getName() . '(' . $arguments . ')'; From 230fd2aa4c4d681b466b1c2957e075246cb0c748 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 12 Mar 2025 17:03:24 +0100 Subject: [PATCH 368/555] [CLEANUP] Avoid magic method forwarding in `AtRuleSet` (#1153) Part of #1147 --- config/phpstan-baseline.neon | 12 ------------ src/RuleSet/AtRuleSet.php | 5 +++-- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 68ad99db..3f8363a2 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -126,18 +126,6 @@ parameters: count: 1 path: ../src/Rule/Rule.php - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:comments\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/RuleSet/AtRuleSet.php - - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceBeforeOpeningBrace\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/RuleSet/AtRuleSet.php - - message: '#^Only booleans are allowed in an if condition, string given\.$#' identifier: if.condNotBoolean diff --git a/src/RuleSet/AtRuleSet.php b/src/RuleSet/AtRuleSet.php index c8fb79e3..a896246b 100644 --- a/src/RuleSet/AtRuleSet.php +++ b/src/RuleSet/AtRuleSet.php @@ -63,12 +63,13 @@ public function __toString(): string public function render(OutputFormat $outputFormat): string { - $result = $outputFormat->comments($this); + $formatter = $outputFormat->getFormatter(); + $result = $formatter->comments($this); $arguments = $this->arguments; if ($arguments) { $arguments = ' ' . $arguments; } - $result .= "@{$this->type}$arguments{$outputFormat->spaceBeforeOpeningBrace()}{"; + $result .= "@{$this->type}$arguments{$formatter->spaceBeforeOpeningBrace()}{"; $result .= $this->renderRules($outputFormat); $result .= '}'; return $result; From 7144fdee2fbbbd0c01379b50e699b2e172d76dcd Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 12 Mar 2025 17:24:22 +0100 Subject: [PATCH 369/555] [CLEANUP] Avoid magic method forwarding in `Document` (#1155) Part of #1147 --- config/phpstan-baseline.neon | 6 ------ src/CSSList/Document.php | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 3f8363a2..c174d792 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -36,12 +36,6 @@ parameters: count: 1 path: ../src/CSSList/CSSList.php - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:comments\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/CSSList/Document.php - - message: '#^Parameters should have "string\|null" types as the only types passed to this method$#' identifier: typePerfect.narrowPublicClassMethodParamType diff --git a/src/CSSList/Document.php b/src/CSSList/Document.php index f50c166f..10370dc0 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -86,7 +86,7 @@ public function render(?OutputFormat $outputFormat = null): string if ($outputFormat === null) { $outputFormat = new OutputFormat(); } - return $outputFormat->comments($this) . $this->renderListContents($outputFormat); + return $outputFormat->getFormatter()->comments($this) . $this->renderListContents($outputFormat); } public function isRootList(): bool From 159f7bed3b6499f25d3d27a36aac6e7f89e4e08a Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 12 Mar 2025 17:25:20 +0100 Subject: [PATCH 370/555] [CLEANUP] Avoid magic method forwarding in `Import` (#1156) Part of #1147 --- config/phpstan-baseline.neon | 6 ------ src/Property/Import.php | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index c174d792..ad8b5429 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -90,12 +90,6 @@ parameters: count: 1 path: ../src/Property/Charset.php - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:comments\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/Property/Import.php - - message: '#^Only booleans are allowed in an if condition, string given\.$#' identifier: if.condNotBoolean diff --git a/src/Property/Import.php b/src/Property/Import.php index 41059738..19bee125 100644 --- a/src/Property/Import.php +++ b/src/Property/Import.php @@ -82,7 +82,7 @@ public function __toString(): string public function render(OutputFormat $outputFormat): string { - return $outputFormat->comments($this) . '@import ' . $this->location->render($outputFormat) + return $outputFormat->getFormatter()->comments($this) . '@import ' . $this->location->render($outputFormat) . ($this->mediaQuery === null ? '' : ' ' . $this->mediaQuery) . ';'; } From 0cfbce3ea19306cf51030000c9b85df156dc7a33 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 12 Mar 2025 17:25:58 +0100 Subject: [PATCH 371/555] [CLEANUP] Avoid magic method forwarding in `CalcRuleValueList` (#1157) Part of #1147 --- config/phpstan-baseline.neon | 6 ------ src/Value/CalcRuleValueList.php | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index ad8b5429..00d29d04 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -240,12 +240,6 @@ parameters: count: 3 path: ../src/Value/CalcFunction.php - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:implode\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/Value/CalcRuleValueList.php - - message: '#^Call to method Sabberworm\\CSS\\Value\\Color\:\:hasNoneAsComponentValue\(\) with incorrect case\: HasNoneAsComponentValue$#' identifier: method.nameCase diff --git a/src/Value/CalcRuleValueList.php b/src/Value/CalcRuleValueList.php index d418f001..3c0f24ce 100644 --- a/src/Value/CalcRuleValueList.php +++ b/src/Value/CalcRuleValueList.php @@ -18,6 +18,6 @@ public function __construct(int $lineNumber = 0) public function render(OutputFormat $outputFormat): string { - return $outputFormat->implode(' ', $this->components); + return $outputFormat->getFormatter()->implode(' ', $this->components); } } From b0238f0a45d15d70ace175baa0b25d18f8082a37 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 12 Mar 2025 17:29:28 +0100 Subject: [PATCH 372/555] [DOCS] Avoid Hungarian notation in the README (#1154) Co-authored-by: JakeQZ --- README.md | 262 +++++++++++++++++++++++++++--------------------------- 1 file changed, 131 insertions(+), 131 deletions(-) diff --git a/README.md b/README.md index 7d18303b..3901bc37 100644 --- a/README.md +++ b/README.md @@ -221,44 +221,44 @@ html, body { ```php class Sabberworm\CSS\CSSList\Document#4 (2) { - protected $aContents => + protected $contents => array(4) { [0] => class Sabberworm\CSS\Property\Charset#6 (2) { - private $sCharset => + private $charset => class Sabberworm\CSS\Value\CSSString#5 (2) { - private $sString => + private $string => string(5) "utf-8" - protected $iLineNo => + protected $lineNumber => int(1) } - protected $iLineNo => + protected $lineNumber => int(1) } [1] => class Sabberworm\CSS\RuleSet\AtRuleSet#7 (4) { - private $sType => + private $type => string(9) "font-face" - private $sArgs => + private $arguments => string(0) "" - private $aRules => + private $rules => array(2) { 'font-family' => array(1) { [0] => class Sabberworm\CSS\Rule\Rule#8 (4) { - private $sRule => + private $rule => string(11) "font-family" - private $mValue => + private $value => class Sabberworm\CSS\Value\CSSString#9 (2) { - private $sString => + private $string => string(10) "CrassRoots" - protected $iLineNo => + protected $lineNumber => int(4) } - private $bIsImportant => + private $isImportant => bool(false) - protected $iLineNo => + protected $lineNumber => int(4) } } @@ -266,76 +266,76 @@ class Sabberworm\CSS\CSSList\Document#4 (2) { array(1) { [0] => class Sabberworm\CSS\Rule\Rule#10 (4) { - private $sRule => + private $rule => string(3) "src" - private $mValue => + private $value => class Sabberworm\CSS\Value\URL#11 (2) { - private $oURL => + private $url => class Sabberworm\CSS\Value\CSSString#12 (2) { - private $sString => + private $string => string(15) "../media/cr.ttf" - protected $iLineNo => + protected $lineNumber => int(5) } - protected $iLineNo => + protected $lineNumber => int(5) } - private $bIsImportant => + private $isImportant => bool(false) - protected $iLineNo => + protected $lineNumber => int(5) } } } - protected $iLineNo => + protected $lineNumber => int(3) } [2] => class Sabberworm\CSS\RuleSet\DeclarationBlock#13 (3) { - private $aSelectors => + private $selectors => array(2) { [0] => class Sabberworm\CSS\Property\Selector#14 (2) { - private $sSelector => + private $selector => string(4) "html" - private $iSpecificity => + private $specificity => NULL } [1] => class Sabberworm\CSS\Property\Selector#15 (2) { - private $sSelector => + private $selector => string(4) "body" - private $iSpecificity => + private $specificity => NULL } } - private $aRules => + private $rules => array(1) { 'font-size' => array(1) { [0] => class Sabberworm\CSS\Rule\Rule#16 (4) { - private $sRule => + private $rule => string(9) "font-size" - private $mValue => + private $value => class Sabberworm\CSS\Value\Size#17 (4) { - private $fSize => + private $size => double(1.6) - private $sUnit => + private $unit => string(2) "em" - private $bIsColorComponent => + private $isColorComponent => bool(false) - protected $iLineNo => + protected $lineNumber => int(9) } - private $bIsImportant => + private $isImportant => bool(false) - protected $iLineNo => + protected $lineNumber => int(9) } } } - protected $iLineNo => + protected $lineNumber => int(8) } [3] => @@ -344,96 +344,96 @@ class Sabberworm\CSS\CSSList\Document#4 (2) { string(9) "keyframes" private $animationName => string(6) "mymove" - protected $aContents => + protected $contents => array(2) { [0] => class Sabberworm\CSS\RuleSet\DeclarationBlock#19 (3) { - private $aSelectors => + private $selectors => array(1) { [0] => class Sabberworm\CSS\Property\Selector#20 (2) { - private $sSelector => + private $selector => string(4) "from" - private $iSpecificity => + private $specificity => NULL } } - private $aRules => + private $rules => array(1) { 'top' => array(1) { [0] => class Sabberworm\CSS\Rule\Rule#21 (4) { - private $sRule => + private $rule => string(3) "top" - private $mValue => + private $value => class Sabberworm\CSS\Value\Size#22 (4) { - private $fSize => + private $size => double(0) - private $sUnit => + private $unit => string(2) "px" - private $bIsColorComponent => + private $isColorComponent => bool(false) - protected $iLineNo => + protected $lineNumber => int(13) } - private $bIsImportant => + private $isImportant => bool(false) - protected $iLineNo => + protected $lineNumber => int(13) } } } - protected $iLineNo => + protected $lineNumber => int(13) } [1] => class Sabberworm\CSS\RuleSet\DeclarationBlock#23 (3) { - private $aSelectors => + private $selectors => array(1) { [0] => class Sabberworm\CSS\Property\Selector#24 (2) { - private $sSelector => + private $selector => string(2) "to" - private $iSpecificity => + private $specificity => NULL } } - private $aRules => + private $rules => array(1) { 'top' => array(1) { [0] => class Sabberworm\CSS\Rule\Rule#25 (4) { - private $sRule => + private $rule => string(3) "top" - private $mValue => + private $value => class Sabberworm\CSS\Value\Size#26 (4) { - private $fSize => + private $size => double(200) - private $sUnit => + private $unit => string(2) "px" - private $bIsColorComponent => + private $isColorComponent => bool(false) - protected $iLineNo => + protected $lineNumber => int(14) } - private $bIsImportant => + private $isImportant => bool(false) - protected $iLineNo => + protected $lineNumber => int(14) } } } - protected $iLineNo => + protected $lineNumber => int(14) } } - protected $iLineNo => + protected $lineNumber => int(12) } } - protected $iLineNo => + protected $lineNumber => int(1) } @@ -467,85 +467,85 @@ html, body {font-size: 1.6em;} ```php class Sabberworm\CSS\CSSList\Document#4 (2) { - protected $aContents => + protected $contents => array(1) { [0] => class Sabberworm\CSS\RuleSet\DeclarationBlock#5 (3) { - private $aSelectors => + private $selectors => array(1) { [0] => class Sabberworm\CSS\Property\Selector#6 (2) { - private $sSelector => + private $selector => string(7) "#header" - private $iSpecificity => + private $specificity => NULL } } - private $aRules => + private $rules => array(3) { 'margin' => array(1) { [0] => class Sabberworm\CSS\Rule\Rule#7 (4) { - private $sRule => + private $rule => string(6) "margin" - private $mValue => + private $value => class Sabberworm\CSS\Value\RuleValueList#12 (3) { - protected $aComponents => + protected $components => array(4) { [0] => class Sabberworm\CSS\Value\Size#8 (4) { - private $fSize => + private $size => double(10) - private $sUnit => + private $unit => string(2) "px" - private $bIsColorComponent => + private $isColorComponent => bool(false) - protected $iLineNo => + protected $lineNumber => int(2) } [1] => class Sabberworm\CSS\Value\Size#9 (4) { - private $fSize => + private $size => double(2) - private $sUnit => + private $unit => string(2) "em" - private $bIsColorComponent => + private $isColorComponent => bool(false) - protected $iLineNo => + protected $lineNumber => int(2) } [2] => class Sabberworm\CSS\Value\Size#10 (4) { - private $fSize => + private $size => double(1) - private $sUnit => + private $unit => string(2) "cm" - private $bIsColorComponent => + private $isColorComponent => bool(false) - protected $iLineNo => + protected $lineNumber => int(2) } [3] => class Sabberworm\CSS\Value\Size#11 (4) { - private $fSize => + private $size => double(2) - private $sUnit => + private $unit => string(1) "%" - private $bIsColorComponent => + private $isColorComponent => bool(false) - protected $iLineNo => + protected $lineNumber => int(2) } } - protected $sSeparator => + protected $separator => string(1) " " - protected $iLineNo => + protected $lineNumber => int(2) } - private $bIsImportant => + private $isImportant => bool(false) - protected $iLineNo => + protected $lineNumber => int(2) } } @@ -553,11 +553,11 @@ class Sabberworm\CSS\CSSList\Document#4 (2) { array(1) { [0] => class Sabberworm\CSS\Rule\Rule#13 (4) { - private $sRule => + private $rule => string(11) "font-family" - private $mValue => + private $value => class Sabberworm\CSS\Value\RuleValueList#15 (3) { - protected $aComponents => + protected $components => array(4) { [0] => string(7) "Verdana" @@ -565,9 +565,9 @@ class Sabberworm\CSS\CSSList\Document#4 (2) { string(9) "Helvetica" [2] => class Sabberworm\CSS\Value\CSSString#14 (2) { - private $sString => + private $string => string(9) "Gill Sans" - protected $iLineNo => + protected $lineNumber => int(3) } [3] => @@ -575,12 +575,12 @@ class Sabberworm\CSS\CSSList\Document#4 (2) { } protected $sSeparator => string(1) "," - protected $iLineNo => + protected $lineNumber => int(3) } - private $bIsImportant => + private $isImportant => bool(false) - protected $iLineNo => + protected $lineNumber => int(3) } } @@ -588,22 +588,22 @@ class Sabberworm\CSS\CSSList\Document#4 (2) { array(1) { [0] => class Sabberworm\CSS\Rule\Rule#16 (4) { - private $sRule => + private $rule => string(5) "color" - private $mValue => + private $value => string(3) "red" - private $bIsImportant => + private $isImportant => bool(true) - protected $iLineNo => + protected $lineNumber => int(4) } } } - protected $iLineNo => + protected $lineNumber => int(1) } } - protected $iLineNo => + protected $lineNumber => int(1) } @@ -756,28 +756,28 @@ classDiagram %% end of the generated part - Anchor --> "1" ParserState : oParserState - CSSList --> "*" CSSList : aContents - CSSList --> "*" Charset : aContents - CSSList --> "*" Comment : aComments - CSSList --> "*" Import : aContents - CSSList --> "*" RuleSet : aContents - CSSNamespace --> "*" Comment : aComments - Charset --> "*" Comment : aComments - Charset --> "1" CSSString : oCharset - DeclarationBlock --> "*" Selector : aSelectors - Import --> "*" Comment : aComments - OutputFormat --> "1" OutputFormat : oNextLevelFormat - OutputFormat --> "1" OutputFormatter : oFormatter - OutputFormatter --> "1" OutputFormat : oFormat - Parser --> "1" ParserState : oParserState - ParserState --> "1" Settings : oParserSettings - Rule --> "*" Comment : aComments - Rule --> "1" RuleValueList : mValue - RuleSet --> "*" Comment : aComments - RuleSet --> "*" Rule : aRules - URL --> "1" CSSString : oURL - ValueList --> "*" Value : aComponents + Anchor --> "1" ParserState : parserState + CSSList --> "*" CSSList : contents + CSSList --> "*" Charset : contents + CSSList --> "*" Comment : comments + CSSList --> "*" Import : contents + CSSList --> "*" RuleSet : contents + CSSNamespace --> "*" Comment : comments + Charset --> "*" Comment : comments + Charset --> "1" CSSString : charset + DeclarationBlock --> "*" Selector : selectors + Import --> "*" Comment : comments + OutputFormat --> "1" OutputFormat : nextLevelFormat + OutputFormat --> "1" OutputFormatter : outputFormatter + OutputFormatter --> "1" OutputFormat : outputFormat + Parser --> "1" ParserState : parserState + ParserState --> "1" Settings : parserSettings + Rule --> "*" Comment : comments + Rule --> "1" RuleValueList : value + RuleSet --> "*" Comment : comments + RuleSet --> "*" Rule : rules + URL --> "1" CSSString : url + ValueList --> "*" Value : components ``` ## API and deprecation policy From 831f3d94f080883e552d483b16779e1f315bb78a Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 12 Mar 2025 17:34:41 +0100 Subject: [PATCH 373/555] [CLEANUP] Avoid magic method forwarding in `KeyFrame` (#1158) Part of #1147 --- config/phpstan-baseline.neon | 12 ------------ src/CSSList/KeyFrame.php | 5 +++-- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 00d29d04..01b5d708 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -42,18 +42,6 @@ parameters: count: 1 path: ../src/CSSList/Document.php - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:comments\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/CSSList/KeyFrame.php - - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceBeforeOpeningBrace\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/CSSList/KeyFrame.php - - message: '#^Loose comparison via "\=\=" is not allowed\.$#' identifier: equal.notAllowed diff --git a/src/CSSList/KeyFrame.php b/src/CSSList/KeyFrame.php index 40e0823d..12d79804 100644 --- a/src/CSSList/KeyFrame.php +++ b/src/CSSList/KeyFrame.php @@ -61,8 +61,9 @@ public function __toString(): string public function render(OutputFormat $outputFormat): string { - $result = $outputFormat->comments($this); - $result .= "@{$this->vendorKeyFrame} {$this->animationName}{$outputFormat->spaceBeforeOpeningBrace()}{"; + $formatter = $outputFormat->getFormatter(); + $result = $formatter->comments($this); + $result .= "@{$this->vendorKeyFrame} {$this->animationName}{$formatter->spaceBeforeOpeningBrace()}{"; $result .= $this->renderListContents($outputFormat); $result .= '}'; return $result; From fdd1a655565e789e2025c2b1e53cd49f5f3589bd Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 12 Mar 2025 18:48:43 +0100 Subject: [PATCH 374/555] [CLEANUP] Avoid magic method forwarding in `RuleSet` (#1159) Part of #1147 --- config/phpstan-baseline.neon | 30 ------------------------------ src/RuleSet/RuleSet.php | 12 +++++++----- 2 files changed, 7 insertions(+), 35 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 01b5d708..b527ad4b 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -156,36 +156,6 @@ parameters: count: 1 path: ../src/RuleSet/DeclarationBlock.php - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:removeLastSemicolon\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/RuleSet/RuleSet.php - - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:safely\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/RuleSet/RuleSet.php - - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceAfterRules\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/RuleSet/RuleSet.php - - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceBeforeRules\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/RuleSet/RuleSet.php - - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceBetweenRules\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/RuleSet/RuleSet.php - - message: '#^Only booleans are allowed in a negated boolean, string\|null given\.$#' identifier: booleanNot.exprNotBoolean diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 5da5e401..a972dfd5 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -278,7 +278,8 @@ protected function renderRules(OutputFormat $outputFormat) $isFirst = true; $nextLevelFormat = $outputFormat->nextLevel(); foreach ($this->getRules() as $rule) { - $renderedRule = $nextLevelFormat->safely(static function () use ($rule, $nextLevelFormat): string { + $nextLevelFormatter = $nextLevelFormat->getFormatter(); + $renderedRule = $nextLevelFormatter->safely(static function () use ($rule, $nextLevelFormat): string { return $rule->render($nextLevelFormat); }); if ($renderedRule === null) { @@ -286,19 +287,20 @@ protected function renderRules(OutputFormat $outputFormat) } if ($isFirst) { $isFirst = false; - $result .= $nextLevelFormat->spaceBeforeRules(); + $result .= $nextLevelFormatter->spaceBeforeRules(); } else { - $result .= $nextLevelFormat->spaceBetweenRules(); + $result .= $nextLevelFormatter->spaceBetweenRules(); } $result .= $renderedRule; } + $formatter = $outputFormat->getFormatter(); if (!$isFirst) { // Had some output - $result .= $outputFormat->spaceAfterRules(); + $result .= $formatter->spaceAfterRules(); } - return $outputFormat->removeLastSemicolon($result); + return $formatter->removeLastSemicolon($result); } /** From 8d66357cf580ab5df0d3e1356e9c27ee315ba3de Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 12 Mar 2025 18:53:17 +0100 Subject: [PATCH 375/555] [CLEANUP] Avoid magic method forwarding in `Charset` (#1160) Part of #1147 --- config/phpstan-baseline.neon | 6 ------ src/Property/Charset.php | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index b527ad4b..29a0e5c7 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -66,12 +66,6 @@ parameters: count: 1 path: ../src/Property/CSSNamespace.php - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:comments\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/Property/Charset.php - - message: '#^Method Sabberworm\\CSS\\Property\\Charset\:\:atRuleArgs\(\) should return string but returns Sabberworm\\CSS\\Value\\CSSString\.$#' identifier: return.type diff --git a/src/Property/Charset.php b/src/Property/Charset.php index 97510a1c..61d4fbc9 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -81,7 +81,7 @@ public function __toString(): string public function render(OutputFormat $outputFormat): string { - return "{$outputFormat->comments($this)}@charset {$this->charset->render($outputFormat)};"; + return "{$outputFormat->getFormatter()->comments($this)}@charset {$this->charset->render($outputFormat)};"; } /** From 34ebb96c254963101d8ed5eb74bc9873f6251999 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 12 Mar 2025 18:58:06 +0100 Subject: [PATCH 376/555] [CLEANUP] Avoid magic method forwarding in `DeclarationBlock` (#1161) Part of #1147 --- config/phpstan-baseline.neon | 30 ------------------------------ src/RuleSet/DeclarationBlock.php | 10 ++++++---- 2 files changed, 6 insertions(+), 34 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 29a0e5c7..f5920d63 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -102,36 +102,6 @@ parameters: count: 1 path: ../src/RuleSet/AtRuleSet.php - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:comments\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/RuleSet/DeclarationBlock.php - - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:implode\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/RuleSet/DeclarationBlock.php - - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceAfterSelectorSeparator\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/RuleSet/DeclarationBlock.php - - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceBeforeOpeningBrace\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/RuleSet/DeclarationBlock.php - - - - message: '#^Call to an undefined method Sabberworm\\CSS\\OutputFormat\:\:spaceBeforeSelectorSeparator\(\)\.$#' - identifier: method.notFound - count: 1 - path: ../src/RuleSet/DeclarationBlock.php - - message: '#^Loose comparison via "\!\=" is not allowed\.$#' identifier: notEqual.notAllowed diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 4bd45d68..29516764 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -155,21 +155,23 @@ public function __toString(): string */ public function render(OutputFormat $outputFormat): string { - $result = $outputFormat->comments($this); + $formatter = $outputFormat->getFormatter(); + $result = $formatter->comments($this); if (\count($this->selectors) === 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->lineNumber); } $result .= $outputFormat->getContentBeforeDeclarationBlock(); - $result .= $outputFormat->implode( - $outputFormat->spaceBeforeSelectorSeparator() . ',' . $outputFormat->spaceAfterSelectorSeparator(), + $result .= $formatter->implode( + $formatter->spaceBeforeSelectorSeparator() . ',' . $formatter->spaceAfterSelectorSeparator(), $this->selectors ); $result .= $outputFormat->getContentAfterDeclarationBlockSelectors(); - $result .= $outputFormat->spaceBeforeOpeningBrace() . '{'; + $result .= $formatter->spaceBeforeOpeningBrace() . '{'; $result .= $this->renderRules($outputFormat); $result .= '}'; $result .= $outputFormat->getContentAfterDeclarationBlock(); + return $result; } } From a05cb3b36a8009d35ed1c23388d440392d1cf2ac Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 13 Mar 2025 17:30:58 +0100 Subject: [PATCH 377/555] [TASK] Add native type declarations in `Size` (#1162) Part of #811 --- CHANGELOG.md | 3 ++- src/Value/Size.php | 40 ++++++++++++---------------------------- 2 files changed, 14 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c17a928..716779c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,7 +33,8 @@ Please also have a look at our - Make all non-private properties `@internal` (#886) - Use more native type declarations and strict mode (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933, #958, - #964, #967, #1000, #1044, #1134, #1136, #1137, #1139, #1140, #1141, #1145) + #964, #967, #1000, #1044, #1134, #1136, #1137, #1139, #1140, #1141, #1145, + #1162) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/src/Value/Size.php b/src/Value/Size.php index 9939160b..bbf91eb5 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -17,7 +17,7 @@ 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 + * @var list */ private const ABSOLUTE_SIZE_UNITS = [ 'px', @@ -38,17 +38,17 @@ class Size extends PrimitiveValue ]; /** - * @var array + * @var list */ private const RELATIVE_SIZE_UNITS = ['%', 'em', 'ex', 'ch', 'fr']; /** - * @var array + * @var list */ private const NON_SIZE_UNITS = ['deg', 'grad', 'rad', 's', 'ms', 'turn', 'Hz', 'kHz']; /** - * @var array>|null + * @var array, array>|null */ private static $SIZE_UNITS = null; @@ -69,11 +69,9 @@ class Size extends PrimitiveValue /** * @param float|int|string $size - * @param string|null $unit - * @param bool $isColorComponent * @param int<0, max> $lineNumber */ - public function __construct($size, $unit = null, $isColorComponent = false, int $lineNumber = 0) + public function __construct($size, ?string $unit = null, bool $isColorComponent = false, int $lineNumber = 0) { parent::__construct($lineNumber); $this->size = (float) $size; @@ -82,14 +80,12 @@ public function __construct($size, $unit = null, $isColorComponent = false, int } /** - * @param bool $isColorComponent - * * @throws UnexpectedEOFException * @throws UnexpectedTokenException * * @internal since V8.8.0 */ - public static function parse(ParserState $parserState, $isColorComponent = false): Size + public static function parse(ParserState $parserState, bool $isColorComponent = false): Size { $size = ''; if ($parserState->comes('-')) { @@ -125,9 +121,9 @@ public static function parse(ParserState $parserState, $isColorComponent = false } /** - * @return array> + * @return array, array> */ - private static function getSizeUnits() + private static function getSizeUnits(): array { if (!\is_array(self::$SIZE_UNITS)) { self::$SIZE_UNITS = []; @@ -146,18 +142,12 @@ private static function getSizeUnits() return self::$SIZE_UNITS; } - /** - * @param string $unit - */ - public function setUnit($unit): void + public function setUnit(string $unit): void { $this->unit = $unit; } - /** - * @return string|null - */ - public function getUnit() + public function getUnit(): ?string { return $this->unit; } @@ -170,18 +160,12 @@ public function setSize($size): void $this->size = (float) $size; } - /** - * @return float - */ - public function getSize() + public function getSize(): float { return $this->size; } - /** - * @return bool - */ - public function isColorComponent() + public function isColorComponent(): bool { return $this->isColorComponent; } From 7e688fc248b6196a68d7dcc9cec2cebe8f1a5f85 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 13 Mar 2025 17:36:24 +0100 Subject: [PATCH 378/555] [TASK] Add more unit tests for `Size` (#1165) Part of #757 --- tests/Unit/Value/SizeTest.php | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/tests/Unit/Value/SizeTest.php b/tests/Unit/Value/SizeTest.php index 51da2838..c80f788c 100644 --- a/tests/Unit/Value/SizeTest.php +++ b/tests/Unit/Value/SizeTest.php @@ -7,15 +7,39 @@ use PHPUnit\Framework\TestCase; use Sabberworm\CSS\Parsing\ParserState; use Sabberworm\CSS\Settings; +use Sabberworm\CSS\Value\PrimitiveValue; use Sabberworm\CSS\Value\Size; +use Sabberworm\CSS\Value\Value; /** + * @covers \Sabberworm\CSS\Value\PrimitiveValue * @covers \Sabberworm\CSS\Value\Size + * @covers \Sabberworm\CSS\Value\Value */ final class SizeTest extends TestCase { /** - * @return array + * @test + */ + public function isPrimitiveValue(): void + { + $subject = new Size(1); + + self::assertInstanceOf(PrimitiveValue::class, $subject); + } + + /** + * @test + */ + public function isValue(): void + { + $subject = new Size(1); + + self::assertInstanceOf(Value::class, $subject); + } + + /** + * @return array */ public static function provideUnit(): array { @@ -64,12 +88,14 @@ static function (string $unit): array { /** * @test * + * @param non-empty-string $unit + * * @dataProvider provideUnit */ public function parsesUnit(string $unit): void { - $subject = Size::parse(new ParserState('1' . $unit, Settings::create())); + $parsedSize = Size::parse(new ParserState('1' . $unit, Settings::create())); - self::assertSame($unit, $subject->getUnit()); + self::assertSame($unit, $parsedSize->getUnit()); } } From 6a09851fae6b97810b5319627b90ce6d88766b20 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 13 Mar 2025 18:14:38 +0100 Subject: [PATCH 379/555] [TASK] Add some basic unit tests for `URL` (#1164) Part of #757 Co-authored-by: JakeQZ --- tests/Unit/Value/URLTest.php | 84 ++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 tests/Unit/Value/URLTest.php diff --git a/tests/Unit/Value/URLTest.php b/tests/Unit/Value/URLTest.php new file mode 100644 index 00000000..42d96e29 --- /dev/null +++ b/tests/Unit/Value/URLTest.php @@ -0,0 +1,84 @@ +getURL()); + } + + /** + * @test + */ + public function setUrlReplacesUrl(): void + { + $subject = new URL(new CSSString('http://example.com')); + + $newUrl = new CSSString('http://example.org'); + $subject->setURL($newUrl); + + self::assertSame($newUrl, $subject->getURL()); + } + + /** + * @test + */ + public function getLineNoByDefaultReturnsZero(): void + { + $subject = new URL(new CSSString('http://example.com')); + + self::assertSame(0, $subject->getLineNo()); + } + + /** + * @test + */ + public function getLineNoReturnsLineNumberProvidedToConstructor(): void + { + $lineNumber = 17; + + $subject = new URL(new CSSString('http://example.com'), $lineNumber); + + self::assertSame($lineNumber, $subject->getLineNo()); + } +} From c226c047a1ef8d1df57e301444ae94e7dc200b2b Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 13 Mar 2025 18:15:21 +0100 Subject: [PATCH 380/555] [TASK] Add native type declarations in `URL` (#1163) Part of #811 --- CHANGELOG.md | 2 +- src/Value/URL.php | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 716779c7..1db80aff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ Please also have a look at our - Use more native type declarations and strict mode (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933, #958, #964, #967, #1000, #1044, #1134, #1136, #1137, #1139, #1140, #1141, #1145, - #1162) + #1162, #1163) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/src/Value/URL.php b/src/Value/URL.php index 5a122ff3..f9bc9e8a 100644 --- a/src/Value/URL.php +++ b/src/Value/URL.php @@ -68,10 +68,7 @@ public function setURL(CSSString $url): void $this->url = $url; } - /** - * @return CSSString - */ - public function getURL() + public function getURL(): CSSString { return $this->url; } From d7c618140d86e3e13326d901a6be8b960e3f74ef Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 13 Mar 2025 19:00:12 +0100 Subject: [PATCH 381/555] [TASK] Add native return type for `render*` methods (#1166) Part of #811 --- CHANGELOG.md | 2 +- src/CSSList/CSSList.php | 5 +---- src/RuleSet/RuleSet.php | 5 +---- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1db80aff..83d7f258 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ Please also have a look at our - Use more native type declarations and strict mode (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933, #958, #964, #967, #1000, #1044, #1134, #1136, #1137, #1139, #1140, #1141, #1145, - #1162, #1163) + #1162, #1163, #1166) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 15a5e87a..04332bc7 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -408,10 +408,7 @@ public function __toString(): string return $this->render(new OutputFormat()); } - /** - * @return string - */ - protected function renderListContents(OutputFormat $outputFormat) + protected function renderListContents(OutputFormat $outputFormat): string { $result = ''; $isFirst = true; diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index a972dfd5..311f06be 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -269,10 +269,7 @@ public function __toString(): string return $this->render(new OutputFormat()); } - /** - * @return string - */ - protected function renderRules(OutputFormat $outputFormat) + protected function renderRules(OutputFormat $outputFormat): string { $result = ''; $isFirst = true; From 38de5564fb5848e0086d0c8203d4e2eaf8d1ba43 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 13 Mar 2025 19:01:09 +0100 Subject: [PATCH 382/555] [TASK] Drop magic method forwarding in `OutputFormat` (#898) --- CHANGELOG.md | 1 + src/OutputFormat.php | 18 ------------------ 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83d7f258..f6bcb805 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ Please also have a look at our ### Removed +- Drop magic method forwarding in `OutputFormat` (#898) - Drop `atRuleArgs()` from the `AtRule` interface (#1141) - Remove `OutputFormat::get()` and `::set()` (#1108, #1110) - Drop special support for vendor prefixes (#1083) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 225dab9c..a0e8b894 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -180,24 +180,6 @@ final class OutputFormat */ private $indentationLevel = 0; - /** - * @param non-empty-string $methodName - * @param array $arguments - * - * @return mixed - * - * @throws \Exception - */ - public function __call(string $methodName, array $arguments) - { - if (\method_exists(OutputFormatter::class, $methodName)) { - // @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(), $methodName], $arguments); - } else { - throw new \Exception('Unknown OutputFormat method called: ' . $methodName); - } - } - /** * @internal */ From b561b72a07d117c5a31137556d0873754524a5b3 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 13 Mar 2025 19:05:09 +0100 Subject: [PATCH 383/555] [CLEANUP] Fix method name casing in a call (#1167) --- config/phpstan-baseline.neon | 6 ------ src/Value/Color.php | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index f5920d63..bc85f357 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -162,12 +162,6 @@ parameters: count: 3 path: ../src/Value/CalcFunction.php - - - message: '#^Call to method Sabberworm\\CSS\\Value\\Color\:\:hasNoneAsComponentValue\(\) with incorrect case\: HasNoneAsComponentValue$#' - identifier: method.nameCase - count: 1 - path: ../src/Value/Color.php - - message: '#^Cannot call method getSize\(\) on Sabberworm\\CSS\\Value\\Value\|string\.$#' identifier: method.nonObject diff --git a/src/Value/Color.php b/src/Value/Color.php index bb8951dd..3f5adbc9 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -317,7 +317,7 @@ private function renderAsHex(): string */ private function shouldRenderInModernSyntax(): bool { - if ($this->HasNoneAsComponentValue()) { + if ($this->hasNoneAsComponentValue()) { return true; } From e0e0dd3ddc1fe79276482cde2c33ab1a4b64770e Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sun, 16 Mar 2025 16:25:25 +0000 Subject: [PATCH 384/555] [BUGFIX] Include comments for all rules in declaration block (#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 #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 f6bcb805..73fed056 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,6 +66,7 @@ Please also have a look at our ### Fixed +- Include comments for all rules in declaration block (#1169) - Render rules in line and column number order (#1059) - Don't render `rgb` colors with percentage values using hex notation (#803) - Parse `@font-face` `src` property as comma-delimited list (#790) diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index 42fef09d..dac05cd1 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -68,14 +68,16 @@ public function __construct($rule, int $lineNumber = 0, $columnNumber = 0) } /** + * @param list $commentsBeforeRule + * * @throws UnexpectedEOFException * @throws UnexpectedTokenException * * @internal since V8.8.0 */ - public static function parse(ParserState $parserState): Rule + public static function parse(ParserState $parserState, array $commentsBeforeRule = []): Rule { - $comments = $parserState->consumeWhiteSpace(); + $comments = \array_merge($commentsBeforeRule, $parserState->consumeWhiteSpace()); $rule = new Rule( $parserState->parseIdentifier(!$parserState->comes('--')), $parserState->currentLine(), @@ -98,8 +100,6 @@ public static function parse(ParserState $parserState): Rule $parserState->consume(';'); } - $parserState->consumeWhiteSpace(); - return $rule; } diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 311f06be..9d5ab411 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -65,11 +65,15 @@ public static function parseRuleSet(ParserState $parserState, RuleSet $ruleSet): while ($parserState->comes(';')) { $parserState->consume(';'); } - while (!$parserState->comes('}')) { + while (true) { + $commentsBeforeRule = $parserState->consumeWhiteSpace(); + if ($parserState->comes('}')) { + break; + } $rule = null; if ($parserState->getSettings()->usesLenientParsing()) { try { - $rule = Rule::parse($parserState); + $rule = Rule::parse($parserState, $commentsBeforeRule); } catch (UnexpectedTokenException $e) { try { $consumedText = $parserState->consumeUntil(["\n", ';', '}'], true); @@ -87,7 +91,7 @@ public static function parseRuleSet(ParserState $parserState, RuleSet $ruleSet): } } } else { - $rule = Rule::parse($parserState); + $rule = Rule::parse($parserState, $commentsBeforeRule); } if ($rule instanceof Rule) { $ruleSet->addRule($rule); diff --git a/tests/ParserTest.php b/tests/ParserTest.php index 47ec1ffa..930f12f7 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -1091,9 +1091,11 @@ public function flatCommentExtractingOneComment(): void { $parser = new Parser('div {/*Find Me!*/left:10px; text-align:left;}'); $document = $parser->parse(); + $contents = $document->getContents(); $divRules = $contents[0]->getRules(); $comments = $divRules[0]->getComments(); + self::assertCount(1, $comments); self::assertSame('Find Me!', $comments[0]->getComment()); } @@ -1101,16 +1103,50 @@ public function flatCommentExtractingOneComment(): void /** * @test */ - public function flatCommentExtractingTwoComments(): void + public function flatCommentExtractingTwoConjoinedCommentsForOneRule(): void { - 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(): void + { + $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(): void + { $parser = new Parser('div {/*Find Me!*/left:10px; /*Find Me Too!*/text-align:left;}'); $document = $parser->parse(); + $contents = $document->getContents(); $divRules = $contents[0]->getRules(); $rule1Comments = $divRules[0]->getComments(); $rule2Comments = $divRules[1]->getComments(); + self::assertCount(1, $rule1Comments); self::assertCount(1, $rule2Comments); self::assertSame('Find Me!', $rule1Comments[0]->getComment()); From b3c1936ea710744e6c1e890b72395ea6163a37a6 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 16 Mar 2025 19:39:13 +0100 Subject: [PATCH 385/555] [CLEANUP] Clean up `ParserState` a bit (#1173) --- config/phpstan-baseline.neon | 12 ---- src/Parsing/ParserState.php | 104 +++++++++++++++++------------------ 2 files changed, 52 insertions(+), 64 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index bc85f357..995022b3 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -42,18 +42,6 @@ parameters: count: 1 path: ../src/CSSList/Document.php - - - message: '#^Loose comparison via "\=\=" is not allowed\.$#' - identifier: equal.notAllowed - count: 1 - path: ../src/Parsing/ParserState.php - - - - message: '#^PHPDoc tag @return with type array\\|void is not subtype of native type array\.$#' - identifier: return.phpDocType - count: 1 - path: ../src/Parsing/ParserState.php - - message: '#^Cannot call method render\(\) on string\.$#' identifier: method.nonObject diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 13319973..996ad5f5 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -129,6 +129,7 @@ public function parseIdentifier(bool $ignoreCase = true): string if ($ignoreCase) { $result = $this->strtolower($result); } + return $result; } @@ -181,11 +182,12 @@ public function parseCharacter(bool $isForIdentifier): ?string } else { return $this->consume(1); } + return null; } /** - * @return array|void + * @return list * * @throws UnexpectedEOFException * @throws UnexpectedTokenException @@ -202,24 +204,27 @@ public function consumeWhiteSpace(): array $comment = $this->consumeComment(); } catch (UnexpectedEOFException $e) { $this->currentPosition = \count($this->characters); - return $comments; + break; } } else { $comment = $this->consumeComment(); } - if ($comment !== false) { + if ($comment instanceof Comment) { $comments[] = $comment; } - } while ($comment !== false); + } while ($comment instanceof Comment); + return $comments; } + /** + * @param non-empty-string $string + */ public function comes(string $string, bool $caseInsensitive = false): bool { $peek = $this->peek(\strlen($string)); - return ($peek == '') - ? false - : $this->streql($peek, $string, $caseInsensitive); + + return ($peek !== '') && $this->streql($peek, $string, $caseInsensitive); } /** @@ -232,6 +237,7 @@ public function peek(int $length = 1, int $offset = 0): string if ($offset >= \count($this->characters)) { return ''; } + return $this->substr($offset, $length); } @@ -254,19 +260,22 @@ public function consume($value = 1): string $this->lineNumber ); } + $this->lineNumber += $numberOfLines; $this->currentPosition += $this->strlen($value); - return $value; + $result = $value; } else { if ($this->currentPosition + $value > \count($this->characters)) { throw new UnexpectedEOFException((string) $value, $this->peek(5), 'count', $this->lineNumber); } + $result = $this->substr($this->currentPosition, $value); $numberOfLines = \substr_count($result, "\n"); $this->lineNumber += $numberOfLines; $this->currentPosition += $value; - return $result; } + + return $result; } /** @@ -279,11 +288,12 @@ public function consume($value = 1): string public function consumeExpression(string $expression, ?int $maximumLength = null): string { $matches = null; - $input = $maximumLength !== null ? $this->peek($maximumLength) : $this->inputLeft(); - if (\preg_match($expression, $input, $matches, PREG_OFFSET_CAPTURE) === 1) { - return $this->consume($matches[0][0]); + $input = ($maximumLength !== null) ? $this->peek($maximumLength) : $this->inputLeft(); + if (\preg_match($expression, $input, $matches, PREG_OFFSET_CAPTURE) !== 1) { + throw new UnexpectedTokenException($expression, $this->peek(5), 'expression', $this->lineNumber); } - throw new UnexpectedTokenException($expression, $this->peek(5), 'expression', $this->lineNumber); + + return $this->consume($matches[0][0]); } /** @@ -291,9 +301,10 @@ public function consumeExpression(string $expression, ?int $maximumLength = null */ public function consumeComment() { - $comment = false; + $lineNumber = $this->lineNumber; + $comment = null; + if ($this->comes('/*')) { - $lineNumber = $this->lineNumber; $this->consume(1); $comment = ''; while (($char = $this->consume(1)) !== '') { @@ -305,12 +316,8 @@ public function consumeComment() } } - if ($comment !== false) { - // We skip the * which was included in the comment. - return new Comment(\substr($comment, 1), $lineNumber); - } - - return $comment; + // We skip the * which was included in the comment. + return \is_string($comment) ? new Comment(\substr($comment, 1), $lineNumber) : false; } public function isEnd(): bool @@ -319,7 +326,7 @@ public function isEnd(): bool } /** - * @param array|string $stopCharacters + * @param list|string $stopCharacters * @param array $comments * * @throws UnexpectedEOFException @@ -346,7 +353,8 @@ public function consumeUntil( return $consumedCharacters; } $consumedCharacters .= $character; - if ($comment = $this->consumeComment()) { + $comment = $this->consumeComment(); + if ($comment instanceof Comment) { $comments[] = $comment; } } @@ -371,11 +379,9 @@ private function inputLeft(): string public function streql(string $string1, string $string2, bool $caseInsensitive = true): bool { - if ($caseInsensitive) { - return $this->strtolower($string1) === $this->strtolower($string2); - } else { - return $string1 === $string2; - } + return $caseInsensitive + ? ($this->strtolower($string1) === $this->strtolower($string2)) + : ($string1 === $string2); } /** @@ -391,11 +397,9 @@ public function backtrack(int $numberOfCharacters): void */ public function strlen(string $string): int { - if ($this->parserSettings->hasMultibyteSupport()) { - return \mb_strlen($string, $this->charset); - } else { - return \strlen($string); - } + return $this->parserSettings->hasMultibyteSupport() + ? \mb_strlen($string, $this->charset) + : \strlen($string); } /** @@ -415,20 +419,22 @@ private function substr(int $offset, int $length): string $offset++; $length--; } + return $result; } + /** + * @return ($string is non-empty-string ? non-empty-string : string) + */ private function strtolower(string $string): string { - if ($this->parserSettings->hasMultibyteSupport()) { - return \mb_strtolower($string, $this->charset); - } else { - return \strtolower($string); - } + return $this->parserSettings->hasMultibyteSupport() + ? \mb_strtolower($string, $this->charset) + : \strtolower($string); } /** - * @return array + * @return list * * @throws SourceException if the charset is UTF-8 and the string contains invalid byte sequences */ @@ -440,33 +446,27 @@ private function strsplit(string $string): array if (!\is_array($result)) { throw new SourceException('`preg_split` failed with error ' . \preg_last_error()); } - return $result; } else { $length = \mb_strlen($string, $this->charset); $result = []; for ($i = 0; $i < $length; ++$i) { $result[] = \mb_substr($string, $i, 1, $this->charset); } - return $result; } } else { - if ($string === '') { - return []; - } else { - return \str_split($string); - } + $result = ($string !== '') ? \str_split($string) : []; } + + return $result; } /** - * @return int|false + * @return int<0, max>|false */ private function strpos(string $haystack, string $needle, int $offset) { - if ($this->parserSettings->hasMultibyteSupport()) { - return \mb_strpos($haystack, $needle, $offset, $this->charset); - } else { - return \strpos($haystack, $needle, $offset); - } + return $this->parserSettings->hasMultibyteSupport() + ? \mb_strpos($haystack, $needle, $offset, $this->charset) + : \strpos($haystack, $needle, $offset); } } From 4889353275fa41192e70b82b852858bc9333137b Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 16 Mar 2025 19:50:36 +0100 Subject: [PATCH 386/555] [TASK] Add native type declarations for `Import` (#1172) Part of #811 --- CHANGELOG.md | 2 +- config/phpstan-baseline.neon | 6 ------ src/Property/Import.php | 27 +++++++++------------------ 3 files changed, 10 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73fed056..83e57bbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ Please also have a look at our - Use more native type declarations and strict mode (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933, #958, #964, #967, #1000, #1044, #1134, #1136, #1137, #1139, #1140, #1141, #1145, - #1162, #1163, #1166) + #1162, #1163, #1166, #1172) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 995022b3..6ea025b6 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -60,12 +60,6 @@ parameters: count: 1 path: ../src/Property/Charset.php - - - message: '#^Only booleans are allowed in an if condition, string given\.$#' - identifier: if.condNotBoolean - count: 1 - path: ../src/Property/Import.php - - message: '#^Only booleans are allowed in an if condition, Sabberworm\\CSS\\Value\\RuleValueList\|string\|null given\.$#' identifier: if.condNotBoolean diff --git a/src/Property/Import.php b/src/Property/Import.php index 19bee125..615b612a 100644 --- a/src/Property/Import.php +++ b/src/Property/Import.php @@ -19,7 +19,7 @@ class Import implements AtRule private $location; /** - * @var string + * @var string|null */ private $mediaQuery; @@ -38,10 +38,9 @@ class Import implements AtRule protected $comments = []; /** - * @param string $mediaQuery * @param int<0, max> $lineNumber */ - public function __construct(URL $location, $mediaQuery, int $lineNumber = 0) + public function __construct(URL $location, ?string $mediaQuery, int $lineNumber = 0) { $this->location = $location; $this->mediaQuery = $mediaQuery; @@ -56,18 +55,12 @@ public function getLineNo(): int return $this->lineNumber; } - /** - * @param URL $location - */ - public function setLocation($location): void + public function setLocation(URL $location): void { $this->location = $location; } - /** - * @return URL - */ - public function getLocation() + public function getLocation(): URL { return $this->location; } @@ -95,14 +88,15 @@ public function atRuleName(): string } /** - * @return array + * @return array{0: URL, 1?: non-empty-string} */ public function atRuleArgs(): array { $result = [$this->location]; - if ($this->mediaQuery) { - \array_push($result, $this->mediaQuery); + if (\is_string($this->mediaQuery) && $this->mediaQuery !== '') { + $result[] = $this->mediaQuery; } + return $result; } @@ -130,10 +124,7 @@ public function setComments(array $comments): void $this->comments = $comments; } - /** - * @return string - */ - public function getMediaQuery() + public function getMediaQuery(): ?string { return $this->mediaQuery; } From 4d965b3aafc1fe9b0b3ba2661ecc0e53e169fec9 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 16 Mar 2025 21:29:54 +0100 Subject: [PATCH 387/555] [BUGFIX] Fix the return type of `Selector::isValid()` (#1174) Fixes #1043 Part of #811 --- CHANGELOG.md | 2 +- src/Property/Selector.php | 9 ++++--- tests/Unit/Property/SelectorTest.php | 40 ++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83e57bbf..fe3133c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ Please also have a look at our - Use more native type declarations and strict mode (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933, #958, #964, #967, #1000, #1044, #1134, #1136, #1137, #1139, #1140, #1141, #1145, - #1162, #1163, #1166, #1172) + #1162, #1163, #1166, #1172, #1174) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/src/Property/Selector.php b/src/Property/Selector.php index f1bbbcef..f8008e44 100644 --- a/src/Property/Selector.php +++ b/src/Property/Selector.php @@ -37,13 +37,14 @@ class Selector implements Renderable private $selector; /** - * @return bool - * * @internal since V8.8.0 */ - public static function isValid(string $selector) + public static function isValid(string $selector): bool { - return \preg_match(static::SELECTOR_VALIDATION_RX, $selector); + // Note: We need to use `static::` here as the constant is overridden in the `KeyframeSelector` class. + $numberOfMatches = \preg_match(static::SELECTOR_VALIDATION_RX, $selector); + + return $numberOfMatches === 1; } public function __construct(string $selector) diff --git a/tests/Unit/Property/SelectorTest.php b/tests/Unit/Property/SelectorTest.php index de888fd5..c2d59b60 100644 --- a/tests/Unit/Property/SelectorTest.php +++ b/tests/Unit/Property/SelectorTest.php @@ -98,4 +98,44 @@ public function getSpecificityReturnsSpecificityOfSelectorLastProvidedViaSetSele self::assertSame($expectedSpecificity, $subject->getSpecificity()); } + + /** + * @test + * + * @dataProvider provideSelectorsAndSpecificities + */ + public function isValidForValidSelectorReturnsTrue(string $selector): void + { + self::assertTrue(Selector::isValid($selector)); + } + + /** + * @return array + */ + public static function provideInvalidSelectors(): array + { + return [ + // This is currently broken. + // 'empty string' => [''], + 'percent sign' => ['%'], + // This is currently broken. + // 'hash only' => ['#'], + // This is currently broken. + // 'dot only' => ['.'], + 'slash' => ['/'], + 'less-than sign' => ['<'], + // This is currently broken. + // 'whitespace only' => [" \t\n\r"], + ]; + } + + /** + * @test + * + * @dataProvider provideInvalidSelectors + */ + public function isValidForInvalidSelectorReturnsFalse(string $selector): void + { + self::assertFalse(Selector::isValid($selector)); + } } From dc3b9cb3d9f3aa300e6ebc52e0076a39bee671dd Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 16 Mar 2025 21:34:06 +0100 Subject: [PATCH 388/555] [CLEANUP] Make `Commentable` type annotations more specific (#1171) --- config/phpstan-baseline.neon | 12 ------------ src/CSSList/CSSList.php | 8 ++++---- src/Comment/Commentable.php | 6 +++--- src/Property/CSSNamespace.php | 8 ++++---- src/Property/Charset.php | 8 ++++---- src/Property/Import.php | 8 ++++---- src/Rule/Rule.php | 8 ++++---- src/RuleSet/RuleSet.php | 8 ++++---- 8 files changed, 27 insertions(+), 39 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 6ea025b6..e664bf72 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -108,18 +108,6 @@ parameters: count: 2 path: ../src/RuleSet/RuleSet.php - - - message: '#^Parameter \#1 \$comments \(array\\) of method Sabberworm\\CSS\\RuleSet\\RuleSet\:\:addComments\(\) should be contravariant with parameter \$comments \(array\\) of method Sabberworm\\CSS\\Comment\\Commentable\:\:addComments\(\)$#' - identifier: method.childParameterType - count: 1 - path: ../src/RuleSet/RuleSet.php - - - - message: '#^Parameter \#1 \$comments \(array\\) of method Sabberworm\\CSS\\RuleSet\\RuleSet\:\:setComments\(\) should be contravariant with parameter \$comments \(array\\) of method Sabberworm\\CSS\\Comment\\Commentable\:\:setComments\(\)$#' - identifier: method.childParameterType - count: 1 - path: ../src/RuleSet/RuleSet.php - - message: '#^Parameters should have "Sabberworm\\CSS\\Rule\\Rule" types as the only types passed to this method$#' identifier: typePerfect.narrowPublicClassMethodParamType diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 04332bc7..c4d98f72 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -34,7 +34,7 @@ abstract class CSSList implements Renderable, Commentable { /** - * @var array + * @var list * * @internal since 8.8.0 */ @@ -460,7 +460,7 @@ public function getContents() } /** - * @param array $comments + * @param list $comments */ public function addComments(array $comments): void { @@ -468,7 +468,7 @@ public function addComments(array $comments): void } /** - * @return array + * @return list */ public function getComments(): array { @@ -476,7 +476,7 @@ public function getComments(): array } /** - * @param array $comments + * @param list $comments */ public function setComments(array $comments): void { diff --git a/src/Comment/Commentable.php b/src/Comment/Commentable.php index 5e0fba97..e6eb6a0b 100644 --- a/src/Comment/Commentable.php +++ b/src/Comment/Commentable.php @@ -7,17 +7,17 @@ interface Commentable { /** - * @param array $comments + * @param list $comments */ public function addComments(array $comments): void; /** - * @return array + * @return list */ public function getComments(): array; /** - * @param array $comments + * @param list $comments */ public function setComments(array $comments): void; } diff --git a/src/Property/CSSNamespace.php b/src/Property/CSSNamespace.php index 32a0419d..78989d8a 100644 --- a/src/Property/CSSNamespace.php +++ b/src/Property/CSSNamespace.php @@ -28,7 +28,7 @@ class CSSNamespace implements AtRule private $lineNumber; /** - * @var array + * @var list * * @internal since 8.8.0 */ @@ -121,7 +121,7 @@ public function atRuleArgs(): array } /** - * @param array $comments + * @param list $comments */ public function addComments(array $comments): void { @@ -129,7 +129,7 @@ public function addComments(array $comments): void } /** - * @return array + * @return list */ public function getComments(): array { @@ -137,7 +137,7 @@ public function getComments(): array } /** - * @param array $comments + * @param list $comments */ public function setComments(array $comments): void { diff --git a/src/Property/Charset.php b/src/Property/Charset.php index 61d4fbc9..15bf44d6 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -31,7 +31,7 @@ class Charset implements AtRule protected $lineNumber; /** - * @var array + * @var list * * @internal since 8.8.0 */ @@ -101,7 +101,7 @@ public function atRuleArgs() } /** - * @param array $comments + * @param list $comments */ public function addComments(array $comments): void { @@ -109,7 +109,7 @@ public function addComments(array $comments): void } /** - * @return array + * @return list */ public function getComments(): array { @@ -117,7 +117,7 @@ public function getComments(): array } /** - * @param array $comments + * @param list $comments */ public function setComments(array $comments): void { diff --git a/src/Property/Import.php b/src/Property/Import.php index 615b612a..dfa11ae5 100644 --- a/src/Property/Import.php +++ b/src/Property/Import.php @@ -31,7 +31,7 @@ class Import implements AtRule protected $lineNumber; /** - * @var array + * @var list * * @internal since 8.8.0 */ @@ -101,7 +101,7 @@ public function atRuleArgs(): array } /** - * @param array $comments + * @param list $comments */ public function addComments(array $comments): void { @@ -109,7 +109,7 @@ public function addComments(array $comments): void } /** - * @return array + * @return list */ public function getComments(): array { @@ -117,7 +117,7 @@ public function getComments(): array } /** - * @param array $comments + * @param list $comments */ public function setComments(array $comments): void { diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index dac05cd1..a20e864d 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -49,7 +49,7 @@ class Rule implements Renderable, Commentable protected $columnNumber; /** - * @var array + * @var list * * @internal since 8.8.0 */ @@ -249,7 +249,7 @@ public function render(OutputFormat $outputFormat): string } /** - * @param array $comments + * @param list $comments */ public function addComments(array $comments): void { @@ -257,7 +257,7 @@ public function addComments(array $comments): void } /** - * @return array + * @return list */ public function getComments(): array { @@ -265,7 +265,7 @@ public function getComments(): array } /** - * @param array $comments + * @param list $comments */ public function setComments(array $comments): void { diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 9d5ab411..2c57116f 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -40,7 +40,7 @@ abstract class RuleSet implements Renderable, Commentable protected $lineNumber; /** - * @var array + * @var list * * @internal since 8.8.0 */ @@ -305,7 +305,7 @@ protected function renderRules(OutputFormat $outputFormat): string } /** - * @param array $comments + * @param list $comments */ public function addComments(array $comments): void { @@ -313,7 +313,7 @@ public function addComments(array $comments): void } /** - * @return array + * @return list */ public function getComments(): array { @@ -321,7 +321,7 @@ public function getComments(): array } /** - * @param array $comments + * @param list $comments */ public function setComments(array $comments): void { From 4312c56fcc5a04e06eff03cdff96b918c289c3ce Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 16 Mar 2025 21:42:58 +0100 Subject: [PATCH 389/555] [TASK] Add native type declarations for `Charset` (#1178) Part of #811 --- CHANGELOG.md | 2 +- config/phpstan-baseline.neon | 6 ------ src/Property/Charset.php | 10 ++-------- 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe3133c2..a53d2ca5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ Please also have a look at our - Use more native type declarations and strict mode (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933, #958, #964, #967, #1000, #1044, #1134, #1136, #1137, #1139, #1140, #1141, #1145, - #1162, #1163, #1166, #1172, #1174) + #1162, #1163, #1166, #1172, #1174, #1178) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index e664bf72..a8bd7388 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -54,12 +54,6 @@ parameters: count: 1 path: ../src/Property/CSSNamespace.php - - - message: '#^Method Sabberworm\\CSS\\Property\\Charset\:\:atRuleArgs\(\) should return string but returns Sabberworm\\CSS\\Value\\CSSString\.$#' - identifier: return.type - count: 1 - path: ../src/Property/Charset.php - - message: '#^Only booleans are allowed in an if condition, Sabberworm\\CSS\\Value\\RuleValueList\|string\|null given\.$#' identifier: if.condNotBoolean diff --git a/src/Property/Charset.php b/src/Property/Charset.php index 15bf44d6..dd9dfad8 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -63,10 +63,7 @@ public function setCharset($charset): void $this->charset = $charset; } - /** - * @return string - */ - public function getCharset() + public function getCharset(): string { return $this->charset->getString(); } @@ -92,10 +89,7 @@ public function atRuleName(): string return 'charset'; } - /** - * @return string - */ - public function atRuleArgs() + public function atRuleArgs(): CSSString { return $this->charset; } From bdebc0bf6d4ed44542d6f2fef82ed3a9b0b256cc Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 16 Mar 2025 23:23:13 +0100 Subject: [PATCH 390/555] [TASK] Add native type declarations for `CSSString` (#1179) Part of #811 --- CHANGELOG.md | 2 +- config/phpstan-baseline.neon | 6 ------ src/Value/CSSString.php | 13 +++---------- 3 files changed, 4 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a53d2ca5..bc1ec091 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ Please also have a look at our - Use more native type declarations and strict mode (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933, #958, #964, #967, #1000, #1044, #1134, #1136, #1137, #1139, #1140, #1141, #1145, - #1162, #1163, #1166, #1172, #1174, #1178) + #1162, #1163, #1166, #1172, #1174, #1178, #1179) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index a8bd7388..89348ca3 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -114,12 +114,6 @@ parameters: count: 1 path: ../src/RuleSet/RuleSet.php - - - message: '#^Parameters should have "string" types as the only types passed to this method$#' - identifier: typePerfect.narrowPublicClassMethodParamType - count: 1 - path: ../src/Value/CSSString.php - - message: '#^Loose comparison via "\!\=" is not allowed\.$#' identifier: notEqual.notAllowed diff --git a/src/Value/CSSString.php b/src/Value/CSSString.php index 8b3064bd..7f352502 100644 --- a/src/Value/CSSString.php +++ b/src/Value/CSSString.php @@ -23,10 +23,9 @@ class CSSString extends PrimitiveValue private $string; /** - * @param string $string * @param int<0, max> $lineNumber */ - public function __construct($string, int $lineNumber = 0) + public function __construct(string $string, int $lineNumber = 0) { $this->string = $string; parent::__construct($lineNumber); @@ -74,18 +73,12 @@ public static function parse(ParserState $parserState): CSSString return new CSSString($result, $parserState->currentLine()); } - /** - * @param string $string - */ - public function setString($string): void + public function setString(string $string): void { $this->string = $string; } - /** - * @return string - */ - public function getString() + public function getString(): string { return $this->string; } From f9609c4976cd0be1b58ed1739c276902c7c560c5 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 16 Mar 2025 23:28:04 +0100 Subject: [PATCH 391/555] [TASK] Avoid the deprecated `__toString()` in tests (#1180) Moving some tests to functional tests and splitting them up will come in later changes for #1057. --- tests/Unit/Value/ColorTest.php | 3 ++- tests/Unit/Value/ValueTest.php | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/tests/Unit/Value/ColorTest.php b/tests/Unit/Value/ColorTest.php index f516d313..aaa25755 100644 --- a/tests/Unit/Value/ColorTest.php +++ b/tests/Unit/Value/ColorTest.php @@ -5,6 +5,7 @@ namespace Sabberworm\CSS\Tests\Unit\Value; use PHPUnit\Framework\TestCase; +use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Parsing\ParserState; use Sabberworm\CSS\Parsing\SourceException; use Sabberworm\CSS\Settings; @@ -346,7 +347,7 @@ public function parsesAndRendersValidColor(string $color, string $expectedRender { $subject = Color::parse(new ParserState($color, Settings::create())); - $renderedResult = (string) $subject; + $renderedResult = $subject->render(OutputFormat::create()); self::assertSame($expectedRendering, $renderedResult); } diff --git a/tests/Unit/Value/ValueTest.php b/tests/Unit/Value/ValueTest.php index d6dd21af..5aa8c83c 100644 --- a/tests/Unit/Value/ValueTest.php +++ b/tests/Unit/Value/ValueTest.php @@ -5,8 +5,10 @@ namespace Sabberworm\CSS\Tests\Unit\Value; use PHPUnit\Framework\TestCase; +use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Parsing\ParserState; use Sabberworm\CSS\Settings; +use Sabberworm\CSS\Value\CSSFunction; use Sabberworm\CSS\Value\Value; /** @@ -48,7 +50,8 @@ public function parsesArithmeticInFunctions(string $operator): void self::DEFAULT_DELIMITERS ); - self::assertSame('max(300px,50vh ' . $operator . ' 10px)', (string) $subject); + self::assertInstanceOf(CSSFunction::class, $subject); + self::assertSame('max(300px,50vh ' . $operator . ' 10px)', $subject->render(OutputFormat::createCompact())); } /** @@ -90,7 +93,11 @@ public function parsesArithmeticWithMultipleOperatorsInFunctions( self::DEFAULT_DELIMITERS ); - self::assertSame(\sprintf($expectedResultTemplate, $expression), (string) $subject); + self::assertInstanceOf(CSSFunction::class, $subject); + self::assertSame( + \sprintf($expectedResultTemplate, $expression), + $subject->render(OutputFormat::createCompact()) + ); } /** @@ -118,6 +125,10 @@ public function parsesArithmeticWithMalformedOperandsInFunctions(string $leftOpe self::DEFAULT_DELIMITERS ); - self::assertSame('max(300px,' . $leftOperand . ' + ' . $rightOperand . ')', (string) $subject); + self::assertInstanceOf(CSSFunction::class, $subject); + self::assertSame( + 'max(300px,' . $leftOperand . ' + ' . $rightOperand . ')', + $subject->render(OutputFormat::createCompact()) + ); } } From c9d37e7e098f256563ccc644c0535e9f1406c6ef Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 17 Mar 2025 01:01:42 +0100 Subject: [PATCH 392/555] [TASK] Remove `__toString()` (#1046) Closes #998 --- CHANGELOG.md | 1 + src/CSSList/AtRuleBlockList.php | 8 ------ src/CSSList/CSSList.php | 8 ------ src/CSSList/KeyFrame.php | 8 ------ src/Comment/Comment.php | 8 ------ src/Property/CSSNamespace.php | 8 ------ src/Property/Charset.php | 8 ------ src/Property/Import.php | 8 ------ src/Property/Selector.php | 8 ------ src/Renderable.php | 5 ---- src/Rule/Rule.php | 8 ------ src/RuleSet/AtRuleSet.php | 8 ------ src/RuleSet/DeclarationBlock.php | 10 ------- src/RuleSet/RuleSet.php | 8 ------ src/Value/CSSFunction.php | 8 ------ src/Value/CSSString.php | 8 ------ src/Value/Color.php | 8 ------ src/Value/LineName.php | 8 ------ src/Value/Size.php | 8 ------ src/Value/URL.php | 8 ------ src/Value/ValueList.php | 8 ------ tests/FunctionalDeprecated/.gitkeep | 0 .../Comment/CommentTest.php | 27 ------------------- .../Property/SelectorTest.php | 25 ----------------- 24 files changed, 1 insertion(+), 211 deletions(-) create mode 100644 tests/FunctionalDeprecated/.gitkeep delete mode 100644 tests/FunctionalDeprecated/Comment/CommentTest.php delete mode 100644 tests/FunctionalDeprecated/Property/SelectorTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index bc1ec091..0c999e26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ Please also have a look at our ### Removed +- Remove `__toString()` (#1046) - Drop magic method forwarding in `OutputFormat` (#898) - Drop `atRuleArgs()` from the `AtRule` interface (#1141) - Remove `OutputFormat::get()` and `::set()` (#1108, #1110) diff --git a/src/CSSList/AtRuleBlockList.php b/src/CSSList/AtRuleBlockList.php index 5d17ee86..57c04ab8 100644 --- a/src/CSSList/AtRuleBlockList.php +++ b/src/CSSList/AtRuleBlockList.php @@ -46,14 +46,6 @@ public function atRuleArgs(): string return $this->arguments; } - /** - * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. - */ - public function __toString(): string - { - return $this->render(new OutputFormat()); - } - public function render(OutputFormat $outputFormat): string { $formatter = $outputFormat->getFormatter(); diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index c4d98f72..67277b44 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -400,14 +400,6 @@ public function removeDeclarationBlockBySelector($selectors, $removeAll = false) } } - /** - * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. - */ - public function __toString(): string - { - return $this->render(new OutputFormat()); - } - protected function renderListContents(OutputFormat $outputFormat): string { $result = ''; diff --git a/src/CSSList/KeyFrame.php b/src/CSSList/KeyFrame.php index 12d79804..f1f96cec 100644 --- a/src/CSSList/KeyFrame.php +++ b/src/CSSList/KeyFrame.php @@ -51,14 +51,6 @@ public function getAnimationName(): string return $this->animationName; } - /** - * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. - */ - public function __toString(): string - { - return $this->render(new OutputFormat()); - } - public function render(OutputFormat $outputFormat): string { $formatter = $outputFormat->getFormatter(); diff --git a/src/Comment/Comment.php b/src/Comment/Comment.php index 761d6d82..e96bd28e 100644 --- a/src/Comment/Comment.php +++ b/src/Comment/Comment.php @@ -50,14 +50,6 @@ public function setComment(string $commentText): void $this->commentText = $commentText; } - /** - * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. - */ - public function __toString(): string - { - return $this->render(new OutputFormat()); - } - public function render(OutputFormat $outputFormat): string { return '/*' . $this->commentText . '*/'; diff --git a/src/Property/CSSNamespace.php b/src/Property/CSSNamespace.php index 78989d8a..75336249 100644 --- a/src/Property/CSSNamespace.php +++ b/src/Property/CSSNamespace.php @@ -54,14 +54,6 @@ public function getLineNo(): int return $this->lineNumber; } - /** - * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. - */ - public function __toString(): string - { - return $this->render(new OutputFormat()); - } - public function render(OutputFormat $outputFormat): string { return '@namespace ' . ($this->prefix === null ? '' : $this->prefix . ' ') diff --git a/src/Property/Charset.php b/src/Property/Charset.php index dd9dfad8..14cb9321 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -68,14 +68,6 @@ public function getCharset(): string return $this->charset->getString(); } - /** - * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. - */ - public function __toString(): string - { - return $this->render(new OutputFormat()); - } - public function render(OutputFormat $outputFormat): string { return "{$outputFormat->getFormatter()->comments($this)}@charset {$this->charset->render($outputFormat)};"; diff --git a/src/Property/Import.php b/src/Property/Import.php index dfa11ae5..f889786b 100644 --- a/src/Property/Import.php +++ b/src/Property/Import.php @@ -65,14 +65,6 @@ public function getLocation(): URL return $this->location; } - /** - * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. - */ - public function __toString(): string - { - return $this->render(new OutputFormat()); - } - public function render(OutputFormat $outputFormat): string { return $outputFormat->getFormatter()->comments($this) . '@import ' . $this->location->render($outputFormat) diff --git a/src/Property/Selector.php b/src/Property/Selector.php index f8008e44..180ffeba 100644 --- a/src/Property/Selector.php +++ b/src/Property/Selector.php @@ -62,14 +62,6 @@ public function setSelector(string $selector): void $this->selector = \trim($selector); } - /** - * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. - */ - public function __toString(): string - { - return $this->getSelector(); - } - /** * @return int<0, max> */ diff --git a/src/Renderable.php b/src/Renderable.php index ed633654..9ebf9a9b 100644 --- a/src/Renderable.php +++ b/src/Renderable.php @@ -6,10 +6,5 @@ interface Renderable { - /** - * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. - */ - public function __toString(): string; - public function render(OutputFormat $outputFormat): string; } diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index a20e864d..b84f0996 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -224,14 +224,6 @@ public function getIsImportant() return $this->isImportant; } - /** - * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. - */ - public function __toString(): string - { - return $this->render(new OutputFormat()); - } - public function render(OutputFormat $outputFormat): string { $formatter = $outputFormat->getFormatter(); diff --git a/src/RuleSet/AtRuleSet.php b/src/RuleSet/AtRuleSet.php index a896246b..1e28aa54 100644 --- a/src/RuleSet/AtRuleSet.php +++ b/src/RuleSet/AtRuleSet.php @@ -53,14 +53,6 @@ public function atRuleArgs() return $this->arguments; } - /** - * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. - */ - public function __toString(): string - { - return $this->render(new OutputFormat()); - } - public function render(OutputFormat $outputFormat): string { $formatter = $outputFormat->getFormatter(); diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 29516764..7763f00e 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -140,16 +140,6 @@ public function getSelectors() return $this->selectors; } - /** - * @throws OutputException - * - * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. - */ - public function __toString(): string - { - return $this->render(new OutputFormat()); - } - /** * @throws OutputException */ diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 2c57116f..622f9f16 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -265,14 +265,6 @@ public function removeRule($searchPattern): void } } - /** - * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. - */ - public function __toString(): string - { - return $this->render(new OutputFormat()); - } - protected function renderRules(OutputFormat $outputFormat): string { $result = ''; diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index 3c10900e..5941b4b3 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -105,14 +105,6 @@ public function getArguments() return $this->components; } - /** - * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. - */ - public function __toString(): string - { - return $this->render(new OutputFormat()); - } - public function render(OutputFormat $outputFormat): string { $arguments = parent::render($outputFormat); diff --git a/src/Value/CSSString.php b/src/Value/CSSString.php index 7f352502..a71175ef 100644 --- a/src/Value/CSSString.php +++ b/src/Value/CSSString.php @@ -83,14 +83,6 @@ public function getString(): string return $this->string; } - /** - * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. - */ - public function __toString(): string - { - return $this->render(new OutputFormat()); - } - public function render(OutputFormat $outputFormat): string { $string = \addslashes($this->string); diff --git a/src/Value/Color.php b/src/Value/Color.php index 3f5adbc9..f6eeaa60 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -224,14 +224,6 @@ public function getColorDescription() return $this->getName(); } - /** - * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. - */ - public function __toString(): string - { - return $this->render(new OutputFormat()); - } - public function render(OutputFormat $outputFormat): string { if ($this->shouldRenderAsHex($outputFormat)) { diff --git a/src/Value/LineName.php b/src/Value/LineName.php index aad8b6bd..9afe6703 100644 --- a/src/Value/LineName.php +++ b/src/Value/LineName.php @@ -49,14 +49,6 @@ public static function parse(ParserState $parserState): LineName return new LineName($names, $parserState->currentLine()); } - /** - * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. - */ - public function __toString(): string - { - return $this->render(new OutputFormat()); - } - public function render(OutputFormat $outputFormat): string { return '[' . parent::render(OutputFormat::createCompact()) . ']'; diff --git a/src/Value/Size.php b/src/Value/Size.php index bbf91eb5..e3cf6b89 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -195,14 +195,6 @@ public function isRelative(): bool return false; } - /** - * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. - */ - public function __toString(): string - { - return $this->render(new OutputFormat()); - } - public function render(OutputFormat $outputFormat): string { $locale = \localeconv(); diff --git a/src/Value/URL.php b/src/Value/URL.php index f9bc9e8a..f1fe258b 100644 --- a/src/Value/URL.php +++ b/src/Value/URL.php @@ -73,14 +73,6 @@ public function getURL(): CSSString return $this->url; } - /** - * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. - */ - public function __toString(): string - { - return $this->render(new OutputFormat()); - } - public function render(OutputFormat $outputFormat): string { return "url({$this->url->render($outputFormat)})"; diff --git a/src/Value/ValueList.php b/src/Value/ValueList.php index 8a50809d..3e57d7fe 100644 --- a/src/Value/ValueList.php +++ b/src/Value/ValueList.php @@ -83,14 +83,6 @@ public function setListSeparator($separator): void $this->separator = $separator; } - /** - * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead. - */ - public function __toString(): string - { - return $this->render(new OutputFormat()); - } - public function render(OutputFormat $outputFormat): string { $formatter = $outputFormat->getFormatter(); diff --git a/tests/FunctionalDeprecated/.gitkeep b/tests/FunctionalDeprecated/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tests/FunctionalDeprecated/Comment/CommentTest.php b/tests/FunctionalDeprecated/Comment/CommentTest.php deleted file mode 100644 index 8d9c1d31..00000000 --- a/tests/FunctionalDeprecated/Comment/CommentTest.php +++ /dev/null @@ -1,27 +0,0 @@ -setComment($comment); - - self::assertSame('/*' . $comment . '*/', (string) $subject); - } -} diff --git a/tests/FunctionalDeprecated/Property/SelectorTest.php b/tests/FunctionalDeprecated/Property/SelectorTest.php deleted file mode 100644 index 917d227f..00000000 --- a/tests/FunctionalDeprecated/Property/SelectorTest.php +++ /dev/null @@ -1,25 +0,0 @@ - Date: Mon, 17 Mar 2025 11:25:00 +0100 Subject: [PATCH 393/555] [TASK] Add native type declarations for `CSSList` (#1181) Part of #811 --- CHANGELOG.md | 2 +- src/CSSList/CSSList.php | 27 ++++++++++++--------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c999e26..f311a7b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ Please also have a look at our - Use more native type declarations and strict mode (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933, #958, #964, #967, #1000, #1044, #1134, #1136, #1137, #1139, #1140, #1141, #1145, - #1162, #1163, #1166, #1172, #1174, #1178, #1179) + #1162, #1163, #1166, #1172, #1174, #1178, #1179, #1181) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 67277b44..36c248cc 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -41,7 +41,7 @@ abstract class CSSList implements Renderable, Commentable protected $comments = []; /** - * @var array + * @var array, RuleSet|CSSList|Import|Charset> * * @internal since 8.8.0 */ @@ -283,11 +283,9 @@ public function append($item): void /** * Splices the list of contents. * - * @param int $offset - * @param int $length * @param array $replacement */ - public function splice($offset, $length = null, $replacement = null): void + public function splice(int $offset, ?int $length = null, ?array $replacement = null): void { \array_splice($this->contents, $offset, $length, $replacement); } @@ -317,13 +315,14 @@ public function insertBefore($item, $sibling): void * * @return bool whether the item was removed */ - public function remove($itemToRemove) + public function remove($itemToRemove): bool { $key = \array_search($itemToRemove, $this->contents, true); if ($key !== false) { unset($this->contents[$key]); return true; } + return false; } @@ -333,10 +332,9 @@ public function remove($itemToRemove) * @param RuleSet|Import|Charset|CSSList $oldItem * May be a `RuleSet` (most likely a `DeclarationBlock`), an `Import`, a `Charset` * or another `CSSList` (most likely a `MediaQuery`) - * - * @return bool + * @param RuleSet|Import|Charset|CSSList|array $newItem */ - public function replace($oldItem, $newItem) + public function replace($oldItem, $newItem): bool { $key = \array_search($oldItem, $this->contents, true); if ($key !== false) { @@ -347,6 +345,7 @@ public function replace($oldItem, $newItem) } return true; } + return false; } @@ -364,10 +363,10 @@ public function setContents(array $contents): void /** * Removes a declaration block from the CSS list if it matches all given selectors. * - * @param DeclarationBlock|array|string $selectors the selectors to match + * @param DeclarationBlock|array|string $selectors the selectors to match * @param bool $removeAll whether to stop at the first declaration block found or remove all blocks */ - public function removeDeclarationBlockBySelector($selectors, $removeAll = false): void + public function removeDeclarationBlockBySelector($selectors, bool $removeAll = false): void { if ($selectors instanceof DeclarationBlock) { $selectors = $selectors->getSelectors(); @@ -436,17 +435,15 @@ protected function renderListContents(OutputFormat $outputFormat): string /** * Return true if the list can not be further outdented. Only important when rendering. - * - * @return bool */ - abstract public function isRootList(); + abstract public function isRootList(): bool; /** * Returns the stored items. * - * @return array + * @return array, RuleSet|Import|Charset|CSSList> */ - public function getContents() + public function getContents(): array { return $this->contents; } From 1226a2f7aad0a98e49b5e1183dbaab416fa23d25 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 17 Mar 2025 22:48:41 +0100 Subject: [PATCH 394/555] [CLEANUP] Fix type annotation of `::getSelectors()` (#1184) Also add a native type declaration. This will need more cleanup and refactoring later on. --- CHANGELOG.md | 2 +- config/phpstan-baseline.neon | 6 ------ src/RuleSet/DeclarationBlock.php | 8 ++++---- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f311a7b2..b8681519 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ Please also have a look at our - Use more native type declarations and strict mode (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933, #958, #964, #967, #1000, #1044, #1134, #1136, #1137, #1139, #1140, #1141, #1145, - #1162, #1163, #1166, #1172, #1174, #1178, #1179, #1181) + #1162, #1163, #1166, #1172, #1174, #1178, #1179, #1181, #1184) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 89348ca3..11effaf5 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -6,12 +6,6 @@ parameters: count: 1 path: ../src/CSSList/AtRuleBlockList.php - - - message: '#^Parameter &\$result by\-ref type of method Sabberworm\\CSS\\CSSList\\CSSBlockList\:\:allSelectors\(\) expects array\, array\ given\.$#' - identifier: parameterByRef.type - count: 2 - path: ../src/CSSList/CSSBlockList.php - - message: '#^Parameter &\$result by\-ref type of method Sabberworm\\CSS\\CSSList\\CSSBlockList\:\:allValues\(\) expects array\, array\ given\.$#' identifier: parameterByRef.type diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 7763f00e..8e299070 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -25,7 +25,7 @@ class DeclarationBlock extends RuleSet { /** - * @var array + * @var array */ private $selectors = []; @@ -76,7 +76,7 @@ public static function parse(ParserState $parserState, $list = null) } /** - * @param array|string $selectors + * @param array|string $selectors * @param CSSList|null $list * * @throws UnexpectedTokenException @@ -133,9 +133,9 @@ public function removeSelector($selectorToRemove): bool } /** - * @return array + * @return array */ - public function getSelectors() + public function getSelectors(): array { return $this->selectors; } From a583a2e04074a26013e26ce41fc5bcbe6faecacd Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 17 Mar 2025 23:09:33 +0100 Subject: [PATCH 395/555] [TASK] Add native type declarations for `CSSBlockList` (#1183) Also add some more type checks to ensure that the corresponding types are actually returned. Part of #811 --- CHANGELOG.md | 2 +- config/phpstan-baseline.neon | 6 ------ src/CSSList/CSSBlockList.php | 19 +++++++------------ 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8681519..f9f1d0de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ Please also have a look at our - Use more native type declarations and strict mode (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933, #958, #964, #967, #1000, #1044, #1134, #1136, #1137, #1139, #1140, #1141, #1145, - #1162, #1163, #1166, #1172, #1174, #1178, #1179, #1181, #1184) + #1162, #1163, #1166, #1172, #1174, #1178, #1179, #1181, #1183, #1184) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 11effaf5..889cc859 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -6,12 +6,6 @@ parameters: count: 1 path: ../src/CSSList/AtRuleBlockList.php - - - message: '#^Parameter &\$result by\-ref type of method Sabberworm\\CSS\\CSSList\\CSSBlockList\:\:allValues\(\) expects array\, array\ given\.$#' - identifier: parameterByRef.type - count: 1 - path: ../src/CSSList/CSSBlockList.php - - message: '#^Loose comparison via "\!\=" is not allowed\.$#' identifier: notEqual.notAllowed diff --git a/src/CSSList/CSSBlockList.php b/src/CSSList/CSSBlockList.php index 217a6276..40a6ff0a 100644 --- a/src/CSSList/CSSBlockList.php +++ b/src/CSSList/CSSBlockList.php @@ -41,7 +41,7 @@ public function getAllDeclarationBlocks(): array } /** - * @param array $result + * @param list $result */ protected function allDeclarationBlocks(array &$result): void { @@ -76,15 +76,13 @@ public function getAllRuleSets(): array /** * @param CSSList|Rule|RuleSet|Value $element - * @param array $result - * @param string|null $searchString - * @param bool $searchInFunctionArguments + * @param list $result */ protected function allValues( $element, array &$result, - $searchString = null, - $searchInFunctionArguments = false + ?string $searchString = null, + bool $searchInFunctionArguments = false ): void { if ($element instanceof CSSBlockList) { foreach ($element->getContents() as $content) { @@ -102,19 +100,16 @@ protected function allValues( $this->allValues($component, $result, $searchString, $searchInFunctionArguments); } } - } else { - // Non-List `Value` or `CSSString` (CSS identifier) + } elseif ($element instanceof Value) { $result[] = $element; } } /** - * @param array $result - * @param string|null $specificitySearch + * @param list $result */ - protected function allSelectors(array &$result, $specificitySearch = null): void + protected function allSelectors(array &$result, ?string $specificitySearch = null): void { - /** @var array $declarationBlocks */ $declarationBlocks = []; $this->allDeclarationBlocks($declarationBlocks); foreach ($declarationBlocks as $declarationBlock) { From f371c22921891ff6c9eaf7feb2a6a9e710daf644 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 17 Mar 2025 23:23:15 +0100 Subject: [PATCH 396/555] [TASK] Add native type declarations for `RuleSet` (#1186) Part of #811 --- CHANGELOG.md | 2 +- src/RuleSet/RuleSet.php | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9f1d0de..e39699b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ Please also have a look at our - Use more native type declarations and strict mode (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933, #958, #964, #967, #1000, #1044, #1134, #1136, #1137, #1139, #1140, #1141, #1145, - #1162, #1163, #1166, #1172, #1174, #1178, #1179, #1181, #1183, #1184) + #1162, #1163, #1166, #1172, #1174, #1178, #1179, #1181, #1183, #1184, #1186) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 622f9f16..1b071cbb 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -151,14 +151,13 @@ public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void * as well as one matching the pattern with the dash excluded. * Passing a `Rule` behaves like calling `getRules($rule->getRule())`. * - * @return array + * @return array, Rule> */ - public function getRules($searchPattern = null) + public function getRules($searchPattern = null): array { if ($searchPattern instanceof Rule) { $searchPattern = $searchPattern->getRule(); } - /** @var array $result */ $result = []; foreach ($this->rules as $propertyName => $rules) { // Either no search rule is given or the search rule matches the found rule exactly @@ -187,7 +186,7 @@ public function getRules($searchPattern = null) /** * Overrides all the rules of this set. * - * @param array $rules The rules to override with. + * @param array $rules The rules to override with. */ public function setRules(array $rules): void { @@ -212,13 +211,14 @@ public function setRules(array $rules): void * * @return array */ - public function getRulesAssoc($searchPattern = null) + public function getRulesAssoc($searchPattern = null): array { /** @var array $result */ $result = []; foreach ($this->getRules($searchPattern) as $rule) { $result[$rule->getRule()] = $rule; } + return $result; } From ff1af18181d1c87ce9118de9c57d48ec4867665e Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 18 Mar 2025 11:32:07 +0100 Subject: [PATCH 397/555] [TASK] Add native type declarations for `CSSNamespace` (#1187) Part of #811 --- CHANGELOG.md | 3 ++- config/phpstan-baseline.neon | 12 ------------ src/Property/CSSNamespace.php | 29 ++++++++++++----------------- 3 files changed, 14 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e39699b8..65250de3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,8 @@ Please also have a look at our - Use more native type declarations and strict mode (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933, #958, #964, #967, #1000, #1044, #1134, #1136, #1137, #1139, #1140, #1141, #1145, - #1162, #1163, #1166, #1172, #1174, #1178, #1179, #1181, #1183, #1184, #1186) + #1162, #1163, #1166, #1172, #1174, #1178, #1179, #1181, #1183, #1184, #1186, + #1187) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 889cc859..722454ba 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -30,18 +30,6 @@ parameters: count: 1 path: ../src/CSSList/Document.php - - - message: '#^Cannot call method render\(\) on string\.$#' - identifier: method.nonObject - count: 1 - path: ../src/Property/CSSNamespace.php - - - - message: '#^Only booleans are allowed in an if condition, string given\.$#' - identifier: if.condNotBoolean - count: 1 - path: ../src/Property/CSSNamespace.php - - message: '#^Only booleans are allowed in an if condition, Sabberworm\\CSS\\Value\\RuleValueList\|string\|null given\.$#' identifier: if.condNotBoolean diff --git a/src/Property/CSSNamespace.php b/src/Property/CSSNamespace.php index 75336249..5b174861 100644 --- a/src/Property/CSSNamespace.php +++ b/src/Property/CSSNamespace.php @@ -6,6 +6,8 @@ use Sabberworm\CSS\Comment\Comment; use Sabberworm\CSS\OutputFormat; +use Sabberworm\CSS\Value\CSSString; +use Sabberworm\CSS\Value\URL; /** * `CSSNamespace` represents an `@namespace` rule. @@ -13,12 +15,12 @@ class CSSNamespace implements AtRule { /** - * @var string + * @var CSSString|URL */ private $url; /** - * @var string + * @var string|null */ private $prefix; @@ -35,11 +37,10 @@ class CSSNamespace implements AtRule protected $comments = []; /** - * @param string $url - * @param string|null $prefix + * @param CSSString|URL $url * @param int<0, max> $lineNumber */ - public function __construct($url, $prefix = null, int $lineNumber = 0) + public function __construct($url, ?string $prefix = null, int $lineNumber = 0) { $this->url = $url; $this->prefix = $prefix; @@ -61,33 +62,27 @@ public function render(OutputFormat $outputFormat): string } /** - * @return string + * @return CSSString|URL */ public function getUrl() { return $this->url; } - /** - * @return string|null - */ - public function getPrefix() + public function getPrefix(): ?string { return $this->prefix; } /** - * @param string $url + * @param CSSString|URL $url */ public function setUrl($url): void { $this->url = $url; } - /** - * @param string $prefix - */ - public function setPrefix($prefix): void + public function setPrefix(string $prefix): void { $this->prefix = $prefix; } @@ -101,12 +96,12 @@ public function atRuleName(): string } /** - * @return array + * @return array{0: CSSString|URL|non-empty-string, 1?: CSSString|URL} */ public function atRuleArgs(): array { $result = [$this->url]; - if ($this->prefix) { + if (\is_string($this->prefix) && $this->prefix !== '') { \array_unshift($result, $this->prefix); } return $result; From e89a90cfe6a520800d2b47cd1649ffd32a3210ae Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 18 Mar 2025 11:41:39 +0100 Subject: [PATCH 398/555] [TASK] Reduce and finetune the scope of `@covers` annotations (#1188) The legacy tests are not very focused. Until we have split them up, try to avoid false positives for code coverage. Also add `@covers` annotations for the parent classes of the tested classes. --- tests/CSSList/AtRuleBlockListTest.php | 2 ++ tests/Comment/CommentTest.php | 4 +--- tests/OutputFormatTest.php | 2 +- tests/ParserTest.php | 10 ---------- tests/RuleSet/DeclarationBlockTest.php | 1 + tests/RuleSet/LenientParsingTest.php | 11 +---------- 6 files changed, 6 insertions(+), 24 deletions(-) diff --git a/tests/CSSList/AtRuleBlockListTest.php b/tests/CSSList/AtRuleBlockListTest.php index 676b908a..2c55d3fb 100644 --- a/tests/CSSList/AtRuleBlockListTest.php +++ b/tests/CSSList/AtRuleBlockListTest.php @@ -10,6 +10,8 @@ /** * @covers \Sabberworm\CSS\CSSList\AtRuleBlockList + * @covers \Sabberworm\CSS\CSSList\CSSBlockList + * @covers \Sabberworm\CSS\CSSList\CSSList */ final class AtRuleBlockListTest extends TestCase { diff --git a/tests/Comment/CommentTest.php b/tests/Comment/CommentTest.php index 7befcc27..15d5983e 100644 --- a/tests/Comment/CommentTest.php +++ b/tests/Comment/CommentTest.php @@ -9,9 +9,7 @@ use Sabberworm\CSS\Tests\ParserTest as TestsParserTest; /** - * @covers \Sabberworm\CSS\Comment\Comment - * @covers \Sabberworm\CSS\OutputFormat - * @covers \Sabberworm\CSS\OutputFormatter + * @coversNothing */ final class CommentTest extends TestCase { diff --git a/tests/OutputFormatTest.php b/tests/OutputFormatTest.php index 3679245d..9852ae58 100644 --- a/tests/OutputFormatTest.php +++ b/tests/OutputFormatTest.php @@ -11,7 +11,7 @@ use Sabberworm\CSS\Parsing\OutputException; /** - * @covers \Sabberworm\CSS\OutputFormat + * @coversNothing */ final class OutputFormatTest extends TestCase { diff --git a/tests/ParserTest.php b/tests/ParserTest.php index 930f12f7..f2608fb3 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -28,17 +28,7 @@ use Sabberworm\CSS\Value\ValueList; /** - * @covers \Sabberworm\CSS\CSSList\Document * @covers \Sabberworm\CSS\Parser - * @covers \Sabberworm\CSS\RuleSet\DeclarationBlock - * @covers \Sabberworm\CSS\Rule\Rule - * @covers \Sabberworm\CSS\Value\CSSString - * @covers \Sabberworm\CSS\Value\CalcFunction - * @covers \Sabberworm\CSS\Value\Color - * @covers \Sabberworm\CSS\Value\LineName - * @covers \Sabberworm\CSS\Value\Size - * @covers \Sabberworm\CSS\Value\URL - * @covers \Sabberworm\CSS\Value\Value */ final class ParserTest extends TestCase { diff --git a/tests/RuleSet/DeclarationBlockTest.php b/tests/RuleSet/DeclarationBlockTest.php index 2f5dc9f2..128048e8 100644 --- a/tests/RuleSet/DeclarationBlockTest.php +++ b/tests/RuleSet/DeclarationBlockTest.php @@ -13,6 +13,7 @@ /** * @covers \Sabberworm\CSS\RuleSet\DeclarationBlock + * @covers \Sabberworm\CSS\RuleSet\RuleSet */ final class DeclarationBlockTest extends TestCase { diff --git a/tests/RuleSet/LenientParsingTest.php b/tests/RuleSet/LenientParsingTest.php index 312bbd8b..c014f021 100644 --- a/tests/RuleSet/LenientParsingTest.php +++ b/tests/RuleSet/LenientParsingTest.php @@ -11,16 +11,7 @@ use Sabberworm\CSS\Settings; /** - * @covers \Sabberworm\CSS\CSSList\Document - * @covers \Sabberworm\CSS\Parser - * @covers \Sabberworm\CSS\RuleSet\DeclarationBlock - * @covers \Sabberworm\CSS\Rule\Rule - * @covers \Sabberworm\CSS\Value\CSSString - * @covers \Sabberworm\CSS\Value\CalcFunction - * @covers \Sabberworm\CSS\Value\Color - * @covers \Sabberworm\CSS\Value\LineName - * @covers \Sabberworm\CSS\Value\Size - * @covers \Sabberworm\CSS\Value\URL + * @coversNothing */ final class LenientParsingTest extends TestCase { From 2db8991197df8e7f81fd7800768e02da73c38663 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 18 Mar 2025 18:45:16 +0100 Subject: [PATCH 399/555] [TASK] Add native type declarations for `Rule` (#1190) Part of #811 --- CHANGELOG.md | 2 +- config/phpstan-baseline.neon | 12 ---------- src/Parsing/ParserState.php | 2 ++ src/Rule/Rule.php | 46 ++++++++++++++++-------------------- 4 files changed, 24 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65250de3..2a663e92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,7 @@ Please also have a look at our (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933, #958, #964, #967, #1000, #1044, #1134, #1136, #1137, #1139, #1140, #1141, #1145, #1162, #1163, #1166, #1172, #1174, #1178, #1179, #1181, #1183, #1184, #1186, - #1187) + #1187, #1190) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 722454ba..bdea7afc 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -36,18 +36,6 @@ parameters: count: 1 path: ../src/Rule/Rule.php - - - message: '#^Parameters should have "bool" types as the only types passed to this method$#' - identifier: typePerfect.narrowPublicClassMethodParamType - count: 1 - path: ../src/Rule/Rule.php - - - - message: '#^Parameters should have "int\|int" types as the only types passed to this method$#' - identifier: typePerfect.narrowPublicClassMethodParamType - count: 1 - path: ../src/Rule/Rule.php - - message: '#^Only booleans are allowed in an if condition, string given\.$#' identifier: if.condNotBoolean diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 996ad5f5..88f533a8 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -107,6 +107,8 @@ public function setPosition(int $position): void } /** + * @return non-empty-string + * * @throws UnexpectedTokenException */ public function parseIdentifier(bool $ignoreCase = true): string diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index b84f0996..fbd1646f 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -22,7 +22,7 @@ class Rule implements Renderable, Commentable { /** - * @var string + * @var non-empty-string */ private $rule; @@ -42,7 +42,7 @@ class Rule implements Renderable, Commentable protected $lineNumber; /** - * @var int + * @var int<0, max> * * @internal since 8.8.0 */ @@ -56,11 +56,11 @@ class Rule implements Renderable, Commentable protected $comments = []; /** - * @param string $rule + * @param non-empty-string $rule * @param int<0, max> $lineNumber - * @param int $columnNumber + * @param int<0, max> $columnNumber */ - public function __construct($rule, int $lineNumber = 0, $columnNumber = 0) + public function __construct(string $rule, int $lineNumber = 0, int $columnNumber = 0) { $this->rule = $rule; $this->lineNumber = $lineNumber; @@ -108,11 +108,11 @@ public static function parse(ParserState $parserState, array $commentsBeforeRule * 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 $rule + * @param non-empty-string $rule * * @return list */ - private static function listDelimiterForRule($rule): array + private static function listDelimiterForRule(string $rule): array { if (\preg_match('/^font($|-)/', $rule)) { return [',', '/', ' ']; @@ -135,35 +135,35 @@ public function getLineNo(): int } /** - * @return int + * @return int<0, max> */ - public function getColNo() + public function getColNo(): int { return $this->columnNumber; } /** * @param int<0, max> $lineNumber - * @param int $columnNumber + * @param int<0, max> $columnNumber */ - public function setPosition(int $lineNumber, $columnNumber): void + public function setPosition(int $lineNumber, int $columnNumber): void { $this->columnNumber = $columnNumber; $this->lineNumber = $lineNumber; } /** - * @param string $rule + * @param non-empty-string $rule */ - public function setRule($rule): void + public function setRule(string $rule): void { $this->rule = $rule; } /** - * @return string + * @return non-empty-string */ - public function getRule() + public function getRule(): string { return $this->rule; } @@ -189,9 +189,8 @@ public function setValue($value): void * Otherwise, the existing value will be wrapped by one. * * @param RuleValueList|array $value - * @param string $type */ - public function addValue($value, $type = ' '): void + public function addValue($value, string $type = ' '): void { if (!\is_array($value)) { $value = [$value]; @@ -208,22 +207,19 @@ public function addValue($value, $type = ' '): void } } - /** - * @param bool $isImportant - */ - public function setIsImportant($isImportant): void + public function setIsImportant(bool $isImportant): void { $this->isImportant = $isImportant; } - /** - * @return bool - */ - public function getIsImportant() + public function getIsImportant(): bool { return $this->isImportant; } + /** + * @return non-empty-string + */ public function render(OutputFormat $outputFormat): string { $formatter = $outputFormat->getFormatter(); From 8f82bf3a7d47126abf5861de29db1475687591a1 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 18 Mar 2025 23:19:46 +0100 Subject: [PATCH 400/555] [TASK] Add native type declarations for `AtRuleSet` (#1192) Part of #811 --- CHANGELOG.md | 2 +- config/phpstan-baseline.neon | 6 ------ src/RuleSet/AtRuleSet.php | 10 +++------- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a663e92..bbee1fd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,7 @@ Please also have a look at our (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933, #958, #964, #967, #1000, #1044, #1134, #1136, #1137, #1139, #1140, #1141, #1145, #1162, #1163, #1166, #1172, #1174, #1178, #1179, #1181, #1183, #1184, #1186, - #1187, #1190) + #1187, #1190, #1192) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index bdea7afc..a84cbd81 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -36,12 +36,6 @@ parameters: count: 1 path: ../src/Rule/Rule.php - - - message: '#^Only booleans are allowed in an if condition, string given\.$#' - identifier: if.condNotBoolean - count: 1 - path: ../src/RuleSet/AtRuleSet.php - - message: '#^Loose comparison via "\!\=" is not allowed\.$#' identifier: notEqual.notAllowed diff --git a/src/RuleSet/AtRuleSet.php b/src/RuleSet/AtRuleSet.php index 1e28aa54..e2ae38a1 100644 --- a/src/RuleSet/AtRuleSet.php +++ b/src/RuleSet/AtRuleSet.php @@ -27,10 +27,9 @@ class AtRuleSet extends RuleSet implements AtRule /** * @param non-empty-string $type - * @param string $arguments * @param int<0, max> $lineNumber */ - public function __construct($type, $arguments = '', int $lineNumber = 0) + public function __construct(string $type, string $arguments = '', int $lineNumber = 0) { parent::__construct($lineNumber); $this->type = $type; @@ -45,10 +44,7 @@ public function atRuleName(): string return $this->type; } - /** - * @return string - */ - public function atRuleArgs() + public function atRuleArgs(): string { return $this->arguments; } @@ -58,7 +54,7 @@ public function render(OutputFormat $outputFormat): string $formatter = $outputFormat->getFormatter(); $result = $formatter->comments($this); $arguments = $this->arguments; - if ($arguments) { + if ($arguments !== '') { $arguments = ' ' . $arguments; } $result .= "@{$this->type}$arguments{$formatter->spaceBeforeOpeningBrace()}{"; From 7e80f086f71e6ed267bc7a2bd1461fa1bfbc8bce Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 19 Mar 2025 00:02:26 +0100 Subject: [PATCH 401/555] [TASK] Add native type declarations for `DeclarationBlock` (#1193) Part of #811 --- CHANGELOG.md | 2 +- src/RuleSet/DeclarationBlock.php | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbee1fd4..d69fbd33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,7 @@ Please also have a look at our (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933, #958, #964, #967, #1000, #1044, #1134, #1136, #1137, #1139, #1140, #1141, #1145, #1162, #1163, #1166, #1172, #1174, #1178, #1179, #1181, #1183, #1184, #1186, - #1187, #1190, #1192) + #1187, #1190, #1192, #1193) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 8e299070..938c05fe 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -30,8 +30,6 @@ class DeclarationBlock extends RuleSet private $selectors = []; /** - * @param CSSList|null $list - * * @return DeclarationBlock|false * * @throws UnexpectedTokenException @@ -39,7 +37,7 @@ class DeclarationBlock extends RuleSet * * @internal since V8.8.0 */ - public static function parse(ParserState $parserState, $list = null) + public static function parse(ParserState $parserState, ?CSSList $list = null) { $comments = []; $result = new DeclarationBlock($parserState->currentLine()); @@ -77,11 +75,10 @@ public static function parse(ParserState $parserState, $list = null) /** * @param array|string $selectors - * @param CSSList|null $list * * @throws UnexpectedTokenException */ - public function setSelectors($selectors, $list = null): void + public function setSelectors($selectors, ?CSSList $list = null): void { if (\is_array($selectors)) { $this->selectors = $selectors; From 45cd62ff0df68c49ed2ac71508bca6bc8bedc81f Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 19 Mar 2025 17:37:42 +0100 Subject: [PATCH 402/555] [TASK] Add native type declarations for `ValueList` (#1196) Also polish some PHPDoc type annotations. Part of #811. --- src/Value/ValueList.php | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/Value/ValueList.php b/src/Value/ValueList.php index 3e57d7fe..b63b34ef 100644 --- a/src/Value/ValueList.php +++ b/src/Value/ValueList.php @@ -29,7 +29,7 @@ abstract class ValueList extends Value protected $separator; /** - * @param array|Value|string $components + * @param array|Value|string $components * @param string $separator * @param int<0, max> $lineNumber */ @@ -52,33 +52,27 @@ public function addListComponent($component): void } /** - * @return array + * @return array */ - public function getListComponents() + public function getListComponents(): array { return $this->components; } /** - * @param array $components + * @param array $components */ public function setListComponents(array $components): void { $this->components = $components; } - /** - * @return string - */ - public function getListSeparator() + public function getListSeparator(): string { return $this->separator; } - /** - * @param string $separator - */ - public function setListSeparator($separator): void + public function setListSeparator(string $separator): void { $this->separator = $separator; } From 357206220795a2515df3244f83205792f5442e83 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 19 Mar 2025 23:10:15 +0100 Subject: [PATCH 403/555] [TASK] Add native type declarations for `CSSFunction` (#1197) Also improve the related type annotations and declarations in other classes in order to keep things consistent and to keep Rector from changing things. --- src/Value/CSSFunction.php | 22 +++++++++++----------- src/Value/Color.php | 4 ++-- src/Value/RuleValueList.php | 2 +- src/Value/Value.php | 4 ++-- src/Value/ValueList.php | 12 +++++++++--- 5 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index 5941b4b3..306df768 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -17,19 +17,19 @@ class CSSFunction extends ValueList { /** - * @var string + * @var non-empty-string * * @internal since 8.8.0 */ protected $name; /** - * @param string $name - * @param RuleValueList|array $arguments - * @param string $separator + * @param non-empty-string $name + * @param RuleValueList|array $arguments + * @param non-empty-string $separator * @param int<0, max> $lineNumber */ - public function __construct($name, $arguments, $separator = ',', int $lineNumber = 0) + public function __construct(string $name, $arguments, string $separator = ',', int $lineNumber = 0) { if ($arguments instanceof RuleValueList) { $separator = $arguments->getListSeparator(); @@ -82,25 +82,25 @@ private static function parseArguments(ParserState $parserState) } /** - * @return string + * @return non-empty-string */ - public function getName() + public function getName(): string { return $this->name; } /** - * @param string $name + * @param non-empty-string $name */ - public function setName($name): void + public function setName(string $name): void { $this->name = $name; } /** - * @return array + * @return array */ - public function getArguments() + public function getArguments(): array { return $this->components; } diff --git a/src/Value/Color.php b/src/Value/Color.php index f6eeaa60..5f7829e8 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -217,9 +217,9 @@ public function setColor(array $colorValues): void } /** - * @return string + * @return non-empty-string */ - public function getColorDescription() + public function getColorDescription(): string { return $this->getName(); } diff --git a/src/Value/RuleValueList.php b/src/Value/RuleValueList.php index 9e505979..414d775c 100644 --- a/src/Value/RuleValueList.php +++ b/src/Value/RuleValueList.php @@ -12,7 +12,7 @@ class RuleValueList extends ValueList { /** - * @param string $separator + * @param non-empty-string $separator * @param int<0, max> $lineNumber */ public function __construct($separator = ',', int $lineNumber = 0) diff --git a/src/Value/Value.php b/src/Value/Value.php index a81ab472..3432b18c 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -32,7 +32,7 @@ public function __construct(int $lineNumber = 0) } /** - * @param array $listDelimiters + * @param array $listDelimiters * * @return Value|string * @@ -43,7 +43,7 @@ public function __construct(int $lineNumber = 0) */ public static function parseValue(ParserState $parserState, array $listDelimiters = []) { - /** @var array $stack */ + /** @var list $stack */ $stack = []; $parserState->consumeWhiteSpace(); //Build a list of delimiters and parsed values diff --git a/src/Value/ValueList.php b/src/Value/ValueList.php index b63b34ef..1d26b74d 100644 --- a/src/Value/ValueList.php +++ b/src/Value/ValueList.php @@ -15,14 +15,14 @@ abstract class ValueList extends Value { /** - * @var array + * @var array * * @internal since 8.8.0 */ protected $components; /** - * @var string + * @var non-empty-string * * @internal since 8.8.0 */ @@ -30,7 +30,7 @@ abstract class ValueList extends Value /** * @param array|Value|string $components - * @param string $separator + * @param non-empty-string $separator * @param int<0, max> $lineNumber */ public function __construct($components = [], $separator = ',', int $lineNumber = 0) @@ -67,11 +67,17 @@ public function setListComponents(array $components): void $this->components = $components; } + /** + * @return non-empty-string + */ public function getListSeparator(): string { return $this->separator; } + /** + * @param non-empty-string $separator + */ public function setListSeparator(string $separator): void { $this->separator = $separator; From a0b9a78752abbf15f418609b13e1d9da4f1a252b Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 19 Mar 2025 23:31:02 +0100 Subject: [PATCH 404/555] [CLEANUP] And some more annotations for non-empty strings (#1199) --- src/CSSList/AtRuleBlockList.php | 3 +++ src/CSSList/KeyFrame.php | 3 +++ src/Comment/Comment.php | 3 +++ src/OutputFormat.php | 6 +++++- src/Property/CSSNamespace.php | 3 +++ src/Property/Charset.php | 3 +++ src/Property/Import.php | 3 +++ src/RuleSet/AtRuleSet.php | 3 +++ src/RuleSet/DeclarationBlock.php | 2 ++ src/Value/CSSFunction.php | 3 +++ src/Value/CSSString.php | 3 +++ src/Value/Color.php | 3 +++ src/Value/LineName.php | 3 +++ src/Value/Size.php | 3 +++ src/Value/URL.php | 3 +++ 15 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/CSSList/AtRuleBlockList.php b/src/CSSList/AtRuleBlockList.php index 57c04ab8..8ee62ae4 100644 --- a/src/CSSList/AtRuleBlockList.php +++ b/src/CSSList/AtRuleBlockList.php @@ -46,6 +46,9 @@ public function atRuleArgs(): string return $this->arguments; } + /** + * @return non-empty-string + */ public function render(OutputFormat $outputFormat): string { $formatter = $outputFormat->getFormatter(); diff --git a/src/CSSList/KeyFrame.php b/src/CSSList/KeyFrame.php index f1f96cec..e632d088 100644 --- a/src/CSSList/KeyFrame.php +++ b/src/CSSList/KeyFrame.php @@ -51,6 +51,9 @@ public function getAnimationName(): string return $this->animationName; } + /** + * @return non-empty-string + */ public function render(OutputFormat $outputFormat): string { $formatter = $outputFormat->getFormatter(); diff --git a/src/Comment/Comment.php b/src/Comment/Comment.php index e96bd28e..cb19917c 100644 --- a/src/Comment/Comment.php +++ b/src/Comment/Comment.php @@ -50,6 +50,9 @@ public function setComment(string $commentText): void $this->commentText = $commentText; } + /** + * @return non-empty-string + */ public function render(OutputFormat $outputFormat): string { return '/*' . $this->commentText . '*/'; diff --git a/src/OutputFormat.php b/src/OutputFormat.php index a0e8b894..c7dc2133 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -9,7 +9,7 @@ final class OutputFormat /** * Value format: `"` means double-quote, `'` means single-quote * - * @var string + * @var non-empty-string */ private $stringQuotingType = '"'; @@ -181,6 +181,8 @@ final class OutputFormat private $indentationLevel = 0; /** + * @return non-empty-string + * * @internal */ public function getStringQuotingType(): string @@ -189,6 +191,8 @@ public function getStringQuotingType(): string } /** + * @param non-empty-string $quotingType + * * @return $this fluent interface */ public function setStringQuotingType(string $quotingType): self diff --git a/src/Property/CSSNamespace.php b/src/Property/CSSNamespace.php index 5b174861..d2274d18 100644 --- a/src/Property/CSSNamespace.php +++ b/src/Property/CSSNamespace.php @@ -55,6 +55,9 @@ public function getLineNo(): int return $this->lineNumber; } + /** + * @return non-empty-string + */ public function render(OutputFormat $outputFormat): string { return '@namespace ' . ($this->prefix === null ? '' : $this->prefix . ' ') diff --git a/src/Property/Charset.php b/src/Property/Charset.php index 14cb9321..6e81cd4e 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -68,6 +68,9 @@ public function getCharset(): string return $this->charset->getString(); } + /** + * @return non-empty-string + */ public function render(OutputFormat $outputFormat): string { return "{$outputFormat->getFormatter()->comments($this)}@charset {$this->charset->render($outputFormat)};"; diff --git a/src/Property/Import.php b/src/Property/Import.php index f889786b..e2c2f41f 100644 --- a/src/Property/Import.php +++ b/src/Property/Import.php @@ -65,6 +65,9 @@ public function getLocation(): URL return $this->location; } + /** + * @return non-empty-string + */ public function render(OutputFormat $outputFormat): string { return $outputFormat->getFormatter()->comments($this) . '@import ' . $this->location->render($outputFormat) diff --git a/src/RuleSet/AtRuleSet.php b/src/RuleSet/AtRuleSet.php index e2ae38a1..0fda9638 100644 --- a/src/RuleSet/AtRuleSet.php +++ b/src/RuleSet/AtRuleSet.php @@ -49,6 +49,9 @@ public function atRuleArgs(): string return $this->arguments; } + /** + * @return non-empty-string + */ public function render(OutputFormat $outputFormat): string { $formatter = $outputFormat->getFormatter(); diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 938c05fe..9a1a0ca0 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -138,6 +138,8 @@ public function getSelectors(): array } /** + * @return non-empty-string + * * @throws OutputException */ public function render(OutputFormat $outputFormat): string diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index 306df768..8671fd3e 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -105,6 +105,9 @@ public function getArguments(): array return $this->components; } + /** + * @return non-empty-string + */ public function render(OutputFormat $outputFormat): string { $arguments = parent::render($outputFormat); diff --git a/src/Value/CSSString.php b/src/Value/CSSString.php index a71175ef..52b521e6 100644 --- a/src/Value/CSSString.php +++ b/src/Value/CSSString.php @@ -83,6 +83,9 @@ public function getString(): string return $this->string; } + /** + * @return non-empty-string + */ public function render(OutputFormat $outputFormat): string { $string = \addslashes($this->string); diff --git a/src/Value/Color.php b/src/Value/Color.php index 5f7829e8..4c59a0a3 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -224,6 +224,9 @@ public function getColorDescription(): string return $this->getName(); } + /** + * @return non-empty-string + */ public function render(OutputFormat $outputFormat): string { if ($this->shouldRenderAsHex($outputFormat)) { diff --git a/src/Value/LineName.php b/src/Value/LineName.php index 9afe6703..ed5ceeeb 100644 --- a/src/Value/LineName.php +++ b/src/Value/LineName.php @@ -49,6 +49,9 @@ public static function parse(ParserState $parserState): LineName return new LineName($names, $parserState->currentLine()); } + /** + * @return non-empty-string + */ public function render(OutputFormat $outputFormat): string { return '[' . parent::render(OutputFormat::createCompact()) . ']'; diff --git a/src/Value/Size.php b/src/Value/Size.php index e3cf6b89..a5e15497 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -195,6 +195,9 @@ public function isRelative(): bool return false; } + /** + * @return non-empty-string + */ public function render(OutputFormat $outputFormat): string { $locale = \localeconv(); diff --git a/src/Value/URL.php b/src/Value/URL.php index f1fe258b..4b4fb4c8 100644 --- a/src/Value/URL.php +++ b/src/Value/URL.php @@ -73,6 +73,9 @@ public function getURL(): CSSString return $this->url; } + /** + * @return non-empty-string + */ public function render(OutputFormat $outputFormat): string { return "url({$this->url->render($outputFormat)})"; From b29b16d2b86c935f541d87c3de23d1c1c892de89 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 19 Mar 2025 23:35:59 +0100 Subject: [PATCH 405/555] [CLEANUP] Make annotations for `OutputFormat` more specific (#1200) --- src/OutputFormat.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index c7dc2133..6ad45aa4 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -176,7 +176,7 @@ final class OutputFormat private $nextLevelFormat; /** - * @var int + * @var int<0, max> */ private $indentationLevel = 0; @@ -643,6 +643,8 @@ public function setRenderComments(bool $renderComments): self } /** + * @return int<0, max> + * * @internal */ public function getIndentationLevel(): int @@ -651,6 +653,8 @@ public function getIndentationLevel(): int } /** + * @param int<1, max> $numberOfTabs + * * @return $this fluent interface */ public function indentWithTabs(int $numberOfTabs = 1): self @@ -659,6 +663,8 @@ public function indentWithTabs(int $numberOfTabs = 1): self } /** + * @param int<1, max> $numberOfSpaces + * * @return $this fluent interface */ public function indentWithSpaces(int $numberOfSpaces = 2): self From 28496865721a3005009aa4581b3f231e9b376a10 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 19 Mar 2025 23:40:01 +0100 Subject: [PATCH 406/555] [TASK] Drop the unused `ParserState::strpos()` method (#1202) --- src/Parsing/ParserState.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 88f533a8..e90650d5 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -461,14 +461,4 @@ private function strsplit(string $string): array return $result; } - - /** - * @return int<0, max>|false - */ - private function strpos(string $haystack, string $needle, int $offset) - { - return $this->parserSettings->hasMultibyteSupport() - ? \mb_strpos($haystack, $needle, $offset, $this->charset) - : \strpos($haystack, $needle, $offset); - } } From 9e315e17f78ae3902623d63eaf6201a5ddb4faa0 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 19 Mar 2025 23:40:42 +0100 Subject: [PATCH 407/555] [TASK] Add native type declarations for `RuleValueList` (#1203) Part of #811 --- CHANGELOG.md | 2 +- src/Value/RuleValueList.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d69fbd33..c46a151f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,7 @@ Please also have a look at our (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933, #958, #964, #967, #1000, #1044, #1134, #1136, #1137, #1139, #1140, #1141, #1145, #1162, #1163, #1166, #1172, #1174, #1178, #1179, #1181, #1183, #1184, #1186, - #1187, #1190, #1192, #1193) + #1187, #1190, #1192, #1193, #1203) - Add visibility to all class/interface constants (#469) ### Deprecated diff --git a/src/Value/RuleValueList.php b/src/Value/RuleValueList.php index 414d775c..9884f5cf 100644 --- a/src/Value/RuleValueList.php +++ b/src/Value/RuleValueList.php @@ -15,7 +15,7 @@ class RuleValueList extends ValueList * @param non-empty-string $separator * @param int<0, max> $lineNumber */ - public function __construct($separator = ',', int $lineNumber = 0) + public function __construct(string $separator = ',', int $lineNumber = 0) { parent::__construct([], $separator, $lineNumber); } From 43b8afa88abadfc7166d0d7b2fbcbd67c0c74745 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 19 Mar 2025 23:43:01 +0100 Subject: [PATCH 408/555] [CLEANUP] Improve type annotations in `LineName` (#1198) --- src/Value/LineName.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Value/LineName.php b/src/Value/LineName.php index ed5ceeeb..791f0cc3 100644 --- a/src/Value/LineName.php +++ b/src/Value/LineName.php @@ -12,7 +12,7 @@ class LineName extends ValueList { /** - * @param array $components + * @param array $components * @param int<0, max> $lineNumber */ public function __construct(array $components = [], int $lineNumber = 0) From 639366092026f296465a6f5fefe996ed91d6ec43 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 20 Mar 2025 10:30:23 +0100 Subject: [PATCH 409/555] [TASK] Add native type declarations for `Color` (#1204) Also make some types more specific. Also improve code formatting a bit. Part of #811 --- config/phpstan-baseline.neon | 6 ------ src/Value/Color.php | 25 ++++++++++++------------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index a84cbd81..416fa9d1 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -90,12 +90,6 @@ parameters: count: 3 path: ../src/Value/Color.php - - - message: '#^Provide more specific return type "Sabberworm\\CSS\\Value\\Color" over abstract one$#' - identifier: typePerfect.narrowReturnObjectType - count: 1 - path: ../src/Value/Color.php - - message: '#^Loose comparison via "\!\=" is not allowed\.$#' identifier: notEqual.notAllowed diff --git a/src/Value/Color.php b/src/Value/Color.php index 4c59a0a3..028ce856 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -16,7 +16,7 @@ class Color extends CSSFunction { /** - * @param array $colorValues + * @param array $colorValues * @param int<0, max> $lineNumber */ public function __construct(array $colorValues, int $lineNumber = 0) @@ -32,17 +32,16 @@ public function __construct(array $colorValues, int $lineNumber = 0) */ public static function parse(ParserState $parserState, bool $ignoreCase = false): CSSFunction { - return - $parserState->comes('#') - ? self::parseHexColor($parserState) - : self::parseColorFunction($parserState); + return $parserState->comes('#') + ? self::parseHexColor($parserState) + : self::parseColorFunction($parserState); } /** * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ - private static function parseHexColor(ParserState $parserState): CSSFunction + private static function parseHexColor(ParserState $parserState): Color { $parserState->consume('#'); $hexValue = $parserState->parseIdentifier(false); @@ -183,10 +182,9 @@ private static function parseColorFunction(ParserState $parserState): CSSFunctio } $parserState->consume(')'); - return - $containsVar - ? new CSSFunction($colorMode, \array_values($colorValues), ',', $parserState->currentLine()) - : new Color($colorValues, $parserState->currentLine()); + return $containsVar + ? new CSSFunction($colorMode, \array_values($colorValues), ',', $parserState->currentLine()) + : new Color($colorValues, $parserState->currentLine()); } private static function mapRange(float $value, float $fromMin, float $fromMax, float $toMin, float $toMax): float @@ -196,19 +194,20 @@ private static function mapRange(float $value, float $fromMin, float $fromMax, f $multiplier = $toRange / $fromRange; $newValue = $value - $fromMin; $newValue *= $multiplier; + return $newValue + $toMin; } /** - * @return array + * @return array */ - public function getColor() + public function getColor(): array { return $this->components; } /** - * @param array $colorValues + * @param array $colorValues */ public function setColor(array $colorValues): void { From acfe85e5fdb4cf1528cf49c45db79b1489b568ff Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Mon, 24 Mar 2025 19:08:55 +0000 Subject: [PATCH 410/555] [CLEANUP] Return `null` from `DeclarationBlock::parse()` on failure (#1209) Also add clarification of meaning of return value from `CSSList::parseListItem()`, where `null` and `false` have different meanings. Part of #1176. --- config/phpstan-baseline.neon | 6 ---- src/CSSList/CSSList.php | 7 +++-- src/RuleSet/DeclarationBlock.php | 6 ++-- .../RuleSet/DeclarationBlockTest.php | 28 +++++++++++++++++++ 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 416fa9d1..47b197a1 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -48,12 +48,6 @@ parameters: count: 1 path: ../src/RuleSet/DeclarationBlock.php - - - message: '#^Returning false in non return bool class method\. Use null with type\|null instead or add bool return type$#' - identifier: typePerfect.nullOverFalse - count: 1 - path: ../src/RuleSet/DeclarationBlock.php - - message: '#^Only booleans are allowed in a negated boolean, string\|null given\.$#' identifier: booleanNot.exprNotBoolean diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 36c248cc..3cd76206 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -106,6 +106,9 @@ public static function parseList(ParserState $parserState, CSSList $list): void /** * @return AtRuleBlockList|KeyFrame|Charset|CSSNamespace|Import|AtRuleSet|DeclarationBlock|false|null + * If `null` is returned, it means the end of the list has been reached. + * If `false` is returned, it means an invalid item has been encountered, + * but parsing of the next item should proceed. * * @throws SourceException * @throws UnexpectedEOFException @@ -139,7 +142,7 @@ private static function parseListItem(ParserState $parserState, CSSList $list) } elseif ($parserState->comes('}')) { if ($isRoot) { if ($parserState->getSettings()->usesLenientParsing()) { - return DeclarationBlock::parse($parserState); + return DeclarationBlock::parse($parserState) ?? false; } else { throw new SourceException('Unopened {', $parserState->currentLine()); } @@ -148,7 +151,7 @@ private static function parseListItem(ParserState $parserState, CSSList $list) return null; } } else { - return DeclarationBlock::parse($parserState, $list); + return DeclarationBlock::parse($parserState, $list) ?? false; } } diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 9a1a0ca0..06829fa2 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -30,14 +30,12 @@ class DeclarationBlock extends RuleSet private $selectors = []; /** - * @return DeclarationBlock|false - * * @throws UnexpectedTokenException * @throws UnexpectedEOFException * * @internal since V8.8.0 */ - public static function parse(ParserState $parserState, ?CSSList $list = null) + public static function parse(ParserState $parserState, ?CSSList $list = null): ?DeclarationBlock { $comments = []; $result = new DeclarationBlock($parserState->currentLine()); @@ -63,7 +61,7 @@ public static function parse(ParserState $parserState, ?CSSList $list = null) if (!$parserState->comes('}')) { $parserState->consumeUntil('}', false, true); } - return false; + return null; } else { throw $e; } diff --git a/tests/Functional/RuleSet/DeclarationBlockTest.php b/tests/Functional/RuleSet/DeclarationBlockTest.php index ca3d3f87..fe6b9e51 100644 --- a/tests/Functional/RuleSet/DeclarationBlockTest.php +++ b/tests/Functional/RuleSet/DeclarationBlockTest.php @@ -6,15 +6,43 @@ use PHPUnit\Framework\TestCase; use Sabberworm\CSS\OutputFormat; +use Sabberworm\CSS\Parsing\ParserState; use Sabberworm\CSS\Property\Selector; use Sabberworm\CSS\Rule\Rule; use Sabberworm\CSS\RuleSet\DeclarationBlock; +use Sabberworm\CSS\Settings; /** * @covers \Sabberworm\CSS\RuleSet\DeclarationBlock */ final class DeclarationBlockTest extends TestCase { + /** + * @return array + */ + public static function provideInvalidDeclarationBlock(): array + { + return [ + 'no selector' => ['{ color: red; }'], + 'invalid selector' => ['/ { color: red; }'], + 'no opening brace' => ['body color: red; }'], + ]; + } + + /** + * @test + * + * @dataProvider provideInvalidDeclarationBlock + */ + public function parseReturnsNullForInvalidDeclarationBlock(string $invalidDeclarationBlock): void + { + $parserState = new ParserState($invalidDeclarationBlock, Settings::create()); + + $result = DeclarationBlock::parse($parserState); + + self::assertNull($result); + } + /** * @test */ From 734c72eb3c59fef7050f80ac4626421ce9023498 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Tue, 25 Mar 2025 09:12:41 +0000 Subject: [PATCH 411/555] [TASK] Add (and use) a `CSSListItem` type (#1212) This allows a single type to be used for the contents of a `CSSList`, instead of a long list of orred types, and helps with static analysis. Various `assertInstanceOf()` tests are added to the test cases to confirm that the list items are of the type expected. Some `implements` and `exetends` lists are now alphabetically sorted. Also don't implement interfaces extended by another that is also implemented PHP<7.4 does not allow this. Instead, for clarity, add a DocBlock comment stating which additional interfaces should be implemented that are not explicitly listed in the `implements` section. When our minimum PHP version becomes 7.4 or above, we can revisit this. --- config/phpstan-baseline.neon | 12 ++++---- src/CSSList/CSSList.php | 42 +++++++++++--------------- src/CSSList/CSSListItem.php | 18 +++++++++++ src/Property/AtRule.php | 7 ++++- src/RuleSet/RuleSet.php | 6 +++- tests/CSSList/AtRuleBlockListTest.php | 3 ++ tests/ParserTest.php | 36 ++++++++++++++++++++++ tests/RuleSet/DeclarationBlockTest.php | 6 +++- 8 files changed, 97 insertions(+), 33 deletions(-) create mode 100644 src/CSSList/CSSListItem.php diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 47b197a1..007667a5 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -18,6 +18,12 @@ parameters: count: 1 path: ../src/CSSList/CSSList.php + - + message: '#^Parameters should have "Sabberworm\\CSS\\CSSList\\CSSListItem\|array" types as the only types passed to this method$#' + identifier: typePerfect.narrowPublicClassMethodParamType + count: 1 + path: ../src/CSSList/CSSList.php + - message: '#^Short ternary operator is not allowed\. Use null coalesce operator if applicable or consider using long ternary\.$#' identifier: ternary.shortNotAllowed @@ -54,12 +60,6 @@ parameters: count: 2 path: ../src/RuleSet/RuleSet.php - - - message: '#^Parameters should have "Sabberworm\\CSS\\Rule\\Rule" types as the only types passed to this method$#' - identifier: typePerfect.narrowPublicClassMethodParamType - count: 1 - path: ../src/RuleSet/RuleSet.php - - message: '#^Parameters should have "string" types as the only types passed to this method$#' identifier: typePerfect.narrowPublicClassMethodParamType diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 3cd76206..e6cb900f 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -30,8 +30,11 @@ * `RuleSet`s as well as other `CSSList` objects. * * It can also contain `Import` and `Charset` objects stemming from at-rules. + * + * Note that `CSSListItem` extends both `Commentable` and `Renderable`, + * so those interfaces must also be implemented by concrete subclasses. */ -abstract class CSSList implements Renderable, Commentable +abstract class CSSList implements CSSListItem { /** * @var list @@ -41,7 +44,7 @@ abstract class CSSList implements Renderable, Commentable protected $comments = []; /** - * @var array, RuleSet|CSSList|Import|Charset> + * @var array, CSSListItem> * * @internal since 8.8.0 */ @@ -105,7 +108,7 @@ public static function parseList(ParserState $parserState, CSSList $list): void } /** - * @return AtRuleBlockList|KeyFrame|Charset|CSSNamespace|Import|AtRuleSet|DeclarationBlock|false|null + * @return CSSListItem|false|null * If `null` is returned, it means the end of the list has been reached. * If `false` is returned, it means an invalid item has been encountered, * but parsing of the next item should proceed. @@ -156,13 +159,11 @@ private static function parseListItem(ParserState $parserState, CSSList $list) } /** - * @return AtRuleBlockList|KeyFrame|Charset|CSSNamespace|Import|AtRuleSet|null - * * @throws SourceException * @throws UnexpectedTokenException * @throws UnexpectedEOFException */ - private static function parseAtRule(ParserState $parserState) + private static function parseAtRule(ParserState $parserState): ?CSSListItem { $parserState->consume('@'); $identifier = $parserState->parseIdentifier(); @@ -265,20 +266,16 @@ public function getLineNo(): int /** * Prepends an item to the list of contents. - * - * @param RuleSet|CSSList|Import|Charset $item */ - public function prepend($item): void + public function prepend(CSSListItem $item): void { \array_unshift($this->contents, $item); } /** * Appends an item to the list of contents. - * - * @param RuleSet|CSSList|Import|Charset $item */ - public function append($item): void + public function append(CSSListItem $item): void { $this->contents[] = $item; } @@ -286,7 +283,7 @@ public function append($item): void /** * Splices the list of contents. * - * @param array $replacement + * @param array $replacement */ public function splice(int $offset, ?int $length = null, ?array $replacement = null): void { @@ -296,11 +293,8 @@ public function splice(int $offset, ?int $length = null, ?array $replacement = n /** * 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): void + public function insertBefore(CSSListItem $item, CSSListItem $sibling): void { if (\in_array($sibling, $this->contents, true)) { $this->replace($sibling, [$item, $sibling]); @@ -312,13 +306,13 @@ public function insertBefore($item, $sibling): void /** * Removes an item from the CSS list. * - * @param RuleSet|Import|Charset|CSSList $itemToRemove + * @param CSSListItem $itemToRemove * May be a `RuleSet` (most likely a `DeclarationBlock`), an `Import`, * a `Charset` or another `CSSList` (most likely a `MediaQuery`) * * @return bool whether the item was removed */ - public function remove($itemToRemove): bool + public function remove(CSSListItem $itemToRemove): bool { $key = \array_search($itemToRemove, $this->contents, true); if ($key !== false) { @@ -332,12 +326,12 @@ public function remove($itemToRemove): bool /** * Replaces an item from the CSS list. * - * @param RuleSet|Import|Charset|CSSList $oldItem + * @param CSSListItem $oldItem * May be a `RuleSet` (most likely a `DeclarationBlock`), an `Import`, a `Charset` * or another `CSSList` (most likely a `MediaQuery`) - * @param RuleSet|Import|Charset|CSSList|array $newItem + * @param CSSListItem|array $newItem */ - public function replace($oldItem, $newItem): bool + public function replace(CSSListItem $oldItem, $newItem): bool { $key = \array_search($oldItem, $this->contents, true); if ($key !== false) { @@ -353,7 +347,7 @@ public function replace($oldItem, $newItem): bool } /** - * @param array $contents + * @param array $contents */ public function setContents(array $contents): void { @@ -444,7 +438,7 @@ abstract public function isRootList(): bool; /** * Returns the stored items. * - * @return array, RuleSet|Import|Charset|CSSList> + * @return array, CSSListItem> */ public function getContents(): array { diff --git a/src/CSSList/CSSListItem.php b/src/CSSList/CSSListItem.php new file mode 100644 index 00000000..3cf2509b --- /dev/null +++ b/src/CSSList/CSSListItem.php @@ -0,0 +1,18 @@ +parse()->getContents(); $atRuleBlockList = $contents[0]; + self::assertInstanceOf(AtRuleBlockList::class, $atRuleBlockList); self::assertSame('media', $atRuleBlockList->atRuleName()); } @@ -73,6 +75,7 @@ public function parsesArgumentsOfMediaQueries(string $css): void $contents = (new Parser($css))->parse()->getContents(); $atRuleBlockList = $contents[0]; + self::assertInstanceOf(AtRuleBlockList::class, $atRuleBlockList); self::assertSame('(min-width: 768px)', $atRuleBlockList->atRuleArgs()); } diff --git a/tests/ParserTest.php b/tests/ParserTest.php index f2608fb3..7d1b528c 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -5,6 +5,8 @@ namespace Sabberworm\CSS\Tests; use PHPUnit\Framework\TestCase; +use Sabberworm\CSS\Comment\Commentable; +use Sabberworm\CSS\CSSList\CSSList; use Sabberworm\CSS\CSSList\Document; use Sabberworm\CSS\CSSList\KeyFrame; use Sabberworm\CSS\OutputFormat; @@ -973,9 +975,30 @@ public function lineNumbersParsing(): void $actual = []; foreach ($document->getContents() as $contentItem) { + // PHPStan can see what `assertInstanceOf()` does, + // but does not understand `LogicalOr` with multiple `IsIntanceOf` constraints. + // So a more explicit type check is required. + // TODO: tidy this up when an interface with `getLineNo()` is added. + if ( + !$contentItem instanceof Charset && + !$contentItem instanceof CSSList && + !$contentItem instanceof CSSNamespace && + !$contentItem instanceof Import && + !$contentItem instanceof RuleSet + ) { + self::fail('Content item is not of an expected type. It\'s a `' . \get_class($contentItem) . '`.'); + } $actual[$contentItem->getLineNo()] = [\get_class($contentItem)]; if ($contentItem instanceof KeyFrame) { foreach ($contentItem->getContents() as $block) { + if ( + !$block instanceof CSSList && + !$block instanceof RuleSet + ) { + self::fail( + 'KeyFrame content item is not of an expected type. It\'s a `' . \get_class($block) . '`.' + ); + } $actual[$contentItem->getLineNo()][] = $block->getLineNo(); } } @@ -1037,6 +1060,7 @@ public function commentExtracting(): void $nodes = $document->getContents(); // Import property. + self::assertInstanceOf(Commentable::class, $nodes[0]); $importComments = $nodes[0]->getComments(); self::assertCount(2, $importComments); self::assertSame("*\n * Comments\n ", $importComments[0]->getComment()); @@ -1044,6 +1068,7 @@ public function commentExtracting(): void // Declaration block. $fooBarBlock = $nodes[1]; + self::assertInstanceOf(Commentable::class, $fooBarBlock); $fooBarBlockComments = $fooBarBlock->getComments(); // TODO Support comments in selectors. // $this->assertCount(2, $fooBarBlockComments); @@ -1051,6 +1076,7 @@ public function commentExtracting(): void // $this->assertSame("* Number 5 *", $fooBarBlockComments[1]->getComment()); // Declaration rules. + self::assertInstanceOf(RuleSet::class, $fooBarBlock); $fooBarRules = $fooBarBlock->getRules(); $fooBarRule = $fooBarRules[0]; $fooBarRuleComments = $fooBarRule->getComments(); @@ -1058,16 +1084,20 @@ public function commentExtracting(): void self::assertSame(' Number 6 ', $fooBarRuleComments[0]->getComment()); // Media property. + self::assertInstanceOf(Commentable::class, $nodes[2]); $mediaComments = $nodes[2]->getComments(); self::assertCount(0, $mediaComments); // Media children. + self::assertInstanceOf(CSSList::class, $nodes[2]); $mediaRules = $nodes[2]->getContents(); + self::assertInstanceOf(Commentable::class, $mediaRules[0]); $fooBarComments = $mediaRules[0]->getComments(); self::assertCount(1, $fooBarComments); self::assertSame('* Number 10 *', $fooBarComments[0]->getComment()); // Media -> declaration -> rule. + self::assertInstanceOf(RuleSet::class, $mediaRules[0]); $fooBarRules = $mediaRules[0]->getRules(); $fooBarChildComments = $fooBarRules[0]->getComments(); self::assertCount(1, $fooBarChildComments); @@ -1083,6 +1113,7 @@ public function flatCommentExtractingOneComment(): void $document = $parser->parse(); $contents = $document->getContents(); + self::assertInstanceOf(RuleSet::class, $contents[0]); $divRules = $contents[0]->getRules(); $comments = $divRules[0]->getComments(); @@ -1099,6 +1130,7 @@ public function flatCommentExtractingTwoConjoinedCommentsForOneRule(): void $document = $parser->parse(); $contents = $document->getContents(); + self::assertInstanceOf(RuleSet::class, $contents[0]); $divRules = $contents[0]->getRules(); $comments = $divRules[0]->getComments(); @@ -1116,6 +1148,7 @@ public function flatCommentExtractingTwoSpaceSeparatedCommentsForOneRule(): void $document = $parser->parse(); $contents = $document->getContents(); + self::assertInstanceOf(RuleSet::class, $contents[0]); $divRules = $contents[0]->getRules(); $comments = $divRules[0]->getComments(); @@ -1133,6 +1166,7 @@ public function flatCommentExtractingCommentsForTwoRules(): void $document = $parser->parse(); $contents = $document->getContents(); + self::assertInstanceOf(RuleSet::class, $contents[0]); $divRules = $contents[0]->getRules(); $rule1Comments = $divRules[0]->getComments(); $rule2Comments = $divRules[1]->getComments(); @@ -1151,6 +1185,7 @@ public function topLevelCommentExtracting(): void $parser = new Parser('/*Find Me!*/div {left:10px; text-align:left;}'); $document = $parser->parse(); $contents = $document->getContents(); + self::assertInstanceOf(Commentable::class, $contents[0]); $comments = $contents[0]->getComments(); self::assertCount(1, $comments); self::assertSame('Find Me!', $comments[0]->getComment()); @@ -1216,6 +1251,7 @@ public function escapedSpecialCaseTokens(): void { $document = self::parsedStructureForFile('escaped-tokens'); $contents = $document->getContents(); + self::assertInstanceOf(RuleSet::class, $contents[0]); $rules = $contents[0]->getRules(); $urlRule = $rules[0]; $calcRule = $rules[1]; diff --git a/tests/RuleSet/DeclarationBlockTest.php b/tests/RuleSet/DeclarationBlockTest.php index 128048e8..5aaf0662 100644 --- a/tests/RuleSet/DeclarationBlockTest.php +++ b/tests/RuleSet/DeclarationBlockTest.php @@ -8,6 +8,7 @@ use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Parser; use Sabberworm\CSS\Rule\Rule; +use Sabberworm\CSS\RuleSet\RuleSet; use Sabberworm\CSS\Settings as ParserSettings; use Sabberworm\CSS\Value\Size; @@ -30,8 +31,9 @@ public function overrideRules(): void $contents = $document->getContents(); $wrapper = $contents[0]; + self::assertInstanceOf(RuleSet::class, $wrapper); self::assertCount(2, $wrapper->getRules()); - $contents[0]->setRules([$rule]); + $wrapper->setRules([$rule]); $rules = $wrapper->getRules(); self::assertCount(1, $rules); @@ -50,6 +52,8 @@ public function ruleInsertion(): void $contents = $document->getContents(); $wrapper = $contents[0]; + self::assertInstanceOf(RuleSet::class, $wrapper); + $leftRules = $wrapper->getRules('left'); self::assertCount(1, $leftRules); $firstLeftRule = $leftRules[0]; From ece56337e74631bfe4c3b6caefd365359e3c62a1 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 25 Mar 2025 17:10:17 +0100 Subject: [PATCH 412/555] [TASK] Configure the target PHP version for PHPStan (#1216) This will help avoid it suggesting things that are only possile in later PHP versions. Fixes #1214 --- config/phpstan.neon | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/phpstan.neon b/config/phpstan.neon index 06ce70f2..6a410ac2 100644 --- a/config/phpstan.neon +++ b/config/phpstan.neon @@ -6,6 +6,8 @@ parameters: # Don't be overly greedy on machines with more CPU's to be a good neighbor especially on CI maximumNumberOfProcesses: 5 + phpVersion: 70200 + level: 3 paths: From ee07bde2ef1488815152fd4c2f153a9e7025ecc2 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Tue, 25 Mar 2025 19:24:06 +0000 Subject: [PATCH 413/555] [TASK] Add trait providing standard implementation of `Commentable` (#1206) Part of #813. --- composer.json | 1 + src/Comment/CommentContainer.php | 44 ++++ src/Comment/Commentable.php | 3 + tests/Unit/Comment/CommentContainerTest.php | 232 ++++++++++++++++++ .../Fixtures/ConcreteCommentContainer.php | 13 + 5 files changed, 293 insertions(+) create mode 100644 src/Comment/CommentContainer.php create mode 100644 tests/Unit/Comment/CommentContainerTest.php create mode 100644 tests/Unit/Comment/Fixtures/ConcreteCommentContainer.php diff --git a/composer.json b/composer.json index f07d3222..192eea6d 100644 --- a/composer.json +++ b/composer.json @@ -33,6 +33,7 @@ "phpstan/phpstan-phpunit": "1.4.2 || 2.0.4", "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.3", "phpunit/phpunit": "8.5.41", + "rawr/cross-data-providers": "2.4.0", "rector/rector": "1.2.10 || 2.0.7", "rector/type-perfect": "1.0.0 || 2.0.2" }, diff --git a/src/Comment/CommentContainer.php b/src/Comment/CommentContainer.php new file mode 100644 index 00000000..87f6ff46 --- /dev/null +++ b/src/Comment/CommentContainer.php @@ -0,0 +1,44 @@ + + */ + protected $comments = []; + + /** + * @param list $comments + */ + public function addComments(array $comments): void + { + $this->comments = \array_merge($this->comments, $comments); + } + + /** + * @return list + */ + public function getComments(): array + { + return $this->comments; + } + + /** + * @param list $comments + */ + public function setComments(array $comments): void + { + $this->comments = $comments; + } +} diff --git a/src/Comment/Commentable.php b/src/Comment/Commentable.php index e6eb6a0b..5f28021d 100644 --- a/src/Comment/Commentable.php +++ b/src/Comment/Commentable.php @@ -4,6 +4,9 @@ namespace Sabberworm\CSS\Comment; +/** + * A standard implementation of this interface is available in the `CommentContainer` trait. + */ interface Commentable { /** diff --git a/tests/Unit/Comment/CommentContainerTest.php b/tests/Unit/Comment/CommentContainerTest.php new file mode 100644 index 00000000..a29691b9 --- /dev/null +++ b/tests/Unit/Comment/CommentContainerTest.php @@ -0,0 +1,232 @@ +subject = new ConcreteCommentContainer(); + } + + /** + * @test + */ + public function getCommentsInitiallyReturnsEmptyArray(): void + { + self::assertSame([], $this->subject->getComments()); + } + + /** + * @return array}> + */ + public function provideCommentArray(): array + { + return [ + 'no comment' => [[]], + 'one comment' => [[new Comment('Is this really a spoon?')]], + 'two comments' => [[ + new Comment('I’m a teapot.'), + new Comment('I’m a cafetière.'), + ]], + ]; + } + + /** + * @test + * + * @param list $comments + * + * @dataProvider provideCommentArray + */ + public function addCommentsOnVirginContainerAddsCommentsProvided(array $comments): void + { + $this->subject->addComments($comments); + + self::assertSame($comments, $this->subject->getComments()); + } + + /** + * @test + * + * @param list $comments + * + * @dataProvider provideCommentArray + */ + public function addCommentsWithEmptyArrayKeepsOriginalCommentsUnchanged(array $comments): void + { + $this->subject->setComments($comments); + + $this->subject->addComments([]); + + self::assertSame($comments, $this->subject->getComments()); + } + + /** + * @return array}> + */ + public function provideAlternativeCommentArray(): array + { + return [ + 'no comment' => [[]], + 'one comment' => [[new Comment('Can I eat it with my hands?')]], + 'two comments' => [[ + new Comment('I’m a beer barrel.'), + new Comment('I’m a vineyard.'), + ]], + ]; + } + + /** + * @return array}> + */ + public function provideAlternativeNonemptyCommentArray(): array + { + $data = $this->provideAlternativeCommentArray(); + + unset($data['no comment']); + + return $data; + } + + /** + * This provider crosses two comment arrays (0, 1 or 2 comments) with different comments, + * so that all combinations can be tested. + * + * @return array, 1: list}> + */ + public function provideTwoDistinctCommentArrays(): array + { + return DataProviders::cross($this->provideCommentArray(), $this->provideAlternativeCommentArray()); + } + + /** + * @return array, 1: non-empty-list}> + */ + public function provideTwoDistinctCommentArraysWithSecondNonempty(): array + { + return DataProviders::cross($this->provideCommentArray(), $this->provideAlternativeNonemptyCommentArray()); + } + + private static function createContainsContstraint(Comment $comment): TraversableContains + { + return new TraversableContains($comment); + } + + /** + * @param non-empty-list $comments + * + * @return non-empty-list + */ + private static function createContainsContstraints(array $comments): array + { + return \array_map([self::class, 'createContainsContstraint'], $comments); + } + + /** + * @test + * + * @param list $commentsToAdd + * @param non-empty-list $originalComments + * + * @dataProvider provideTwoDistinctCommentArraysWithSecondNonempty + */ + public function addCommentsKeepsOriginalComments(array $commentsToAdd, array $originalComments): void + { + $this->subject->setComments($originalComments); + + $this->subject->addComments($commentsToAdd); + + self::assertThat( + $this->subject->getComments(), + LogicalAnd::fromConstraints(...self::createContainsContstraints($originalComments)) + ); + } + + /** + * @test + * + * @param list $originalComments + * @param non-empty-list $commentsToAdd + * + * @dataProvider provideTwoDistinctCommentArraysWithSecondNonempty + */ + public function addCommentsAfterCommentsSetAddsCommentsProvided(array $originalComments, array $commentsToAdd): void + { + $this->subject->setComments($originalComments); + + $this->subject->addComments($commentsToAdd); + + self::assertThat( + $this->subject->getComments(), + LogicalAnd::fromConstraints(...self::createContainsContstraints($commentsToAdd)) + ); + } + + /** + * @test + * + * @param non-empty-list $comments + * + * @dataProvider provideAlternativeNonemptyCommentArray + */ + public function addCommentsAppends(array $comments): void + { + $firstComment = new Comment('I must be first!'); + $this->subject->setComments([$firstComment]); + + $this->subject->addComments($comments); + + $result = $this->subject->getComments(); + self::assertNotEmpty($result); + self::assertSame($firstComment, $result[0]); + } + + /** + * @test + * + * @param list $comments + * + * @dataProvider provideCommentArray + */ + public function setCommentsOnVirginContainerSetsCommentsProvided(array $comments): void + { + $this->subject->setComments($comments); + + self::assertSame($comments, $this->subject->getComments()); + } + + /** + * @test + * + * @param list $originalComments + * @param list $commentsToSet + * + * @dataProvider provideTwoDistinctCommentArrays + */ + public function setCommentsReplacesWithCommentsProvided(array $originalComments, array $commentsToSet): void + { + $this->subject->setComments($originalComments); + + $this->subject->setComments($commentsToSet); + + self::assertSame($commentsToSet, $this->subject->getComments()); + } +} diff --git a/tests/Unit/Comment/Fixtures/ConcreteCommentContainer.php b/tests/Unit/Comment/Fixtures/ConcreteCommentContainer.php new file mode 100644 index 00000000..39f6ec37 --- /dev/null +++ b/tests/Unit/Comment/Fixtures/ConcreteCommentContainer.php @@ -0,0 +1,13 @@ + Date: Wed, 26 Mar 2025 00:08:20 +0000 Subject: [PATCH 414/555] [TASK] Use `CommentContainer` trait to implement `Commentable` (#1217) Closes #813. --- src/CSSList/CSSList.php | 34 ++------------------------------- src/Property/CSSNamespace.php | 35 +++------------------------------- src/Property/Charset.php | 35 +++------------------------------- src/Property/Import.php | 35 +++------------------------------- src/Rule/Rule.php | 34 +++------------------------------ src/RuleSet/RuleSet.php | 36 +++-------------------------------- 6 files changed, 17 insertions(+), 192 deletions(-) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index e6cb900f..a92d57d7 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -4,8 +4,7 @@ namespace Sabberworm\CSS\CSSList; -use Sabberworm\CSS\Comment\Comment; -use Sabberworm\CSS\Comment\Commentable; +use Sabberworm\CSS\Comment\CommentContainer; use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Parsing\ParserState; use Sabberworm\CSS\Parsing\SourceException; @@ -36,12 +35,7 @@ */ abstract class CSSList implements CSSListItem { - /** - * @var list - * - * @internal since 8.8.0 - */ - protected $comments = []; + use CommentContainer; /** * @var array, CSSListItem> @@ -444,28 +438,4 @@ public function getContents(): array { return $this->contents; } - - /** - * @param list $comments - */ - public function addComments(array $comments): void - { - $this->comments = \array_merge($this->comments, $comments); - } - - /** - * @return list - */ - public function getComments(): array - { - return $this->comments; - } - - /** - * @param list $comments - */ - public function setComments(array $comments): void - { - $this->comments = $comments; - } } diff --git a/src/Property/CSSNamespace.php b/src/Property/CSSNamespace.php index d2274d18..eef1fcf7 100644 --- a/src/Property/CSSNamespace.php +++ b/src/Property/CSSNamespace.php @@ -4,7 +4,7 @@ namespace Sabberworm\CSS\Property; -use Sabberworm\CSS\Comment\Comment; +use Sabberworm\CSS\Comment\CommentContainer; use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Value\CSSString; use Sabberworm\CSS\Value\URL; @@ -14,6 +14,8 @@ */ class CSSNamespace implements AtRule { + use CommentContainer; + /** * @var CSSString|URL */ @@ -29,13 +31,6 @@ class CSSNamespace implements AtRule */ private $lineNumber; - /** - * @var list - * - * @internal since 8.8.0 - */ - protected $comments = []; - /** * @param CSSString|URL $url * @param int<0, max> $lineNumber @@ -109,28 +104,4 @@ public function atRuleArgs(): array } return $result; } - - /** - * @param list $comments - */ - public function addComments(array $comments): void - { - $this->comments = \array_merge($this->comments, $comments); - } - - /** - * @return list - */ - public function getComments(): array - { - return $this->comments; - } - - /** - * @param list $comments - */ - public function setComments(array $comments): void - { - $this->comments = $comments; - } } diff --git a/src/Property/Charset.php b/src/Property/Charset.php index 6e81cd4e..51b43e13 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -4,7 +4,7 @@ namespace Sabberworm\CSS\Property; -use Sabberworm\CSS\Comment\Comment; +use Sabberworm\CSS\Comment\CommentContainer; use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Value\CSSString; @@ -18,6 +18,8 @@ */ class Charset implements AtRule { + use CommentContainer; + /** * @var CSSString */ @@ -30,13 +32,6 @@ class Charset implements AtRule */ protected $lineNumber; - /** - * @var list - * - * @internal since 8.8.0 - */ - protected $comments = []; - /** * @param int<0, max> $lineNumber */ @@ -88,28 +83,4 @@ public function atRuleArgs(): CSSString { return $this->charset; } - - /** - * @param list $comments - */ - public function addComments(array $comments): void - { - $this->comments = \array_merge($this->comments, $comments); - } - - /** - * @return list - */ - public function getComments(): array - { - return $this->comments; - } - - /** - * @param list $comments - */ - public function setComments(array $comments): void - { - $this->comments = $comments; - } } diff --git a/src/Property/Import.php b/src/Property/Import.php index e2c2f41f..31feaf55 100644 --- a/src/Property/Import.php +++ b/src/Property/Import.php @@ -4,7 +4,7 @@ namespace Sabberworm\CSS\Property; -use Sabberworm\CSS\Comment\Comment; +use Sabberworm\CSS\Comment\CommentContainer; use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Value\URL; @@ -13,6 +13,8 @@ */ class Import implements AtRule { + use CommentContainer; + /** * @var URL */ @@ -30,13 +32,6 @@ class Import implements AtRule */ protected $lineNumber; - /** - * @var list - * - * @internal since 8.8.0 - */ - protected $comments = []; - /** * @param int<0, max> $lineNumber */ @@ -95,30 +90,6 @@ public function atRuleArgs(): array return $result; } - /** - * @param list $comments - */ - public function addComments(array $comments): void - { - $this->comments = \array_merge($this->comments, $comments); - } - - /** - * @return list - */ - public function getComments(): array - { - return $this->comments; - } - - /** - * @param list $comments - */ - public function setComments(array $comments): void - { - $this->comments = $comments; - } - public function getMediaQuery(): ?string { return $this->mediaQuery; diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index fbd1646f..4eb7decd 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -6,6 +6,7 @@ use Sabberworm\CSS\Comment\Comment; use Sabberworm\CSS\Comment\Commentable; +use Sabberworm\CSS\Comment\CommentContainer; use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Parsing\ParserState; use Sabberworm\CSS\Parsing\UnexpectedEOFException; @@ -21,6 +22,8 @@ */ class Rule implements Renderable, Commentable { + use CommentContainer; + /** * @var non-empty-string */ @@ -48,13 +51,6 @@ class Rule implements Renderable, Commentable */ protected $columnNumber; - /** - * @var list - * - * @internal since 8.8.0 - */ - protected $comments = []; - /** * @param non-empty-string $rule * @param int<0, max> $lineNumber @@ -235,28 +231,4 @@ public function render(OutputFormat $outputFormat): string $result .= ';'; return $result; } - - /** - * @param list $comments - */ - public function addComments(array $comments): void - { - $this->comments = \array_merge($this->comments, $comments); - } - - /** - * @return list - */ - public function getComments(): array - { - return $this->comments; - } - - /** - * @param list $comments - */ - public function setComments(array $comments): void - { - $this->comments = $comments; - } } diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 871aa757..fc597ea0 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -4,8 +4,7 @@ namespace Sabberworm\CSS\RuleSet; -use Sabberworm\CSS\Comment\Comment; -use Sabberworm\CSS\Comment\Commentable; +use Sabberworm\CSS\Comment\CommentContainer; use Sabberworm\CSS\CSSList\CSSListItem; use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Parsing\ParserState; @@ -28,6 +27,8 @@ */ abstract class RuleSet implements CSSListItem { + use CommentContainer; + /** * the rules in this rule set, using the property name as the key, * with potentially multiple rules per property name. @@ -43,13 +44,6 @@ abstract class RuleSet implements CSSListItem */ protected $lineNumber; - /** - * @var list - * - * @internal since 8.8.0 - */ - protected $comments = []; - /** * @param int<0, max> $lineNumber */ @@ -299,28 +293,4 @@ protected function renderRules(OutputFormat $outputFormat): string return $formatter->removeLastSemicolon($result); } - - /** - * @param list $comments - */ - public function addComments(array $comments): void - { - $this->comments = \array_merge($this->comments, $comments); - } - - /** - * @return list - */ - public function getComments(): array - { - return $this->comments; - } - - /** - * @param list $comments - */ - public function setComments(array $comments): void - { - $this->comments = $comments; - } } From 11214f05f268304c0f2345798ada6b685e8aad3f Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Wed, 26 Mar 2025 16:46:19 +0000 Subject: [PATCH 415/555] [TASK] Prevent Dependabot updating "rawr/cross-data-providers" (#1219) Version 3.0 of this package is not compatible. --- .github/dependabot.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4f6aacc9..d9694cab 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -20,6 +20,7 @@ updates: - dependency-name: "phpstan/*" - dependency-name: "phpunit/phpunit" versions: [ ">= 9.0.0" ] + - dependency-name: "rawr/cross-data-providers" - dependency-name: "rector/rector" versioning-strategy: "increase" commit-message: From 993d6b580cb35a3e849536817ae1e54ae74cf913 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Thu, 27 Mar 2025 08:47:26 +0000 Subject: [PATCH 416/555] [TASK] Add rebasing guidelines to `CONTRIBUTING.md` (#1220) This section is copied directly from the sister project, Emogrifier. Resolves #1215. --- CONTRIBUTING.md | 70 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 292a98c1..1d0085f3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -124,3 +124,73 @@ what a commit is about. When you create a pull request, please [make your PR editable](https://github.com/blog/2247-improving-collaboration-with-forks). + +## Rebasing + +If other PRs have been merged during the time between your initial PR creation +and final approval, it may be required that you rebase your changes against the +latest `main` branch. + +There are potential pitfalls here if you follow the suggestions from `git`, +which could leave your branch in an unrecoverable mess, +and you having to start over with a new branch and new PR. + +The procedure below is tried and tested, and will help you avoid frustration. + +To rebase a feature branch to the latest `main`: + +1. Make sure that your local copy of the repository has the most up-to-date + revisions of `main` (this is important, otherwise you may end up rebasing to + an older base point): + ```bash + git switch main + git pull + ``` +1. Switch to the (feature) branch to be rebased and make sure your copy is up to + date: + ```bash + git switch feature/something-cool + git pull + ``` +1. Consider taking a copy of the folder tree at this stage; this may help when + resolving conflicts in the next step. +1. Begin the rebasing process + ```bash + git rebase main + ``` +1. Resolve the conflicts in the reported files. (This will typically require + reversing the order of the new entries in `CHANGELOG.md`.) You may use a + folder `diff` against the copy taken at step 3 to assist, but bear in mind + that at this stage `git` is partway through rebasing, so some files will have + been merged and include the latest changes from `main`, whilst others might + not. In any case, you should ignore changes to files not reported as having + conflicts. + + If there were no conflicts, skip this and the next step. +1. Mark the conflicting files as resolved and continue the rebase + ```bash + git add . + git rebase --continue + ``` + (You can alternatively use more specific wildcards or specify individual + files with a full relative path.) + + If there were no conflicts reported in the previous step, skip this step. + + If there are more conflicts to resolve, repeat the previous step then this + step again. +1. Force-push the rebased (feature) branch to the remote repository + ```bash + git push --force + ``` + The `--force` option is important. Without it, you'll get an error with a + hint suggesting a `git pull` is required: + ``` + hint: Updates were rejected because the tip of your current branch is behind + hint: its remote counterpart. Integrate the remote changes (e.g. + hint: 'git pull ...') before pushing again. + hint: See the 'Note about fast-forwards' in 'git push --help' for details. + ``` + ***DO NOT*** follow the hint and execute `git pull`. This will result in the + set of all commits on the feature branch being duplicated, and the "patching + base" not being moved at all. From 12afe20e14657740ec3551e6f871a92e3686e6be Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sat, 29 Mar 2025 07:34:45 +0000 Subject: [PATCH 417/555] [TASK] Integrate changelog entries from 8.8 release (#1222) (And one that was missed from 8.7.) --- CHANGELOG.md | 52 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c46a151f..387e912b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,6 @@ Please also have a look at our ### Added -- `OutputFormat` properties for space around specific list separators (#880) - Partial support for CSS Color Module Level 4: - `rgb` and `rgba`, and `hsl` and `hsla` are now aliases (#797} - Parse color functions that use the "modern" syntax (#800) @@ -23,14 +22,8 @@ Please also have a look at our - Initialize `KeyFrame` properties to sensible defaults (#1146) - Make `OutputFormat` `final` (#1128) -- Mark the `OutputFormat` constructor as `@internal` (#1131) -- Mark `OutputFormatter` as `@internal` (#896) - Make `Selector` a `Renderable` (#1017) -- Mark `Selector::isValid()` as `@internal` (#1037) -- Mark parsing-related methods of most CSS elements as `@internal` (#908) -- Mark `OutputFormat::nextLevel()` as `@internal` (#901) - Only allow `string` for some `OutputFormat` properties (#885) -- Make all non-private properties `@internal` (#886) - Use more native type declarations and strict mode (#641, #772, #774, #778, #804, #841, #873, #875, #891, #922, #923, #933, #958, #964, #967, #1000, #1044, #1134, #1136, #1137, #1139, #1140, #1141, #1145, @@ -40,13 +33,6 @@ Please also have a look at our ### Deprecated -- Deprecate extending `OutputFormat` (#1131) -- Deprecate magic method forwarding from `OutputFormat` to `OutputFormatter` - (#894) -- Deprecate greedy calculation of selector specificity (#1018) -- Deprecate `__toString()` (#1006) -- `OutputFormat` properties for space around list separators as an array (#880) - ### Removed - Remove `__toString()` (#1046) @@ -68,10 +54,7 @@ Please also have a look at our ### Fixed -- Include comments for all rules in declaration block (#1169) -- Render rules in line and column number order (#1059) - Don't render `rgb` colors with percentage values using hex notation (#803) -- Parse `@font-face` `src` property as comma-delimited list (#790) ### Documentation @@ -80,6 +63,41 @@ Please also have a look at our @ziegenberg is a new contributor to this release and did a lot of the heavy lifting. Thanks! :heart: +## 8.8.0: Bug fixes and deprecations + +### Added + +- `OutputFormat` properties for space around specific list separators (#880) + +### Changed + +- 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) +- Mark `OutputFormat::nextLevel()` as `@internal` (#901) +- Make all non-private properties `@internal` (#886) + +### Deprecated + +- 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) +- `OutputFormat` properties for space around list separators as an array (#880) +- Deprecate `OutputFormat::level()` (#870) + +### 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) + ## 8.7.0: Add support for PHP 8.4 ### Added From 8ea11a4ad0758b64aa3998e7bb79dc1e889c5779 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 29 Mar 2025 20:28:17 +0100 Subject: [PATCH 418/555] [TASK] Add to UML diagram that `Selector` implements `Renderable` (#1224) Followup to #1017 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3901bc37..4a9c66b0 100644 --- a/README.md +++ b/README.md @@ -719,6 +719,7 @@ classDiagram Commentable <|.. RuleSet: realization RuleSet <|-- AtRuleSet: inheritance AtRule <|.. AtRuleSet: realization + Renderable <|.. Selector: realization Selector <|-- KeyframeSelector: inheritance Renderable <|-- AtRule: inheritance Commentable <|-- AtRule: inheritance From 5b17141cb69ce8996e66b6505ba1aec65db70534 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sun, 30 Mar 2025 07:34:48 +0100 Subject: [PATCH 419/555] [TASK] Add `Positionable` interface and implementing trait (#1221) This is for CSS items which have a position in the document. New methods are added: - `getLineNumber` to replace `getLineNo`; - `getColumnNumber` to replace `getColNo`. These return a nullable `int`, instead of using zero to indicate absence. The old methods are now deprecated, but defined in the interface and implemented in the trait. Note that this change only adds the interface and trait. It does not modify any classes to actually implement or use these. Part of #1207. --- src/Position/Position.php | 68 ++++++ src/Position/Positionable.php | 45 ++++ .../Position/Fixtures/ConcretePosition.php | 13 ++ tests/Unit/Position/PositionTest.php | 194 ++++++++++++++++++ tests/UnitDeprecated/.gitkeep | 0 .../UnitDeprecated/Position/PositionTest.php | 135 ++++++++++++ 6 files changed, 455 insertions(+) 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 delete mode 100644 tests/UnitDeprecated/.gitkeep create mode 100644 tests/UnitDeprecated/Position/PositionTest.php diff --git a/src/Position/Position.php b/src/Position/Position.php new file mode 100644 index 00000000..655f34b9 --- /dev/null +++ b/src/Position/Position.php @@ -0,0 +1,68 @@ +|null + */ + protected $lineNumber; + + /** + * @var int<0, max>|null + */ + protected $columnNumber; + + /** + * @return int<1, max>|null + */ + public function getLineNumber(): ?int + { + return $this->lineNumber; + } + + /** + * @return int<0, max> + */ + public function getLineNo(): int + { + return $this->getLineNumber() ?? 0; + } + + /** + * @return int<0, max>|null + */ + public function getColumnNumber(): ?int + { + return $this->columnNumber; + } + + /** + * @return int<0, max> + */ + public function getColNo(): int + { + return $this->getColumnNumber() ?? 0; + } + + /** + * @param int<0, max>|null $lineNumber + * @param int<0, max>|null $columnNumber + */ + public function setPosition(?int $lineNumber, ?int $columnNumber = null): void + { + // 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..124752fe --- /dev/null +++ b/src/Position/Positionable.php @@ -0,0 +1,45 @@ +|null + */ + public function getLineNumber(): ?int; + + /** + * @deprecated in version 9.0.0, will be removed in v10.0. Use `getLineNumber()` instead. + * + * @return int<0, max> + */ + public function getLineNo(): int; + + /** + * @return int<0, max>|null + */ + public function getColumnNumber(): ?int; + + /** + * @deprecated in version 9.0.0, will be removed in v10.0. Use `getColumnNumber()` instead. + * + * @return int<0, max> + */ + public function getColNo(): int; + + /** + * @param int<0, max>|null $lineNumber + * Providing zero for this parameter is deprecated in version 9.0.0, and will not be supported from v10.0. + * Use `null` instead when no line number is available. + * @param int<0, max>|null $columnNumber + */ + public function setPosition(?int $lineNumber, ?int $columnNumber = null): void; +} 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(): void + { + self::assertNull($this->subject->getLineNumber()); + } + + /** + * @test + */ + public function getColumnNumberInitiallyReturnsNull(): void + { + self::assertNull($this->subject->getColumnNumber()); + } + + /** + * @return array}> + */ + public function provideLineNumber(): array + { + return [ + 'line 1' => [1], + 'line 42' => [42], + ]; + } + + /** + * @test + * + * @param int<1, max> $lineNumber + * + * @dataProvider provideLineNumber + */ + public function setPositionOnVirginSetsLineNumber(int $lineNumber): void + { + $this->subject->setPosition($lineNumber); + + self::assertSame($lineNumber, $this->subject->getLineNumber()); + } + + /** + * @test + * + * @param int<1, max> $lineNumber + * + * @dataProvider provideLineNumber + */ + public function setPositionSetsNewLineNumber(int $lineNumber): void + { + $this->subject->setPosition(99); + + $this->subject->setPosition($lineNumber); + + self::assertSame($lineNumber, $this->subject->getLineNumber()); + } + + /** + * @test + */ + public function setPositionWithNullClearsLineNumber(): void + { + $this->subject->setPosition(99); + + $this->subject->setPosition(null); + + self::assertNull($this->subject->getLineNumber()); + } + + /** + * @return array}> + */ + public function provideColumnNumber(): array + { + return [ + 'column 0' => [0], + 'column 14' => [14], + 'column 39' => [39], + ]; + } + + /** + * @test + * + * @param int<0, max> $columnNumber + * + * @dataProvider provideColumnNumber + */ + public function setPositionOnVirginSetsColumnNumber(int $columnNumber): void + { + $this->subject->setPosition(1, $columnNumber); + + self::assertSame($columnNumber, $this->subject->getColumnNumber()); + } + + /** + * @test + * + * @dataProvider provideColumnNumber + */ + public function setPositionSetsNewColumnNumber(int $columnNumber): void + { + $this->subject->setPosition(1, 99); + + $this->subject->setPosition(2, $columnNumber); + + self::assertSame($columnNumber, $this->subject->getColumnNumber()); + } + + /** + * @test + */ + public function setPositionWithoutColumnNumberClearsColumnNumber(): void + { + $this->subject->setPosition(1, 99); + + $this->subject->setPosition(2); + + self::assertNull($this->subject->getColumnNumber()); + } + + /** + * @test + */ + public function setPositionWithNullForColumnNumberClearsColumnNumber(): void + { + $this->subject->setPosition(1, 99); + + $this->subject->setPosition(2, null); + + self::assertNull($this->subject->getColumnNumber()); + } + + /** + * @return array, 1: int<0, max>}> + */ + public function provideLineAndColumnNumber(): array + { + return DataProviders::cross($this->provideLineNumber(), $this->provideColumnNumber()); + } + + /** + * @test + * + * @dataProvider provideLineAndColumnNumber + */ + public function setPositionOnVirginSetsLineAndColumnNumber(int $lineNumber, int $columnNumber): void + { + $this->subject->setPosition($lineNumber, $columnNumber); + + self::assertSame($lineNumber, $this->subject->getLineNumber()); + self::assertSame($columnNumber, $this->subject->getColumnNumber()); + } + + /** + * @test + * + * @dataProvider provideLineAndColumnNumber + */ + public function setPositionSetsNewLineAndColumnNumber(int $lineNumber, int $columnNumber): void + { + $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/.gitkeep b/tests/UnitDeprecated/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/UnitDeprecated/Position/PositionTest.php b/tests/UnitDeprecated/Position/PositionTest.php new file mode 100644 index 00000000..2e06cc6d --- /dev/null +++ b/tests/UnitDeprecated/Position/PositionTest.php @@ -0,0 +1,135 @@ +subject = new ConcretePosition(); + } + + /** + * @return array}> + */ + public function provideLineNumber(): array + { + return [ + 'line 1' => [1], + 'line 42' => [42], + ]; + } + + /** + * @return array}> + */ + public function provideColumnNumber(): array + { + return [ + 'column 0' => [0], + 'column 14' => [14], + 'column 39' => [39], + ]; + } + + /** + * @test + */ + public function getLineNoInitiallyReturnsZero(): void + { + self::assertSame(0, $this->subject->getLineNo()); + } + + /** + * @test + * + * @dataProvider provideLineNumber + */ + public function getLineNoReturnsLineNumberSet(int $lineNumber): void + { + $this->subject->setPosition($lineNumber); + + self::assertSame($lineNumber, $this->subject->getLineNo()); + } + + /** + * @test + */ + public function getLineNoReturnsZeroAfterLineNumberCleared(): void + { + $this->subject->setPosition(99); + + $this->subject->setPosition(null); + + self::assertSame(0, $this->subject->getLineNo()); + } + + /** + * @test + */ + public function getColNoInitiallyReturnsZero(): void + { + self::assertSame(0, $this->subject->getColNo()); + } + + /** + * @test + * + * @dataProvider provideColumnNumber + */ + public function getColNoReturnsColumnNumberSet(int $columnNumber): void + { + $this->subject->setPosition(1, $columnNumber); + + self::assertSame($columnNumber, $this->subject->getColNo()); + } + + /** + * @test + */ + public function getColNoReturnsZeroAfterColumnNumberCleared(): void + { + $this->subject->setPosition(1, 99); + + $this->subject->setPosition(2); + + self::assertSame(0, $this->subject->getColNo()); + } + + /** + * @test + */ + public function setPositionWithZeroClearsLineNumber(): void + { + $this->subject->setPosition(99); + + $this->subject->setPosition(0); + + self::assertNull($this->subject->getLineNumber()); + } + + /** + * @test + */ + public function getLineNoAfterSetPositionWithZeroReturnsZero(): void + { + $this->subject->setPosition(99); + + $this->subject->setPosition(0); + + self::assertSame(0, $this->subject->getLineNo()); + } +} From db60032e9da9817a4a0f093f93a31f85a778c622 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Mon, 31 Mar 2025 10:42:57 +0100 Subject: [PATCH 420/555] [TASK] Update class diagram to include `CSSListItem` (#1226) Follow-up to #1212. --- README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4a9c66b0..4ac96ece 100644 --- a/README.md +++ b/README.md @@ -622,11 +622,14 @@ class Sabberworm\CSS\CSSList\Document#4 (2) { classDiagram direction LR - %% Start of the part generated from the PHP code using tasuku43/mermaid-class-diagram + %% Start of the part originally generated from the PHP code using tasuku43/mermaid-class-diagram class Renderable { <> } + class CSSListItem { + <> + } class DeclarationBlock { } class RuleSet { @@ -715,14 +718,14 @@ classDiagram } RuleSet <|-- DeclarationBlock: inheritance - Renderable <|.. RuleSet: realization - Commentable <|.. RuleSet: realization + Renderable <|-- CSSListItem: inheritance + Commentable <|-- CSSListItem: inheritance + CSSListItem <|.. RuleSet: realization RuleSet <|-- AtRuleSet: inheritance AtRule <|.. AtRuleSet: realization Renderable <|.. Selector: realization Selector <|-- KeyframeSelector: inheritance - Renderable <|-- AtRule: inheritance - Commentable <|-- AtRule: inheritance + CSSListItem <|-- AtRule: inheritance AtRule <|.. Charset: realization AtRule <|.. Import: realization AtRule <|.. CSSNamespace: realization @@ -734,8 +737,7 @@ classDiagram SourceException <|-- UnexpectedTokenException: inheritance CSSList <|-- CSSBlockList: inheritance CSSBlockList <|-- Document: inheritance - Renderable <|.. CSSList: realization - Commentable <|.. CSSList: realization + CSSListItem <|.. CSSList: realization CSSList <|-- KeyFrame: inheritance AtRule <|.. KeyFrame: realization CSSBlockList <|-- AtRuleBlockList: inheritance From e2d9582349facb930b2aec5df4d15565efa43755 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 31 Mar 2025 16:22:42 +0200 Subject: [PATCH 421/555] [CLEANUP] Autoformat the code and drop unused imports (#1228) --- src/CSSList/CSSList.php | 1 - src/Position/Positionable.php | 8 ++++---- src/Property/AtRule.php | 2 -- src/RuleSet/RuleSet.php | 1 - tests/ParserTest.php | 14 +++++++------- tests/Unit/Comment/CommentContainerTest.php | 20 ++++++++++++-------- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index a92d57d7..f55e0f05 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -15,7 +15,6 @@ use Sabberworm\CSS\Property\CSSNamespace; use Sabberworm\CSS\Property\Import; use Sabberworm\CSS\Property\Selector; -use Sabberworm\CSS\Renderable; use Sabberworm\CSS\RuleSet\AtRuleSet; use Sabberworm\CSS\RuleSet\DeclarationBlock; use Sabberworm\CSS\RuleSet\RuleSet; diff --git a/src/Position/Positionable.php b/src/Position/Positionable.php index 124752fe..e4e9d0ab 100644 --- a/src/Position/Positionable.php +++ b/src/Position/Positionable.php @@ -17,9 +17,9 @@ interface Positionable public function getLineNumber(): ?int; /** - * @deprecated in version 9.0.0, will be removed in v10.0. Use `getLineNumber()` instead. - * * @return int<0, max> + * + * @deprecated in version 9.0.0, will be removed in v10.0. Use `getLineNumber()` instead. */ public function getLineNo(): int; @@ -29,9 +29,9 @@ public function getLineNo(): int; public function getColumnNumber(): ?int; /** - * @deprecated in version 9.0.0, will be removed in v10.0. Use `getColumnNumber()` instead. - * * @return int<0, max> + * + * @deprecated in version 9.0.0, will be removed in v10.0. Use `getColumnNumber()` instead. */ public function getColNo(): int; diff --git a/src/Property/AtRule.php b/src/Property/AtRule.php index 8b0d41a1..49a160a1 100644 --- a/src/Property/AtRule.php +++ b/src/Property/AtRule.php @@ -4,9 +4,7 @@ namespace Sabberworm\CSS\Property; -use Sabberworm\CSS\Comment\Commentable; use Sabberworm\CSS\CSSList\CSSListItem; -use Sabberworm\CSS\Renderable; /** * Note that `CSSListItem` extends both `Commentable` and `Renderable`, diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index fc597ea0..8c711cf9 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -10,7 +10,6 @@ use Sabberworm\CSS\Parsing\ParserState; use Sabberworm\CSS\Parsing\UnexpectedEOFException; use Sabberworm\CSS\Parsing\UnexpectedTokenException; -use Sabberworm\CSS\Renderable; use Sabberworm\CSS\Rule\Rule; /** diff --git a/tests/ParserTest.php b/tests/ParserTest.php index 7d1b528c..f0fee35b 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -980,11 +980,11 @@ public function lineNumbersParsing(): void // So a more explicit type check is required. // TODO: tidy this up when an interface with `getLineNo()` is added. if ( - !$contentItem instanceof Charset && - !$contentItem instanceof CSSList && - !$contentItem instanceof CSSNamespace && - !$contentItem instanceof Import && - !$contentItem instanceof RuleSet + !$contentItem instanceof Charset + && !$contentItem instanceof CSSList + && !$contentItem instanceof CSSNamespace + && !$contentItem instanceof Import + && !$contentItem instanceof RuleSet ) { self::fail('Content item is not of an expected type. It\'s a `' . \get_class($contentItem) . '`.'); } @@ -992,8 +992,8 @@ public function lineNumbersParsing(): void if ($contentItem instanceof KeyFrame) { foreach ($contentItem->getContents() as $block) { if ( - !$block instanceof CSSList && - !$block instanceof RuleSet + !$block instanceof CSSList + && !$block instanceof RuleSet ) { self::fail( 'KeyFrame content item is not of an expected type. It\'s a `' . \get_class($block) . '`.' diff --git a/tests/Unit/Comment/CommentContainerTest.php b/tests/Unit/Comment/CommentContainerTest.php index a29691b9..80bd6be2 100644 --- a/tests/Unit/Comment/CommentContainerTest.php +++ b/tests/Unit/Comment/CommentContainerTest.php @@ -42,10 +42,12 @@ public function provideCommentArray(): array return [ 'no comment' => [[]], 'one comment' => [[new Comment('Is this really a spoon?')]], - 'two comments' => [[ - new Comment('I’m a teapot.'), - new Comment('I’m a cafetière.'), - ]], + 'two comments' => [ + [ + new Comment('I’m a teapot.'), + new Comment('I’m a cafetière.'), + ], + ], ]; } @@ -87,10 +89,12 @@ public function provideAlternativeCommentArray(): array return [ 'no comment' => [[]], 'one comment' => [[new Comment('Can I eat it with my hands?')]], - 'two comments' => [[ - new Comment('I’m a beer barrel.'), - new Comment('I’m a vineyard.'), - ]], + 'two comments' => [ + [ + new Comment('I’m a beer barrel.'), + new Comment('I’m a vineyard.'), + ], + ], ]; } From a675d1249e98f2ef9c8222f426d835b234179a24 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 31 Mar 2025 16:23:40 +0200 Subject: [PATCH 422/555] [CLEANUP] Fix typos in test method names (#1229) --- tests/Unit/Comment/CommentContainerTest.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/Unit/Comment/CommentContainerTest.php b/tests/Unit/Comment/CommentContainerTest.php index 80bd6be2..8c994083 100644 --- a/tests/Unit/Comment/CommentContainerTest.php +++ b/tests/Unit/Comment/CommentContainerTest.php @@ -129,7 +129,7 @@ public function provideTwoDistinctCommentArraysWithSecondNonempty(): array return DataProviders::cross($this->provideCommentArray(), $this->provideAlternativeNonemptyCommentArray()); } - private static function createContainsContstraint(Comment $comment): TraversableContains + private static function createContainsConstraint(Comment $comment): TraversableContains { return new TraversableContains($comment); } @@ -139,9 +139,9 @@ private static function createContainsContstraint(Comment $comment): Traversable * * @return non-empty-list */ - private static function createContainsContstraints(array $comments): array + private static function createContainsConstraints(array $comments): array { - return \array_map([self::class, 'createContainsContstraint'], $comments); + return \array_map([self::class, 'createContainsConstraint'], $comments); } /** @@ -160,7 +160,7 @@ public function addCommentsKeepsOriginalComments(array $commentsToAdd, array $or self::assertThat( $this->subject->getComments(), - LogicalAnd::fromConstraints(...self::createContainsContstraints($originalComments)) + LogicalAnd::fromConstraints(...self::createContainsConstraints($originalComments)) ); } @@ -180,7 +180,7 @@ public function addCommentsAfterCommentsSetAddsCommentsProvided(array $originalC self::assertThat( $this->subject->getComments(), - LogicalAnd::fromConstraints(...self::createContainsContstraints($commentsToAdd)) + LogicalAnd::fromConstraints(...self::createContainsConstraints($commentsToAdd)) ); } From 36ed5cdf12ebd46a4f3803ba037f86c8fe0894d9 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 31 Mar 2025 16:31:21 +0200 Subject: [PATCH 423/555] [TASK] Migrate to `rawr/phpunit-data-provider` (#1227) The package `rawr/cross-data-providers` that we used has been abandoned and should not be used anymore. --- .github/dependabot.yml | 1 - composer.json | 2 +- tests/Unit/Comment/CommentContainerTest.php | 14 +++++++------- tests/Unit/Position/PositionTest.php | 8 ++++---- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d9694cab..4f6aacc9 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -20,7 +20,6 @@ updates: - dependency-name: "phpstan/*" - dependency-name: "phpunit/phpunit" versions: [ ">= 9.0.0" ] - - dependency-name: "rawr/cross-data-providers" - dependency-name: "rector/rector" versioning-strategy: "increase" commit-message: diff --git a/composer.json b/composer.json index 192eea6d..1aa6895c 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "phpstan/phpstan-phpunit": "1.4.2 || 2.0.4", "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.3", "phpunit/phpunit": "8.5.41", - "rawr/cross-data-providers": "2.4.0", + "rawr/phpunit-data-provider": "3.3.1", "rector/rector": "1.2.10 || 2.0.7", "rector/type-perfect": "1.0.0 || 2.0.2" }, diff --git a/tests/Unit/Comment/CommentContainerTest.php b/tests/Unit/Comment/CommentContainerTest.php index 8c994083..d0e844a4 100644 --- a/tests/Unit/Comment/CommentContainerTest.php +++ b/tests/Unit/Comment/CommentContainerTest.php @@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase; use Sabberworm\CSS\Comment\Comment; use Sabberworm\CSS\Tests\Unit\Comment\Fixtures\ConcreteCommentContainer; -use TRegx\DataProvider\DataProviders; +use TRegx\PhpUnit\DataProviders\DataProvider; /** * @covers \Sabberworm\CSS\Comment\CommentContainer @@ -114,19 +114,19 @@ public function provideAlternativeNonemptyCommentArray(): array * This provider crosses two comment arrays (0, 1 or 2 comments) with different comments, * so that all combinations can be tested. * - * @return array, 1: list}> + * @return DataProvider, 1: list}> */ - public function provideTwoDistinctCommentArrays(): array + public function provideTwoDistinctCommentArrays(): DataProvider { - return DataProviders::cross($this->provideCommentArray(), $this->provideAlternativeCommentArray()); + return DataProvider::cross($this->provideCommentArray(), $this->provideAlternativeCommentArray()); } /** - * @return array, 1: non-empty-list}> + * @return DataProvider, 1: non-empty-list}> */ - public function provideTwoDistinctCommentArraysWithSecondNonempty(): array + public function provideTwoDistinctCommentArraysWithSecondNonempty(): DataProvider { - return DataProviders::cross($this->provideCommentArray(), $this->provideAlternativeNonemptyCommentArray()); + return DataProvider::cross($this->provideCommentArray(), $this->provideAlternativeNonemptyCommentArray()); } private static function createContainsConstraint(Comment $comment): TraversableContains diff --git a/tests/Unit/Position/PositionTest.php b/tests/Unit/Position/PositionTest.php index 86233d7c..e779500e 100644 --- a/tests/Unit/Position/PositionTest.php +++ b/tests/Unit/Position/PositionTest.php @@ -6,7 +6,7 @@ use PHPUnit\Framework\TestCase; use Sabberworm\CSS\Tests\Unit\Position\Fixtures\ConcretePosition; -use TRegx\DataProvider\DataProviders; +use TRegx\PhpUnit\DataProviders\DataProvider; /** * @covers \Sabberworm\CSS\Position\Position @@ -157,11 +157,11 @@ public function setPositionWithNullForColumnNumberClearsColumnNumber(): void } /** - * @return array, 1: int<0, max>}> + * @return DataProvider, 1: int<0, max>}> */ - public function provideLineAndColumnNumber(): array + public function provideLineAndColumnNumber(): DataProvider { - return DataProviders::cross($this->provideLineNumber(), $this->provideColumnNumber()); + return DataProvider::cross($this->provideLineNumber(), $this->provideColumnNumber()); } /** From 9b526151d96e0f5bf00150fd3324f15ce382f75e Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Tue, 1 Apr 2025 09:32:54 +0100 Subject: [PATCH 424/555] [TASK] Implement `Positionable` (#1225) Closes #1207. --- CHANGELOG.md | 16 +++++++++++ README.md | 12 ++++++++ src/CSSList/CSSList.php | 22 ++++----------- src/Comment/Comment.php | 21 ++++---------- src/Parsing/SourceException.php | 20 ++++--------- src/Property/CSSNamespace.php | 20 ++++--------- src/Property/Charset.php | 22 ++++----------- src/Property/Import.php | 22 ++++----------- src/Rule/Rule.php | 48 ++++---------------------------- src/RuleSet/DeclarationBlock.php | 5 +++- src/RuleSet/RuleSet.php | 22 ++++----------- src/Value/CSSFunction.php | 2 +- src/Value/Value.php | 21 ++++---------- 13 files changed, 80 insertions(+), 173 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 387e912b..d78b5796 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,12 @@ Please also have a look at our ### 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) - Partial support for CSS Color Module Level 4: - `rgb` and `rgba`, and `hsl` and `hsla` are now aliases (#797} - Parse color functions that use the "modern" syntax (#800) @@ -20,6 +26,9 @@ Please also have a look at our ### Changed +- Implement `Positionable` in the following CSS item classes: + `Comment`, `CSSList`, `SourceException`, `Charset`, `CSSNamespace`, `Import`, + `Rule`, `DeclarationBlock`, `RuleSet`, `CSSFunction`, `Value` (#1225) - Initialize `KeyFrame` properties to sensible defaults (#1146) - Make `OutputFormat` `final` (#1128) - Make `Selector` a `Renderable` (#1017) @@ -33,6 +42,13 @@ Please also have a look at our ### Deprecated +- `getLineNo()` is deprecated in these classes (use `getLineNumber()` instead): + `Comment`, `CSSList`, `SourceException`, `Charset`, `CSSNamespace`, `Import`, + `Rule`, `DeclarationBlock`, `RuleSet`, `CSSFunction`, `Value` (#1225) +- `Rule::getColNo()` is deprecated (use `getColumnNumber()` instead) (#1225) +- Providing zero as the line number argument to `Rule::setPosition()` is + deprecated (pass `null` instead if there is no line number) (#1225) + ### Removed - Remove `__toString()` (#1046) diff --git a/README.md b/README.md index 4ac96ece..e5dd70db 100644 --- a/README.md +++ b/README.md @@ -627,6 +627,9 @@ classDiagram class Renderable { <> } + class Positionable { + <> + } class CSSListItem { <> } @@ -720,23 +723,30 @@ classDiagram RuleSet <|-- DeclarationBlock: inheritance Renderable <|-- CSSListItem: inheritance Commentable <|-- CSSListItem: inheritance + Positionable <|.. RuleSet: realization CSSListItem <|.. RuleSet: realization RuleSet <|-- AtRuleSet: inheritance AtRule <|.. AtRuleSet: realization Renderable <|.. Selector: realization Selector <|-- KeyframeSelector: inheritance CSSListItem <|-- AtRule: inheritance + Positionable <|.. Charset: realization AtRule <|.. Charset: realization + Positionable <|.. Import: realization AtRule <|.. Import: realization + Positionable <|.. CSSNamespace: realization AtRule <|.. CSSNamespace: realization Renderable <|.. Rule: realization + Positionable <|.. Rule: realization Commentable <|.. Rule: realization SourceException <|-- OutputException: inheritance UnexpectedTokenException <|-- UnexpectedEOFException: inheritance Exception <|-- SourceException: inheritance + Positionable <|.. SourceException: realization SourceException <|-- UnexpectedTokenException: inheritance CSSList <|-- CSSBlockList: inheritance CSSBlockList <|-- Document: inheritance + Positionable <|.. CSSList: realization CSSListItem <|.. CSSList: realization CSSList <|-- KeyFrame: inheritance AtRule <|.. KeyFrame: realization @@ -749,12 +759,14 @@ classDiagram CSSFunction <|-- CalcFunction: inheritance ValueList <|-- LineName: inheritance Renderable <|.. Value: realization + Positionable <|.. Value: realization PrimitiveValue <|-- Size: inheritance PrimitiveValue <|-- CSSString: inheritance Value <|-- PrimitiveValue: inheritance ValueList <|-- CSSFunction: inheritance ValueList <|-- RuleValueList: inheritance Renderable <|.. Comment: realization + Positionable <|.. Comment: realization %% end of the generated part diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index f55e0f05..45deccdd 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -10,6 +10,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; @@ -32,9 +34,10 @@ * Note that `CSSListItem` extends both `Commentable` and `Renderable`, * so those interfaces must also be implemented by concrete subclasses. */ -abstract class CSSList implements CSSListItem +abstract class CSSList implements CSSListItem, Positionable { use CommentContainer; + use Position; /** * @var array, CSSListItem> @@ -43,19 +46,12 @@ abstract class CSSList implements CSSListItem */ protected $contents = []; - /** - * @var int<0, max> - * - * @internal since 8.8.0 - */ - protected $lineNumber; - /** * @param int<0, max> $lineNumber */ public function __construct(int $lineNumber = 0) { - $this->lineNumber = $lineNumber; + $this->setPosition($lineNumber); } /** @@ -249,14 +245,6 @@ private static function identifierIs(string $identifier, string $match): bool ?: \preg_match("/^(-\\w+-)?$match$/i", $identifier) === 1; } - /** - * @return int<0, max> - */ - public function getLineNo(): int - { - return $this->lineNumber; - } - /** * Prepends an item to the list of contents. */ diff --git a/src/Comment/Comment.php b/src/Comment/Comment.php index cb19917c..7a56624c 100644 --- a/src/Comment/Comment.php +++ b/src/Comment/Comment.php @@ -6,15 +6,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<0, max> - * - * @internal since 8.8.0 - */ - protected $lineNumber; + use Position; /** * @var string @@ -29,7 +26,7 @@ class Comment implements Renderable public function __construct(string $commentText = '', int $lineNumber = 0) { $this->commentText = $commentText; - $this->lineNumber = $lineNumber; + $this->setPosition($lineNumber); } public function getComment(): string @@ -37,14 +34,6 @@ public function getComment(): string return $this->commentText; } - /** - * @return int<0, max> - */ - public function getLineNo(): int - { - return $this->lineNumber; - } - public function setComment(string $commentText): void { $this->commentText = $commentText; diff --git a/src/Parsing/SourceException.php b/src/Parsing/SourceException.php index 45b35772..ca07cc48 100644 --- a/src/Parsing/SourceException.php +++ b/src/Parsing/SourceException.php @@ -4,30 +4,22 @@ 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<0, max> - */ - private $lineNumber; + use Position; /** * @param int<0, max> $lineNumber */ public function __construct(string $message, int $lineNumber = 0) { - $this->lineNumber = $lineNumber; + $this->setPosition($lineNumber); if ($lineNumber !== 0) { $message .= " [line no: $lineNumber]"; } parent::__construct($message); } - - /** - * @return int<0, max> - */ - public function getLineNo(): int - { - return $this->lineNumber; - } } diff --git a/src/Property/CSSNamespace.php b/src/Property/CSSNamespace.php index eef1fcf7..e6a2983c 100644 --- a/src/Property/CSSNamespace.php +++ b/src/Property/CSSNamespace.php @@ -6,15 +6,18 @@ use Sabberworm\CSS\Comment\CommentContainer; use Sabberworm\CSS\OutputFormat; +use Sabberworm\CSS\Position\Position; +use Sabberworm\CSS\Position\Positionable; use Sabberworm\CSS\Value\CSSString; use Sabberworm\CSS\Value\URL; /** * `CSSNamespace` represents an `@namespace` rule. */ -class CSSNamespace implements AtRule +class CSSNamespace implements AtRule, Positionable { use CommentContainer; + use Position; /** * @var CSSString|URL @@ -26,11 +29,6 @@ class CSSNamespace implements AtRule */ private $prefix; - /** - * @var int<0, max> $lineNumber - */ - private $lineNumber; - /** * @param CSSString|URL $url * @param int<0, max> $lineNumber @@ -39,15 +37,7 @@ public function __construct($url, ?string $prefix = null, int $lineNumber = 0) { $this->url = $url; $this->prefix = $prefix; - $this->lineNumber = $lineNumber; - } - - /** - * @return int<0, max> - */ - public function getLineNo(): int - { - return $this->lineNumber; + $this->setPosition($lineNumber); } /** diff --git a/src/Property/Charset.php b/src/Property/Charset.php index 51b43e13..90e7d5fb 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -6,6 +6,8 @@ use Sabberworm\CSS\Comment\CommentContainer; use Sabberworm\CSS\OutputFormat; +use Sabberworm\CSS\Position\Position; +use Sabberworm\CSS\Position\Positionable; use Sabberworm\CSS\Value\CSSString; /** @@ -16,37 +18,23 @@ * - 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 CommentContainer; + use Position; /** * @var CSSString */ private $charset; - /** - * @var int<0, max> - * - * @internal since 8.8.0 - */ - protected $lineNumber; - /** * @param int<0, max> $lineNumber */ public function __construct(CSSString $charset, int $lineNumber = 0) { $this->charset = $charset; - $this->lineNumber = $lineNumber; - } - - /** - * @return int<0, max> - */ - public function getLineNo(): int - { - return $this->lineNumber; + $this->setPosition($lineNumber); } /** diff --git a/src/Property/Import.php b/src/Property/Import.php index 31feaf55..51c0e4ea 100644 --- a/src/Property/Import.php +++ b/src/Property/Import.php @@ -6,14 +6,17 @@ use Sabberworm\CSS\Comment\CommentContainer; 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 CommentContainer; + use Position; /** * @var URL @@ -25,13 +28,6 @@ class Import implements AtRule */ private $mediaQuery; - /** - * @var int<0, max> - * - * @internal since 8.8.0 - */ - protected $lineNumber; - /** * @param int<0, max> $lineNumber */ @@ -39,15 +35,7 @@ public function __construct(URL $location, ?string $mediaQuery, int $lineNumber { $this->location = $location; $this->mediaQuery = $mediaQuery; - $this->lineNumber = $lineNumber; - } - - /** - * @return int<0, max> - */ - public function getLineNo(): int - { - return $this->lineNumber; + $this->setPosition($lineNumber); } public function setLocation(URL $location): void diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index 4eb7decd..af93dcde 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -11,6 +11,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; @@ -20,9 +22,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 CommentContainer; + use Position; /** * @var non-empty-string @@ -39,18 +42,6 @@ class Rule implements Renderable, Commentable */ private $isImportant = false; - /** - * @var int<0, max> $lineNumber - */ - protected $lineNumber; - - /** - * @var int<0, max> - * - * @internal since 8.8.0 - */ - protected $columnNumber; - /** * @param non-empty-string $rule * @param int<0, max> $lineNumber @@ -59,8 +50,7 @@ class Rule implements Renderable, Commentable public function __construct(string $rule, int $lineNumber = 0, int $columnNumber = 0) { $this->rule = $rule; - $this->lineNumber = $lineNumber; - $this->columnNumber = $columnNumber; + $this->setPosition($lineNumber, $columnNumber); } /** @@ -122,32 +112,6 @@ private static function listDelimiterForRule(string $rule): array } } - /** - * @return int<0, max> - */ - public function getLineNo(): int - { - return $this->lineNumber; - } - - /** - * @return int<0, max> - */ - public function getColNo(): int - { - return $this->columnNumber; - } - - /** - * @param int<0, max> $lineNumber - * @param int<0, max> $columnNumber - */ - public function setPosition(int $lineNumber, int $columnNumber): void - { - $this->columnNumber = $columnNumber; - $this->lineNumber = $lineNumber; - } - /** * @param non-empty-string $rule */ @@ -193,7 +157,7 @@ public function addValue($value, string $type = ' '): void } if (!($this->value instanceof RuleValueList) || $this->value->getListSeparator() !== $type) { $currentValue = $this->value; - $this->value = new RuleValueList($type, $this->lineNumber); + $this->value = new RuleValueList($type, $this->getLineNumber()); if ($currentValue) { $this->value->addListComponent($currentValue); } diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 06829fa2..e4125797 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -146,7 +146,10 @@ public function render(OutputFormat $outputFormat): string $result = $formatter->comments($this); if (\count($this->selectors) === 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->lineNumber); + throw new OutputException( + 'Attempt to print declaration block with missing selector', + $this->getLineNumber() + ); } $result .= $outputFormat->getContentBeforeDeclarationBlock(); $result .= $formatter->implode( diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 8c711cf9..1d5b2b4c 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -10,6 +10,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\Rule\Rule; /** @@ -24,9 +26,10 @@ * Note that `CSSListItem` extends both `Commentable` and `Renderable`, * so those interfaces must also be implemented by concrete subclasses. */ -abstract class RuleSet implements CSSListItem +abstract class RuleSet implements CSSListItem, Positionable { use CommentContainer; + use Position; /** * the rules in this rule set, using the property name as the key, @@ -36,19 +39,12 @@ abstract class RuleSet implements CSSListItem */ private $rules = []; - /** - * @var int<0, max> - * - * @internal since 8.8.0 - */ - protected $lineNumber; - /** * @param int<0, max> $lineNumber */ public function __construct(int $lineNumber = 0) { - $this->lineNumber = $lineNumber; + $this->setPosition($lineNumber); } /** @@ -97,14 +93,6 @@ public static function parseRuleSet(ParserState $parserState, RuleSet $ruleSet): $parserState->consume('}'); } - /** - * @return int<0, max> - */ - public function getLineNo(): int - { - return $this->lineNumber; - } - public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void { $propertyName = $ruleToAdd->getRule(); diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index 8671fd3e..f78f7cb6 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -36,7 +36,7 @@ public function __construct(string $name, $arguments, string $separator = ',', i $arguments = $arguments->getListComponents(); } $this->name = $name; - $this->lineNumber = $lineNumber; + $this->setPosition($lineNumber); // TODO: redundant? parent::__construct($arguments, $separator, $lineNumber); } diff --git a/src/Value/Value.php b/src/Value/Value.php index 3432b18c..3e51d8d0 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -8,27 +8,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<0, max> - * - * @internal since 8.8.0 - */ - protected $lineNumber; + use Position; /** * @param int<0, max> $lineNumber */ public function __construct(int $lineNumber = 0) { - $this->lineNumber = $lineNumber; + $this->setPosition($lineNumber); } /** @@ -211,12 +208,4 @@ private static function parseUnicodeRangeValue(ParserState $parserState): string return "U+{$range}"; } - - /** - * @return int<0, max> - */ - public function getLineNo(): int - { - return $this->lineNumber; - } } From 6f3e955afb049b3d0c33b29f33ab2fc8af536bdb Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Mon, 7 Apr 2025 10:22:26 +0100 Subject: [PATCH 425/555] [TASK] Deprecate `getLineNo()` etc. in v8.9.0 (#1233) The deprecation and changes from #1207 can be merged to 8.x. See #1232. So the deprecation can be brought forward. --- src/Position/Positionable.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Position/Positionable.php b/src/Position/Positionable.php index e4e9d0ab..25a8295a 100644 --- a/src/Position/Positionable.php +++ b/src/Position/Positionable.php @@ -19,7 +19,7 @@ public function getLineNumber(): ?int; /** * @return int<0, max> * - * @deprecated in version 9.0.0, will be removed in v10.0. Use `getLineNumber()` instead. + * @deprecated in version 8.9.0, will be removed in v9.0. Use `getLineNumber()` instead. */ public function getLineNo(): int; @@ -31,13 +31,13 @@ public function getColumnNumber(): ?int; /** * @return int<0, max> * - * @deprecated in version 9.0.0, will be removed in v10.0. Use `getColumnNumber()` instead. + * @deprecated in version 8.9.0, will be removed in v9.0. Use `getColumnNumber()` instead. */ public function getColNo(): int; /** * @param int<0, max>|null $lineNumber - * Providing zero for this parameter is deprecated in version 9.0.0, and will not be supported from v10.0. + * 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 */ From 4e107ac071a07a196e06b3c9efece231f84ed511 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Mon, 7 Apr 2025 18:13:58 +0100 Subject: [PATCH 426/555] [TASK] Update CHANGELOG for #1233 (#1235) --- CHANGELOG.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d78b5796..0549f23a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,10 +44,11 @@ Please also have a look at our - `getLineNo()` is deprecated in these classes (use `getLineNumber()` instead): `Comment`, `CSSList`, `SourceException`, `Charset`, `CSSNamespace`, `Import`, - `Rule`, `DeclarationBlock`, `RuleSet`, `CSSFunction`, `Value` (#1225) -- `Rule::getColNo()` is deprecated (use `getColumnNumber()` instead) (#1225) + `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) + deprecated (pass `null` instead if there is no line number) (#1225, #1233) ### Removed From 3ab40c6cf619ad1aeb16c74a5045aecf60428653 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Wed, 9 Apr 2025 13:41:36 +0100 Subject: [PATCH 427/555] [BUGFIX] Correct `AtRuleBlockListTest::implementsAtRule()` (#1238) Use the interface type to be tested for in the assertion, not the type of the object itself. --- tests/Unit/CSSList/AtRuleBlockListTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Unit/CSSList/AtRuleBlockListTest.php b/tests/Unit/CSSList/AtRuleBlockListTest.php index bc83ccc8..6688d079 100644 --- a/tests/Unit/CSSList/AtRuleBlockListTest.php +++ b/tests/Unit/CSSList/AtRuleBlockListTest.php @@ -9,6 +9,7 @@ use Sabberworm\CSS\CSSList\AtRuleBlockList; use Sabberworm\CSS\CSSList\CSSBlockList; use Sabberworm\CSS\CSSList\CSSList; +use Sabberworm\CSS\Property\AtRule; use Sabberworm\CSS\Renderable; /** @@ -25,7 +26,7 @@ public function implementsAtRule(): void { $subject = new AtRuleBlockList('supports'); - self::assertInstanceOf(AtRuleBlockList::class, $subject); + self::assertInstanceOf(AtRule::class, $subject); } /** From b74cf2e47c252647da35f2ff6e3e300e2c5b2f5e Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Wed, 9 Apr 2025 13:44:00 +0100 Subject: [PATCH 428/555] [TASK] Add and implement `CSSElement` interface (#1231) Also add tests to confirm that the supplanted types in the DocBlock actually implement the new interface. And correct a DocBlock type to also allow `string`, which is currently possible. cf. #1230 --- CHANGELOG.md | 1 + README.md | 10 ++++++-- src/CSSElement.php | 17 +++++++++++++ src/CSSList/CSSBlockList.php | 3 ++- src/CSSList/CSSList.php | 3 ++- src/CSSList/Document.php | 3 ++- src/Rule/Rule.php | 4 +-- src/RuleSet/RuleSet.php | 3 ++- src/Value/Value.php | 4 +-- tests/Unit/CSSList/CSSListTest.php | 11 ++++++++ tests/Unit/Rule/RuleTest.php | 11 ++++++++ .../Unit/RuleSet/Fixtures/ConcreteRuleSet.php | 19 ++++++++++++++ tests/Unit/RuleSet/RuleSetTest.php | 25 +++++++++++++++++++ tests/Unit/Value/Fixtures/ConcreteValue.php | 19 ++++++++++++++ tests/Unit/Value/ValueTest.php | 12 +++++++++ 15 files changed, 135 insertions(+), 10 deletions(-) create mode 100644 src/CSSElement.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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 0549f23a..a74924aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Please also have a look at our ### Added +- Add Interface `CSSElement` (#1231) - Methods `getLineNumber` and `getColumnNumber` which return a nullable `int` for the following classes: `Comment`, `CSSList`, `SourceException`, `Charset`, `CSSNamespace`, `Import`, diff --git a/README.md b/README.md index e5dd70db..b69cb6d2 100644 --- a/README.md +++ b/README.md @@ -624,6 +624,9 @@ classDiagram %% Start of the part originally generated from the PHP code using tasuku43/mermaid-class-diagram + class CSSElement { + <> + } class Renderable { <> } @@ -721,9 +724,11 @@ classDiagram } RuleSet <|-- DeclarationBlock: inheritance + Renderable <|-- CSSElement: inheritance Renderable <|-- CSSListItem: inheritance Commentable <|-- CSSListItem: inheritance Positionable <|.. RuleSet: realization + CSSElement <|.. RuleSet: realization CSSListItem <|.. RuleSet: realization RuleSet <|-- AtRuleSet: inheritance AtRule <|.. AtRuleSet: realization @@ -736,7 +741,7 @@ classDiagram AtRule <|.. Import: realization Positionable <|.. CSSNamespace: realization AtRule <|.. CSSNamespace: realization - Renderable <|.. Rule: realization + CSSElement <|.. Rule: realization Positionable <|.. Rule: realization Commentable <|.. Rule: realization SourceException <|-- OutputException: inheritance @@ -746,6 +751,7 @@ classDiagram SourceException <|-- UnexpectedTokenException: inheritance CSSList <|-- CSSBlockList: inheritance CSSBlockList <|-- Document: inheritance + CSSElement <|.. CSSList: realization Positionable <|.. CSSList: realization CSSListItem <|.. CSSList: realization CSSList <|-- KeyFrame: inheritance @@ -758,7 +764,7 @@ classDiagram Value <|-- ValueList: inheritance CSSFunction <|-- CalcFunction: inheritance ValueList <|-- LineName: inheritance - Renderable <|.. Value: realization + CSSElement <|.. Value: realization Positionable <|.. Value: realization PrimitiveValue <|-- Size: inheritance PrimitiveValue <|-- CSSString: inheritance 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 @@ + $result */ protected function allValues( diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 45deccdd..1942d8c9 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -5,6 +5,7 @@ namespace Sabberworm\CSS\CSSList; use Sabberworm\CSS\Comment\CommentContainer; +use Sabberworm\CSS\CSSElement; use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Parsing\ParserState; use Sabberworm\CSS\Parsing\SourceException; @@ -34,7 +35,7 @@ * Note that `CSSListItem` extends both `Commentable` and `Renderable`, * so those interfaces must also be implemented by concrete subclasses. */ -abstract class CSSList implements CSSListItem, Positionable +abstract class CSSList implements CSSElement, CSSListItem, Positionable { use CommentContainer; use Position; diff --git a/src/CSSList/Document.php b/src/CSSList/Document.php index 10370dc0..f061b427 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -4,6 +4,7 @@ namespace Sabberworm\CSS\CSSList; +use Sabberworm\CSS\CSSElement; use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Parsing\ParserState; use Sabberworm\CSS\Parsing\SourceException; @@ -33,7 +34,7 @@ public static function parse(ParserState $parserState): Document /** * Returns all `Value` objects found recursively in `Rule`s in the tree. * - * @param CSSList|RuleSet|string $element + * @param CSSElement|string $element * 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 $searchInFunctionArguments whether to also return Value objects used as Function arguments. diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index af93dcde..304e6628 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -7,13 +7,13 @@ use Sabberworm\CSS\Comment\Comment; use Sabberworm\CSS\Comment\Commentable; use Sabberworm\CSS\Comment\CommentContainer; +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; @@ -22,7 +22,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 CommentContainer; use Position; diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 1d5b2b4c..3a7552e3 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -5,6 +5,7 @@ namespace Sabberworm\CSS\RuleSet; use Sabberworm\CSS\Comment\CommentContainer; +use Sabberworm\CSS\CSSElement; use Sabberworm\CSS\CSSList\CSSListItem; use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Parsing\ParserState; @@ -26,7 +27,7 @@ * Note that `CSSListItem` extends both `Commentable` and `Renderable`, * so those interfaces must also be implemented by concrete subclasses. */ -abstract class RuleSet implements CSSListItem, Positionable +abstract class RuleSet implements CSSElement, CSSListItem, Positionable { use CommentContainer; use Position; diff --git a/src/Value/Value.php b/src/Value/Value.php index 3e51d8d0..e33a2949 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -4,19 +4,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/CSSListTest.php b/tests/Unit/CSSList/CSSListTest.php index 69ee4d05..abe87dd6 100644 --- a/tests/Unit/CSSList/CSSListTest.php +++ b/tests/Unit/CSSList/CSSListTest.php @@ -6,6 +6,7 @@ use PHPUnit\Framework\TestCase; use Sabberworm\CSS\Comment\Commentable; +use Sabberworm\CSS\CSSElement; use Sabberworm\CSS\Renderable; use Sabberworm\CSS\RuleSet\DeclarationBlock; use Sabberworm\CSS\Tests\Unit\CSSList\Fixtures\ConcreteCSSList; @@ -15,6 +16,16 @@ */ final class CSSListTest extends TestCase { + /** + * @test + */ + public function implementsCSSElement(): void + { + $subject = new ConcreteCSSList(); + + self::assertInstanceOf(CSSElement::class, $subject); + } + /** * @test */ diff --git a/tests/Unit/Rule/RuleTest.php b/tests/Unit/Rule/RuleTest.php index b803c685..008bcfc1 100644 --- a/tests/Unit/Rule/RuleTest.php +++ b/tests/Unit/Rule/RuleTest.php @@ -5,6 +5,7 @@ namespace Sabberworm\CSS\Tests\Unit\Rule; use PHPUnit\Framework\TestCase; +use Sabberworm\CSS\CSSElement; use Sabberworm\CSS\Parsing\ParserState; use Sabberworm\CSS\Rule\Rule; use Sabberworm\CSS\Settings; @@ -17,6 +18,16 @@ */ final class RuleTest extends TestCase { + /** + * @test + */ + public function implementsCSSElement(): void + { + $subject = new Rule('beverage-container'); + + self::assertInstanceOf(CSSElement::class, $subject); + } + /** * @return array}> */ diff --git a/tests/Unit/RuleSet/Fixtures/ConcreteRuleSet.php b/tests/Unit/RuleSet/Fixtures/ConcreteRuleSet.php new file mode 100644 index 00000000..9c79c09d --- /dev/null +++ b/tests/Unit/RuleSet/Fixtures/ConcreteRuleSet.php @@ -0,0 +1,19 @@ + */ From f0360052da0da8ccaea45aff510a2c8f7b6cb257 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Wed, 9 Apr 2025 17:40:13 +0100 Subject: [PATCH 429/555] [TASK] Add `assertInstanceOf` tests for `CSSListItem` (#1237) These should probably have been added as part of #1212. They confirm that the various types supplanted by `CSSListItem` in the API all implement the new interface. Resolves #1236. --- tests/Unit/CSSList/AtRuleBlockListTest.php | 11 +++++++ tests/Unit/CSSList/CSSListTest.php | 11 +++++++ tests/Unit/CSSList/KeyFrameTest.php | 11 +++++++ tests/Unit/Property/CSSNamespaceTest.php | 34 ++++++++++++++++++++ tests/Unit/Property/CharsetTest.php | 34 ++++++++++++++++++++ tests/Unit/Property/ImportTest.php | 35 +++++++++++++++++++++ tests/Unit/RuleSet/AtRuleSetTest.php | 33 +++++++++++++++++++ tests/Unit/RuleSet/DeclarationBlockTest.php | 33 +++++++++++++++++++ tests/Unit/RuleSet/RuleSetTest.php | 21 +++++++++++-- 9 files changed, 221 insertions(+), 2 deletions(-) create mode 100644 tests/Unit/Property/CSSNamespaceTest.php create mode 100644 tests/Unit/Property/CharsetTest.php create mode 100644 tests/Unit/Property/ImportTest.php create mode 100644 tests/Unit/RuleSet/AtRuleSetTest.php create mode 100644 tests/Unit/RuleSet/DeclarationBlockTest.php diff --git a/tests/Unit/CSSList/AtRuleBlockListTest.php b/tests/Unit/CSSList/AtRuleBlockListTest.php index 6688d079..0252f7d3 100644 --- a/tests/Unit/CSSList/AtRuleBlockListTest.php +++ b/tests/Unit/CSSList/AtRuleBlockListTest.php @@ -9,6 +9,7 @@ use Sabberworm\CSS\CSSList\AtRuleBlockList; use Sabberworm\CSS\CSSList\CSSBlockList; use Sabberworm\CSS\CSSList\CSSList; +use Sabberworm\CSS\CSSList\CSSListItem; use Sabberworm\CSS\Property\AtRule; use Sabberworm\CSS\Renderable; @@ -49,6 +50,16 @@ public function implementsCommentable(): void self::assertInstanceOf(Commentable::class, $subject); } + /** + * @test + */ + public function implementsCSSListItem(): void + { + $subject = new AtRuleBlockList('supports'); + + self::assertInstanceOf(CSSListItem::class, $subject); + } + /** * @test */ diff --git a/tests/Unit/CSSList/CSSListTest.php b/tests/Unit/CSSList/CSSListTest.php index abe87dd6..03539533 100644 --- a/tests/Unit/CSSList/CSSListTest.php +++ b/tests/Unit/CSSList/CSSListTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\TestCase; use Sabberworm\CSS\Comment\Commentable; use Sabberworm\CSS\CSSElement; +use Sabberworm\CSS\CSSList\CSSListItem; use Sabberworm\CSS\Renderable; use Sabberworm\CSS\RuleSet\DeclarationBlock; use Sabberworm\CSS\Tests\Unit\CSSList\Fixtures\ConcreteCSSList; @@ -46,6 +47,16 @@ public function implementsCommentable(): void self::assertInstanceOf(Commentable::class, $subject); } + /** + * @test + */ + public function implementsCSSListItem(): void + { + $subject = new ConcreteCSSList(); + + self::assertInstanceOf(CSSListItem::class, $subject); + } + /** * @test */ diff --git a/tests/Unit/CSSList/KeyFrameTest.php b/tests/Unit/CSSList/KeyFrameTest.php index 20be302c..a3a0d43a 100644 --- a/tests/Unit/CSSList/KeyFrameTest.php +++ b/tests/Unit/CSSList/KeyFrameTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\TestCase; use Sabberworm\CSS\Comment\Commentable; use Sabberworm\CSS\CSSList\CSSList; +use Sabberworm\CSS\CSSList\CSSListItem; use Sabberworm\CSS\CSSList\KeyFrame; use Sabberworm\CSS\Property\AtRule; use Sabberworm\CSS\Renderable; @@ -47,6 +48,16 @@ public function implementsCommentable(): void self::assertInstanceOf(Commentable::class, $subject); } + /** + * @test + */ + public function implementsCSSListItem(): void + { + $subject = new KeyFrame(); + + self::assertInstanceOf(CSSListItem::class, $subject); + } + /** * @test */ diff --git a/tests/Unit/Property/CSSNamespaceTest.php b/tests/Unit/Property/CSSNamespaceTest.php new file mode 100644 index 00000000..2e4d9922 --- /dev/null +++ b/tests/Unit/Property/CSSNamespaceTest.php @@ -0,0 +1,34 @@ +subject = new CSSNamespace(new CSSString('http://www.w3.org/2000/svg')); + } + + /** + * @test + */ + public function implementsCSSListItem(): void + { + self::assertInstanceOf(CSSListItem::class, $this->subject); + } +} diff --git a/tests/Unit/Property/CharsetTest.php b/tests/Unit/Property/CharsetTest.php new file mode 100644 index 00000000..e0645f5e --- /dev/null +++ b/tests/Unit/Property/CharsetTest.php @@ -0,0 +1,34 @@ +subject = new Charset(new CSSString('UTF-8')); + } + + /** + * @test + */ + public function implementsCSSListItem(): void + { + self::assertInstanceOf(CSSListItem::class, $this->subject); + } +} diff --git a/tests/Unit/Property/ImportTest.php b/tests/Unit/Property/ImportTest.php new file mode 100644 index 00000000..4ec028e3 --- /dev/null +++ b/tests/Unit/Property/ImportTest.php @@ -0,0 +1,35 @@ +subject = new Import(new URL(new CSSString('https://example.org/')), null); + } + + /** + * @test + */ + public function implementsCSSListItem(): void + { + self::assertInstanceOf(CSSListItem::class, $this->subject); + } +} diff --git a/tests/Unit/RuleSet/AtRuleSetTest.php b/tests/Unit/RuleSet/AtRuleSetTest.php new file mode 100644 index 00000000..0e3e0c97 --- /dev/null +++ b/tests/Unit/RuleSet/AtRuleSetTest.php @@ -0,0 +1,33 @@ +subject = new AtRuleSet('supports'); + } + + /** + * @test + */ + public function implementsCSSListItem(): void + { + self::assertInstanceOf(CSSListItem::class, $this->subject); + } +} diff --git a/tests/Unit/RuleSet/DeclarationBlockTest.php b/tests/Unit/RuleSet/DeclarationBlockTest.php new file mode 100644 index 00000000..b473da35 --- /dev/null +++ b/tests/Unit/RuleSet/DeclarationBlockTest.php @@ -0,0 +1,33 @@ +subject = new DeclarationBlock(); + } + + /** + * @test + */ + public function implementsCSSListItem(): void + { + self::assertInstanceOf(CSSListItem::class, $this->subject); + } +} diff --git a/tests/Unit/RuleSet/RuleSetTest.php b/tests/Unit/RuleSet/RuleSetTest.php index 7211a77c..0b1fd816 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\CSSList\CSSListItem; use Sabberworm\CSS\Tests\Unit\RuleSet\Fixtures\ConcreteRuleSet; /** @@ -13,13 +14,29 @@ */ final class RuleSetTest extends TestCase { + /** + * @var ConcreteRuleSet + */ + private $subject; + + protected function setUp(): void + { + $this->subject = new ConcreteRuleSet(); + } + /** * @test */ public function implementsCSSElement(): void { - $subject = new ConcreteRuleSet(); + self::assertInstanceOf(CSSElement::class, $this->subject); + } - self::assertInstanceOf(CSSElement::class, $subject); + /** + * @test + */ + public function implementsCSSListItem(): void + { + self::assertInstanceOf(CSSListItem::class, $this->subject); } } From 0c94d6eac637e202fa9dcd1ec6c5b76f2b3a760b Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Wed, 9 Apr 2025 23:04:48 +0100 Subject: [PATCH 430/555] [CLEANUP] Remove `CSSBlockList::allDeclarationBlocks()` (#1239) Change the one remaining usage instance to use `getAllDeclarationBlocks()`, which was refactored in #990. Part of #994. --- src/CSSList/CSSBlockList.php | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/CSSList/CSSBlockList.php b/src/CSSList/CSSBlockList.php index bcccf9df..95148630 100644 --- a/src/CSSList/CSSBlockList.php +++ b/src/CSSList/CSSBlockList.php @@ -41,20 +41,6 @@ public function getAllDeclarationBlocks(): array return $result; } - /** - * @param list $result - */ - protected function allDeclarationBlocks(array &$result): void - { - foreach ($this->contents as $item) { - if ($item instanceof DeclarationBlock) { - $result[] = $item; - } elseif ($item instanceof CSSBlockList) { - $item->allDeclarationBlocks($result); - } - } - } - /** * Returns all `RuleSet` objects recursively found in the tree, no matter how deeply nested the rule sets are. * @@ -111,9 +97,7 @@ protected function allValues( */ protected function allSelectors(array &$result, ?string $specificitySearch = null): void { - $declarationBlocks = []; - $this->allDeclarationBlocks($declarationBlocks); - foreach ($declarationBlocks as $declarationBlock) { + foreach ($this->getAllDeclarationBlocks() as $declarationBlock) { foreach ($declarationBlock->getSelectors() as $selector) { if ($specificitySearch === null) { $result[] = $selector; From d9137fd1cd88d1d69d94af83a1771df2c0dc2c83 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Thu, 10 Apr 2025 09:04:19 +0100 Subject: [PATCH 431/555] [TASK] Move `getAllValues()` to `CSSBlockList` (#1240) Also add unit tests for this method. Part of #994. Relates to #1230. --- src/CSSList/CSSBlockList.php | 26 ++++ src/CSSList/Document.php | 30 ---- tests/Unit/CSSList/CSSBlockListTest.php | 195 ++++++++++++++++++++++++ 3 files changed, 221 insertions(+), 30 deletions(-) diff --git a/src/CSSList/CSSBlockList.php b/src/CSSList/CSSBlockList.php index 95148630..0423caea 100644 --- a/src/CSSList/CSSBlockList.php +++ b/src/CSSList/CSSBlockList.php @@ -61,6 +61,32 @@ public function getAllRuleSets(): array return $result; } + /** + * Returns all `Value` objects found recursively in `Rule`s in the tree. + * + * @param CSSElement|string $element + * 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 $searchInFunctionArguments whether to also return Value objects used as Function arguments. + * + * @return array + * + * @see RuleSet->getRules() + */ + public function getAllValues($element = null, bool $searchInFunctionArguments = false): array + { + $searchString = null; + 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 $element * @param list $result diff --git a/src/CSSList/Document.php b/src/CSSList/Document.php index f061b427..d743b0fb 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -4,13 +4,10 @@ namespace Sabberworm\CSS\CSSList; -use Sabberworm\CSS\CSSElement; use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Parsing\ParserState; use Sabberworm\CSS\Parsing\SourceException; use Sabberworm\CSS\Property\Selector; -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 @@ -31,33 +28,6 @@ public static function parse(ParserState $parserState): Document return $document; } - /** - * Returns all `Value` objects found recursively in `Rule`s in the tree. - * - * @param CSSElement|string $element - * 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 $searchInFunctionArguments whether to also return Value objects used as Function arguments. - * - * @return array - * - * @see RuleSet->getRules() - */ - public function getAllValues($element = null, bool $searchInFunctionArguments = false): array - { - $searchString = null; - if ($element === null) { - $element = $this; - } elseif (\is_string($element)) { - $searchString = $element; - $element = $this; - } - /** @var array $result */ - $result = []; - $this->allValues($element, $result, $searchString, $searchInFunctionArguments); - return $result; - } - /** * Returns all `Selector` objects with the requested specificity found recursively in the tree. * diff --git a/tests/Unit/CSSList/CSSBlockListTest.php b/tests/Unit/CSSList/CSSBlockListTest.php index 0028288f..84e53610 100644 --- a/tests/Unit/CSSList/CSSBlockListTest.php +++ b/tests/Unit/CSSList/CSSBlockListTest.php @@ -11,10 +11,13 @@ use Sabberworm\CSS\Property\Charset; use Sabberworm\CSS\Property\Import; use Sabberworm\CSS\Renderable; +use Sabberworm\CSS\Rule\Rule; use Sabberworm\CSS\RuleSet\AtRuleSet; use Sabberworm\CSS\RuleSet\DeclarationBlock; use Sabberworm\CSS\Tests\Unit\CSSList\Fixtures\ConcreteCSSBlockList; +use Sabberworm\CSS\Value\CSSFunction; use Sabberworm\CSS\Value\CSSString; +use Sabberworm\CSS\Value\Size; use Sabberworm\CSS\Value\URL; /** @@ -276,4 +279,196 @@ public function getAllRuleSetsIgnoresCharset(): void self::assertSame([], $result); } + + /** + * @test + */ + public function getAllValuesWhenNoContentSetReturnsEmptyArray(): void + { + $subject = new ConcreteCSSBlockList(); + + self::assertSame([], $subject->getAllValues()); + } + + /** + * @test + */ + public function getAllValuesReturnsOneValueDirectlySetAsContent(): void + { + $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 + */ + public function getAllValuesReturnsMultipleValuesDirectlySetAsContentInOneDeclarationBlock(): void + { + $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 + */ + public function getAllValuesReturnsMultipleValuesDirectlySetAsContentInMultipleDeclarationBlocks(): void + { + $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 + */ + public function getAllValuesReturnsValuesWithinAtRuleBlockList(): void + { + $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 + */ + public function getAllValuesWithElementProvidedReturnsOnlyValuesWithinThatElement(): void + { + $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 + */ + public function getAllValuesWithSearchStringProvidedReturnsOnlyValuesFromMatchingRules(): void + { + $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 + */ + public function getAllValuesByDefaultDoesNotReturnValuesInFunctionArguments(): void + { + $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 + */ + public function getAllValuesWithSearchInFunctionArgumentsReturnsValuesInFunctionArguments(): void + { + $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); + } } From 01194355bc7aa9594aae20af009fe88401dfc4cd Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Fri, 11 Apr 2025 21:05:22 +0100 Subject: [PATCH 432/555] [TASK] Deconflate `getAllValues()` parameters (#1241) The `$element` parameter was overloaded with a dual purpose. A second separate parameter has been added for rule filtering, which is not actually mutually exclusive with CSS subtree selection. Since `getAllValues()` is part of the public API, the method now supports being called with the old or new signatures, with the old signature being deprecated. Once the deprecation has been included in the 8.x release branch, the messiness of supporting the previous API can be removed. Part of #994. Also relates to #1230. --- CHANGELOG.md | 8 +++++ src/CSSList/CSSBlockList.php | 32 ++++++++++++++---- tests/Unit/CSSList/CSSBlockListTest.php | 45 +++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a74924aa..13e28ddd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,9 @@ Please also have a look at our ### 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) @@ -43,6 +46,11 @@ Please also have a look at our ### 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/CSSList/CSSBlockList.php b/src/CSSList/CSSBlockList.php index 0423caea..3e936912 100644 --- a/src/CSSList/CSSBlockList.php +++ b/src/CSSList/CSSBlockList.php @@ -64,24 +64,44 @@ public function getAllRuleSets(): array /** * Returns all `Value` objects found recursively in `Rule`s in the tree. * - * @param CSSElement|string $element - * 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 CSSElement|string|null $element + * This is the `CSSList` or `RuleSet` to start the search from (defaults to the whole document). + * If a string is given, it is used as a rule name filter. + * Passing a string for this parameter is deprecated in version 8.9.0, and will not work from v9.0; + * use the following parameter to pass a rule name filter instead. + * @param string|bool|null $ruleSearchPatternOrSearchInFunctionArguments + * This allows filtering rules by property name + * (e.g. if "color" is passed, only `Value`s from `color` properties will be returned, + * or if "font-" is provided, `Value`s from all font rules, like `font-size`, and including `font` itself, + * will be returned). + * If a Boolean is provided, it is treated as the `$searchInFunctionArguments` argument. + * Passing a Boolean for this parameter is deprecated in version 8.9.0, and will not work from v9.0; + * use the `$searchInFunctionArguments` parameter instead. * @param bool $searchInFunctionArguments whether to also return Value objects used as Function arguments. * * @return array * * @see RuleSet->getRules() */ - public function getAllValues($element = null, bool $searchInFunctionArguments = false): array - { - $searchString = null; + public function getAllValues( + $element = null, + $ruleSearchPatternOrSearchInFunctionArguments = null, + bool $searchInFunctionArguments = false + ): array { + 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; diff --git a/tests/Unit/CSSList/CSSBlockListTest.php b/tests/Unit/CSSList/CSSBlockListTest.php index 84e53610..2a83f647 100644 --- a/tests/Unit/CSSList/CSSBlockListTest.php +++ b/tests/Unit/CSSList/CSSBlockListTest.php @@ -430,6 +430,30 @@ public function getAllValuesWithSearchStringProvidedReturnsOnlyValuesFromMatchin self::assertSame([$value1], $result); } + /** + * @test + */ + public function getAllValuesWithSearchStringProvidedInNewMethodSignatureReturnsOnlyValuesFromMatchingRules(): void + { + $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 */ @@ -471,4 +495,25 @@ public function getAllValuesWithSearchInFunctionArgumentsReturnsValuesInFunction self::assertSame([$value1, $value2], $result); } + + /** + * @test + */ + public function getAllValuesWithSearchInFunctionArgumentsInNewMethodSignatureReturnsValuesInFunctionArguments(): void + { + $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); + } } From 4429cc22a9921bba1d2148eca6a2d39fe22246d6 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sat, 12 Apr 2025 09:35:15 +0100 Subject: [PATCH 433/555] [TASK] Remove original `getAllValues()` API (#1243) The method still exists with the same (slightly improved) functionality, but the optional arguments have been refactored, and may require changes. Part of #994. Closes #1230. --- CHANGELOG.md | 6 ++++ src/CSSList/CSSBlockList.php | 32 ++++-------------- tests/ParserTest.php | 6 ++-- tests/Unit/CSSList/CSSBlockListTest.php | 45 ------------------------- 4 files changed, 15 insertions(+), 74 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13e28ddd..132605a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,12 @@ Please also have a look at our ### Removed +- Passing a string as the first argument to `getAllValues()` is no longer + supported and will not work; + the search pattern should now be passed as the second argument (#1243) +- Passing a Boolean as the second argument to `getAllValues()` is no longer + supported and will not work; the flag for searching in function arguments + should now be passed as the third argument (#1243) - Remove `__toString()` (#1046) - Drop magic method forwarding in `OutputFormat` (#898) - Drop `atRuleArgs()` from the `AtRule` interface (#1141) diff --git a/src/CSSList/CSSBlockList.php b/src/CSSList/CSSBlockList.php index 3e936912..5e1312b7 100644 --- a/src/CSSList/CSSBlockList.php +++ b/src/CSSList/CSSBlockList.php @@ -64,46 +64,26 @@ public function getAllRuleSets(): array /** * Returns all `Value` objects found recursively in `Rule`s in the tree. * - * @param CSSElement|string|null $element + * @param CSSElement|null $element * This is the `CSSList` or `RuleSet` to start the search from (defaults to the whole document). - * If a string is given, it is used as a rule name filter. - * Passing a string for this parameter is deprecated in version 8.9.0, and will not work from v9.0; - * use the following parameter to pass a rule name filter instead. - * @param string|bool|null $ruleSearchPatternOrSearchInFunctionArguments + * @param string|null $ruleSearchPattern * This allows filtering rules by property name * (e.g. if "color" is passed, only `Value`s from `color` properties will be returned, * or if "font-" is provided, `Value`s from all font rules, like `font-size`, and including `font` itself, * will be returned). - * If a Boolean is provided, it is treated as the `$searchInFunctionArguments` argument. - * Passing a Boolean for this parameter is deprecated in version 8.9.0, and will not work from v9.0; - * use the `$searchInFunctionArguments` parameter instead. - * @param bool $searchInFunctionArguments whether to also return Value objects used as Function arguments. + * @param bool $searchInFunctionArguments whether to also return `Value` objects used as `CSSFunction` arguments. * * @return array * * @see RuleSet->getRules() */ public function getAllValues( - $element = null, - $ruleSearchPatternOrSearchInFunctionArguments = null, + ?CSSElement $element = null, + ?string $ruleSearchPattern = null, bool $searchInFunctionArguments = false ): array { - 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); + $this->allValues($element ?? $this, $result, $ruleSearchPattern, $searchInFunctionArguments); return $result; } diff --git a/tests/ParserTest.php b/tests/ParserTest.php index f0fee35b..87c9b23e 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -153,7 +153,7 @@ public function colorParsing(): void self::assertEmpty($colorRules); } } - foreach ($document->getAllValues('color') as $colorValue) { + foreach ($document->getAllValues(null, 'color') as $colorValue) { self::assertSame('red', $colorValue); } self::assertSame( @@ -455,7 +455,7 @@ public function functionSyntax(): void . '.collapser.expanded + * {height: auto;}'; self::assertSame($expected, $document->render()); - foreach ($document->getAllValues(null, true) as $value) { + foreach ($document->getAllValues(null, null, true) as $value) { if ($value instanceof Size && $value->isSize()) { $value->setSize($value->getSize() * 3); } @@ -463,7 +463,7 @@ public function functionSyntax(): void $expected = \str_replace(['1.2em', '.2em', '60%'], ['3.6em', '.6em', '180%'], $expected); self::assertSame($expected, $document->render()); - foreach ($document->getAllValues(null, true) as $value) { + foreach ($document->getAllValues(null, null, true) as $value) { if ($value instanceof Size && !$value->isRelative() && !$value->isColorComponent()) { $value->setSize($value->getSize() * 2); } diff --git a/tests/Unit/CSSList/CSSBlockListTest.php b/tests/Unit/CSSList/CSSBlockListTest.php index 2a83f647..41f2b41f 100644 --- a/tests/Unit/CSSList/CSSBlockListTest.php +++ b/tests/Unit/CSSList/CSSBlockListTest.php @@ -425,30 +425,6 @@ public function getAllValuesWithSearchStringProvidedReturnsOnlyValuesFromMatchin $declarationBlock->addRule($rule2); $subject->setContents([$declarationBlock]); - $result = $subject->getAllValues('font-'); - - self::assertSame([$value1], $result); - } - - /** - * @test - */ - public function getAllValuesWithSearchStringProvidedInNewMethodSignatureReturnsOnlyValuesFromMatchingRules(): void - { - $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); @@ -491,27 +467,6 @@ public function getAllValuesWithSearchInFunctionArgumentsReturnsValuesInFunction $declarationBlock->addRule($rule); $subject->setContents([$declarationBlock]); - $result = $subject->getAllValues(null, true); - - self::assertSame([$value1, $value2], $result); - } - - /** - * @test - */ - public function getAllValuesWithSearchInFunctionArgumentsInNewMethodSignatureReturnsValuesInFunctionArguments(): void - { - $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); From 03ed64a093ec0221a26e42150ff559a898b25f2c Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sun, 13 Apr 2025 23:13:04 +0100 Subject: [PATCH 434/555] [TASK] Refactor `getAllValues()` (#1244) Move functionality from `allValues()` directly into to `getAllValues()`, so as to avoid passing array by reference, removing `allValues()`. Avoid making the recursive call for nested items that are not `CSSElement`s. Part of #994. Closes #1230. --- src/CSSList/CSSBlockList.php | 54 ++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/src/CSSList/CSSBlockList.php b/src/CSSList/CSSBlockList.php index 5e1312b7..53004b90 100644 --- a/src/CSSList/CSSBlockList.php +++ b/src/CSSList/CSSBlockList.php @@ -73,7 +73,7 @@ public function getAllRuleSets(): array * will be returned). * @param bool $searchInFunctionArguments whether to also return `Value` objects used as `CSSFunction` arguments. * - * @return array + * @return list * * @see RuleSet->getRules() */ @@ -82,40 +82,52 @@ public function getAllValues( ?string $ruleSearchPattern = null, bool $searchInFunctionArguments = false ): array { - $result = []; - $this->allValues($element ?? $this, $result, $ruleSearchPattern, $searchInFunctionArguments); - return $result; - } + $element = $element ?? $this; - /** - * @param CSSElement|string $element - * @param list $result - */ - protected function allValues( - $element, - array &$result, - ?string $searchString = null, - bool $searchInFunctionArguments = false - ): void { + $result = []; if ($element instanceof CSSBlockList) { - foreach ($element->getContents() as $content) { - $this->allValues($content, $result, $searchString, $searchInFunctionArguments); + foreach ($element->getContents() as $contentItem) { + // Statement at-rules are skipped since they do not contain values. + if ($contentItem instanceof CSSElement) { + $result = \array_merge( + $result, + $this->getAllValues($contentItem, $ruleSearchPattern, $searchInFunctionArguments) + ); + } } } elseif ($element instanceof RuleSet) { - foreach ($element->getRules($searchString) as $rule) { - $this->allValues($rule, $result, $searchString, $searchInFunctionArguments); + foreach ($element->getRules($ruleSearchPattern) as $rule) { + $result = \array_merge( + $result, + $this->getAllValues($rule, $ruleSearchPattern, $searchInFunctionArguments) + ); } } elseif ($element instanceof Rule) { - $this->allValues($element->getValue(), $result, $searchString, $searchInFunctionArguments); + $value = $element->getValue(); + // `string` values are discarded. + if ($value instanceof CSSElement) { + $result = \array_merge( + $result, + $this->getAllValues($value, $ruleSearchPattern, $searchInFunctionArguments) + ); + } } elseif ($element instanceof ValueList) { if ($searchInFunctionArguments || !($element instanceof CSSFunction)) { foreach ($element->getListComponents() as $component) { - $this->allValues($component, $result, $searchString, $searchInFunctionArguments); + // `string` components are discarded. + if ($component instanceof CSSElement) { + $result = \array_merge( + $result, + $this->getAllValues($component, $ruleSearchPattern, $searchInFunctionArguments) + ); + } } } } elseif ($element instanceof Value) { $result[] = $element; } + + return $result; } /** From a9874a9d86ef09e9b432f2903c29d180b336af08 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Mon, 14 Apr 2025 09:28:15 +0100 Subject: [PATCH 435/555] [CLEANUP] `allSelectors()` -> `getAllSelectors()` (#1245) The renamed (internal) method now returns the result, instead of having a reference parameter for it. Closes #994. --- src/CSSList/CSSBlockList.php | 8 ++++++-- src/CSSList/Document.php | 8 +++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/CSSList/CSSBlockList.php b/src/CSSList/CSSBlockList.php index 53004b90..90643968 100644 --- a/src/CSSList/CSSBlockList.php +++ b/src/CSSList/CSSBlockList.php @@ -131,10 +131,12 @@ public function getAllValues( } /** - * @param list $result + * @return list */ - protected function allSelectors(array &$result, ?string $specificitySearch = null): void + protected function getAllSelectors(?string $specificitySearch = null): array { + $result = []; + foreach ($this->getAllDeclarationBlocks() as $declarationBlock) { foreach ($declarationBlock->getSelectors() as $selector) { if ($specificitySearch === null) { @@ -173,5 +175,7 @@ protected function allSelectors(array &$result, ?string $specificitySearch = nul } } } + + return $result; } } diff --git a/src/CSSList/Document.php b/src/CSSList/Document.php index d743b0fb..b7a65835 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -38,15 +38,13 @@ public static function parse(ParserState $parserState): Document * An optional filter by specificity. * May contain a comparison operator and a number or just a number (defaults to "=="). * - * @return array + * @return list + * * @example `getSelectorsBySpecificity('>= 100')` */ public function getSelectorsBySpecificity(?string $specificitySearch = null): array { - /** @var array $result */ - $result = []; - $this->allSelectors($result, $specificitySearch); - return $result; + return $this->getAllSelectors($specificitySearch); } /** From 9dbc6a644b2920fbc10297d229d4464276ddb378 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Mon, 14 Apr 2025 09:29:26 +0100 Subject: [PATCH 436/555] [CLEANUP] Avoid negated non-Boolean in `RuleSet` (#1246) Use `=== null` instead to be more precise. --- config/phpstan-baseline.neon | 6 ------ src/RuleSet/RuleSet.php | 4 ++-- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 007667a5..f84a4a00 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -54,12 +54,6 @@ parameters: count: 1 path: ../src/RuleSet/DeclarationBlock.php - - - message: '#^Only booleans are allowed in a negated boolean, string\|null given\.$#' - identifier: booleanNot.exprNotBoolean - count: 2 - path: ../src/RuleSet/RuleSet.php - - message: '#^Parameters should have "string" types as the only types passed to this method$#' identifier: typePerfect.narrowPublicClassMethodParamType diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 3a7552e3..b10d3e97 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -149,7 +149,7 @@ public function getRules($searchPattern = null): array // 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. if ( - !$searchPattern || $propertyName === $searchPattern + $searchPattern === null || $propertyName === $searchPattern || ( \strrpos($searchPattern, '-') === \strlen($searchPattern) - \strlen('-') && (\strpos($propertyName, $searchPattern) === 0 @@ -240,7 +240,7 @@ public function removeRule($searchPattern): void // or the search rule ends in “-” and the found rule starts with the search rule or equals it // (without the trailing dash). if ( - !$searchPattern || $propertyName === $searchPattern + $searchPattern === null || $propertyName === $searchPattern || (\strrpos($searchPattern, '-') === \strlen($searchPattern) - \strlen('-') && (\strpos($propertyName, $searchPattern) === 0 || $propertyName === \substr($searchPattern, 0, -1))) From 3465f1362b80fd5c2d425c9c02f224ef5fbe2fef Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Fri, 2 May 2025 10:30:08 +0100 Subject: [PATCH 437/555] [TASK] Deprecate passing `Rule` to `RuleSet::getRules()` (#1248) And also `getRulesAssoc()`. Relates to #1247. --- CHANGELOG.md | 3 +++ src/RuleSet/RuleSet.php | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 132605a9..f4b2fc5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,9 @@ Please also have a look at our ### Deprecated +- 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 b10d3e97..d96787c5 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -135,7 +135,8 @@ public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void * 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($rule->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, Rule> */ @@ -193,7 +194,9 @@ public function setRules(array $rules): void * @param Rule|string|null $searchPattern * 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($rule->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 f6b5cd34016cfaca78707a017d86a371818b605f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 May 2025 13:57:39 +0200 Subject: [PATCH 438/555] [Dependabot] Update phpunit/phpunit requirement from 8.5.41 to 8.5.42 (#1250) Updates the requirements on [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) to permit the latest version. - [Release notes](https://github.com/sebastianbergmann/phpunit/releases) - [Changelog](https://github.com/sebastianbergmann/phpunit/blob/8.5.42/ChangeLog-8.5.md) - [Commits](https://github.com/sebastianbergmann/phpunit/compare/8.5.41...8.5.42) --- updated-dependencies: - dependency-name: phpunit/phpunit dependency-version: 8.5.42 dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 1aa6895c..e4ea9c43 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "phpstan/phpstan": "1.12.16 || 2.1.2", "phpstan/phpstan-phpunit": "1.4.2 || 2.0.4", "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.3", - "phpunit/phpunit": "8.5.41", + "phpunit/phpunit": "8.5.42", "rawr/phpunit-data-provider": "3.3.1", "rector/rector": "1.2.10 || 2.0.7", "rector/type-perfect": "1.0.0 || 2.0.2" From 807a11ba6a71d505993be1a1761831522d34c780 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sun, 4 May 2025 09:25:23 +0100 Subject: [PATCH 439/555] [TASK] Add separate methods for `RuleSet::removeRule()`, with deprecation (#1249) Passing `string` or `null` to `removeRule()` is deprecated. The new method handles that functionality. Relates to #1247. --- CHANGELOG.md | 7 ++ src/RuleSet/RuleSet.php | 59 ++++++---- tests/Unit/RuleSet/RuleSetTest.php | 170 +++++++++++++++++++++++++++++ 3 files changed, 214 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4b2fc5e..023117c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ Please also have a look at our ### 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: @@ -46,6 +50,9 @@ Please also have a look at our ### 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 `Rule` to `RuleSet::getRules()` or `getRulesAssoc()` is deprecated, affecting the implementing classes `AtRuleSet` and `DeclarationBlock` (call e.g. `getRules($rule->getRule())` instead) (#1248) diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index d96787c5..3ed5cf0f 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -212,18 +212,12 @@ public function getRulesAssoc($searchPattern = null): array } /** - * 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($rule->getRule())`. + * Removes a `Rule` from this `RuleSet` by identity. * * @param Rule|string|null $searchPattern - * pattern to remove. If 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. + * `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($searchPattern): void { @@ -237,23 +231,44 @@ public function removeRule($searchPattern): void unset($this->rules[$nameOfPropertyToRemove][$key]); } } + } elseif ($searchPattern !== null) { + $this->removeMatchingRules($searchPattern); } else { - foreach ($this->rules as $propertyName => $rules) { - // 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 ( - $searchPattern === null || $propertyName === $searchPattern - || (\strrpos($searchPattern, '-') === \strlen($searchPattern) - \strlen('-') - && (\strpos($propertyName, $searchPattern) === 0 - || $propertyName === \substr($searchPattern, 0, -1))) - ) { - unset($this->rules[$propertyName]); - } + $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(string $searchPattern): void + { + foreach ($this->rules 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->rules[$propertyName]); } } } + public function removeAllRules(): void + { + $this->rules = []; + } + protected function renderRules(OutputFormat $outputFormat): string { $result = ''; diff --git a/tests/Unit/RuleSet/RuleSetTest.php b/tests/Unit/RuleSet/RuleSetTest.php index 0b1fd816..774f37e7 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\CSSList\CSSListItem; +use Sabberworm\CSS\Rule\Rule; use Sabberworm\CSS\Tests\Unit\RuleSet\Fixtures\ConcreteRuleSet; /** @@ -39,4 +40,173 @@ public function implementsCSSListItem(): void { self::assertInstanceOf(CSSListItem::class, $this->subject); } + + /** + * @return array, 1: string, 2: list}> + */ + public static function provideRulesAndPropertyNameToRemoveAndExpectedRemainingPropertyNames(): array + { + 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 list $expectedRemainingPropertyNames + * + * @dataProvider provideRulesAndPropertyNameToRemoveAndExpectedRemainingPropertyNames + */ + public function removeMatchingRulesRemovesRulesByPropertyNameAndKeepsOthers( + array $rules, + string $propertyName, + array $expectedRemainingPropertyNames + ): void { + $this->subject->setRules($rules); + + $this->subject->removeMatchingRules($propertyName); + + $remainingRules = $this->subject->getRulesAssoc(); + self::assertArrayNotHasKey($propertyName, $remainingRules); + foreach ($expectedRemainingPropertyNames as $expectedPropertyName) { + self::assertArrayHasKey($expectedPropertyName, $remainingRules); + } + } + + /** + * @return array, 1: string, 2: list}> + */ + public static function provideRulesAndPropertyNamePrefixToRemoveAndExpectedRemainingPropertyNames(): array + { + 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 list $expectedRemainingPropertyNames + * + * @dataProvider provideRulesAndPropertyNamePrefixToRemoveAndExpectedRemainingPropertyNames + */ + public function removeMatchingRulesRemovesRulesByPropertyNamePrefixAndKeepsOthers( + array $rules, + string $propertyNamePrefix, + array $expectedRemainingPropertyNames + ): void { + $propertyNamePrefixWithHyphen = $propertyNamePrefix . '-'; + $this->subject->setRules($rules); + + $this->subject->removeMatchingRules($propertyNamePrefixWithHyphen); + + $remainingRules = $this->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(): array + { + 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): void + { + $this->subject->setRules($rules); + + $this->subject->removeAllRules(); + + self::assertSame([], $this->subject->getRules()); + } } From c3905a7757a485858172ac2c3f05b9e56f4279d7 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Tue, 6 May 2025 08:08:36 +0100 Subject: [PATCH 440/555] [CLEANUP] Use `RuleSet::removeMatchingRules()` in tests (#1254) Now this method has been added, using it appropriately in the tests, rather than the deprecated functionality, will eliminate a PHPStan warning. Note that PHPStan seems to erroneously place the warning against the callee rather than the caller. --- config/phpstan-baseline.neon | 6 ------ tests/ParserTest.php | 4 ++-- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index f84a4a00..9be2ebd7 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -54,12 +54,6 @@ parameters: count: 1 path: ../src/RuleSet/DeclarationBlock.php - - - message: '#^Parameters should have "string" types as the only types passed to this method$#' - identifier: typePerfect.narrowPublicClassMethodParamType - count: 1 - path: ../src/RuleSet/RuleSet.php - - message: '#^Loose comparison via "\!\=" is not allowed\.$#' identifier: notEqual.notAllowed diff --git a/tests/ParserTest.php b/tests/ParserTest.php index 87c9b23e..ffda6b8c 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -348,7 +348,7 @@ public function manipulation(): void $document->render() ); foreach ($document->getAllRuleSets() as $ruleSet) { - $ruleSet->removeRule('font-'); + $ruleSet->removeMatchingRules('font-'); } self::assertSame( '#header {margin: 10px 2em 1cm 2%;color: red !important;background-color: green;' @@ -357,7 +357,7 @@ public function manipulation(): void $document->render() ); foreach ($document->getAllRuleSets() as $ruleSet) { - $ruleSet->removeRule('background-'); + $ruleSet->removeMatchingRules('background-'); } self::assertSame( '#header {margin: 10px 2em 1cm 2%;color: red !important;frequency: 30Hz;transform: rotate(1turn);} From 9706931871a23f8cdd53e8557ebd11643983de59 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Tue, 6 May 2025 08:09:27 +0100 Subject: [PATCH 441/555] [TASK] Drop allowing `Rule` to be passed to `RuleSet::getRules()` (#1253) ... and `getRulesAssoc()`. Relates to #1247. --- CHANGELOG.md | 3 +++ src/RuleSet/RuleSet.php | 15 ++++----------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 023117c6..7ed3cf5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,9 @@ Please also have a look at our ### Changed +- `RuleSet::getRules()` and `getRulesAssoc()` now only allow `string` or `null` + as the parameter (implementing classes are `AtRuleSet` and `DeclarationBlock`) + (#1253) - Parameters for `getAllValues()` are deconflated, so it now takes three (all optional), allowing `$element` and `$ruleSearchPattern` to be specified separately (#1241) diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 3ed5cf0f..e3cd629a 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -131,20 +131,15 @@ public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void * @example $ruleSet->getRules('font-') * //returns an array of all rules either beginning with font- or matching font. * - * @param Rule|string|null $searchPattern + * @param string|null $searchPattern * 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` for this parameter is deprecated in version 8.9.0, and will not work from v9.0. - * Call `getRules($rule->getRule())` instead. * * @return array, Rule> */ - public function getRules($searchPattern = null): array + public function getRules(?string $searchPattern = null): array { - if ($searchPattern instanceof Rule) { - $searchPattern = $searchPattern->getRule(); - } $result = []; foreach ($this->rules as $propertyName => $rules) { // Either no search rule is given or the search rule matches the found rule exactly @@ -191,16 +186,14 @@ public function setRules(array $rules): void * like `{ background-color: green; background-color; rgba(0, 127, 0, 0.7); }` will only yield an associative array * containing the rgba-valued rule while `getRules()` would yield an indexed array containing both. * - * @param Rule|string|null $searchPattern + * @param string|null $searchPattern * 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` for this parameter is deprecated in version 8.9.0, and will not work from v9.0. - * Call `getRulesAssoc($rule->getRule())` instead. * * @return array */ - public function getRulesAssoc($searchPattern = null): array + public function getRulesAssoc(?string $searchPattern = null): array { /** @var array $result */ $result = []; From bddb086102ca074b70073b59353c9ac5ec355d0a Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Tue, 6 May 2025 17:29:08 +0100 Subject: [PATCH 442/555] [TASK] Allow only `Rule` to be passed to `RuleSet::removeRule()` (#1255) Relates to #1247. --- CHANGELOG.md | 3 +++ src/RuleSet/RuleSet.php | 27 ++++++++------------------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ed3cf5c..69831196 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,9 @@ Please also have a look at our ### Changed +- `RuleSet::removeRule()` now only allows `Rule` as the parameter + (implementing classes are `AtRuleSet` and `DeclarationBlock`); + use `removeMatchingRules()` or `removeAllRules()` for other functions (#1255) - `RuleSet::getRules()` and `getRulesAssoc()` now only allow `string` or `null` as the parameter (implementing classes are `AtRuleSet` and `DeclarationBlock`) (#1253) diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index e3cd629a..e1a07f57 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -206,28 +206,17 @@ public function getRulesAssoc(?string $searchPattern = null): array /** * Removes a `Rule` from this `RuleSet` by identity. - * - * @param Rule|string|null $searchPattern - * `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($searchPattern): void + public function removeRule(Rule $ruleToRemove): void { - if ($searchPattern instanceof Rule) { - $nameOfPropertyToRemove = $searchPattern->getRule(); - if (!isset($this->rules[$nameOfPropertyToRemove])) { - return; - } - foreach ($this->rules[$nameOfPropertyToRemove] as $key => $rule) { - if ($rule === $searchPattern) { - unset($this->rules[$nameOfPropertyToRemove][$key]); - } + $nameOfPropertyToRemove = $ruleToRemove->getRule(); + if (!isset($this->rules[$nameOfPropertyToRemove])) { + return; + } + foreach ($this->rules[$nameOfPropertyToRemove] as $key => $rule) { + if ($rule === $ruleToRemove) { + unset($this->rules[$nameOfPropertyToRemove][$key]); } - } elseif ($searchPattern !== null) { - $this->removeMatchingRules($searchPattern); - } else { - $this->removeAllRules(); } } From 9e18840dc028f4b851ae402514ff54e8e9cdc87d Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sat, 10 May 2025 09:31:40 +0100 Subject: [PATCH 443/555] [CLEANUP] Check for `Positionable` in `lineNumbersParsing()` test (#1257) Now the interface has been added, it is no longer necessary to check the object is one of a long list of types. Also use the new `getLineNumber()` instead of the deprecated `getLineNo()`. --- tests/ParserTest.php | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/tests/ParserTest.php b/tests/ParserTest.php index ffda6b8c..656d943b 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -14,6 +14,7 @@ use Sabberworm\CSS\Parsing\OutputException; use Sabberworm\CSS\Parsing\SourceException; use Sabberworm\CSS\Parsing\UnexpectedTokenException; +use Sabberworm\CSS\Position\Positionable; use Sabberworm\CSS\Property\AtRule; use Sabberworm\CSS\Property\Charset; use Sabberworm\CSS\Property\CSSNamespace; @@ -975,31 +976,12 @@ public function lineNumbersParsing(): void $actual = []; foreach ($document->getContents() as $contentItem) { - // PHPStan can see what `assertInstanceOf()` does, - // but does not understand `LogicalOr` with multiple `IsIntanceOf` constraints. - // So a more explicit type check is required. - // TODO: tidy this up when an interface with `getLineNo()` is added. - if ( - !$contentItem instanceof Charset - && !$contentItem instanceof CSSList - && !$contentItem instanceof CSSNamespace - && !$contentItem instanceof Import - && !$contentItem instanceof RuleSet - ) { - self::fail('Content item is not of an expected type. It\'s a `' . \get_class($contentItem) . '`.'); - } - $actual[$contentItem->getLineNo()] = [\get_class($contentItem)]; + self::assertInstanceOf(Positionable::class, $contentItem); + $actual[$contentItem->getLineNumber()] = [\get_class($contentItem)]; if ($contentItem instanceof KeyFrame) { foreach ($contentItem->getContents() as $block) { - if ( - !$block instanceof CSSList - && !$block instanceof RuleSet - ) { - self::fail( - 'KeyFrame content item is not of an expected type. It\'s a `' . \get_class($block) . '`.' - ); - } - $actual[$contentItem->getLineNo()][] = $block->getLineNo(); + self::assertInstanceOf(Positionable::class, $block); + $actual[$contentItem->getLineNumber()][] = $block->getLineNumber(); } } } From 8587712dc63bb3c0874f6d507a6e9fc092a67378 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sat, 10 May 2025 09:32:50 +0100 Subject: [PATCH 444/555] [TASK] Add interface `RuleContainer` for `RuleSet` (#1256) This covers the maniplation of `Rule`s within the container, and may be implemented by other classes in future (e.g. #1194). Note that the naming is consistent with the current codebase, rather than what the CSS entities are now called: - `Rule` represents what is now called a "declaration"; - `RuleSet` represents what is now called a "declaration block"; - `DeclarationBlock` represents what is now called a "style rule"; - `CSSListItem` (closely) represents what is now generically called a "rule". Renaming things is part of a longer-term plan touched on in #1189. --- CHANGELOG.md | 1 + README.md | 4 ++++ src/CSSList/CSSBlockList.php | 3 ++- src/RuleSet/RuleContainer.php | 36 ++++++++++++++++++++++++++++++ src/RuleSet/RuleSet.php | 2 +- tests/Unit/RuleSet/RuleSetTest.php | 9 ++++++++ 6 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 src/RuleSet/RuleContainer.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 69831196..05c0f51d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Please also have a look at our ### Added +- Interface `RuleContainer` for `RuleSet` `Rule` manipulation methods (#1256) - `RuleSet::removeMatchingRules()` method (for the implementing classes `AtRuleSet` and `DeclarationBlock`) (#1249) - `RuleSet::removeAllRules()` method diff --git a/README.md b/README.md index b69cb6d2..9ecdc3e7 100644 --- a/README.md +++ b/README.md @@ -636,6 +636,9 @@ classDiagram class CSSListItem { <> } + class RuleContainer { + <> + } class DeclarationBlock { } class RuleSet { @@ -730,6 +733,7 @@ classDiagram Positionable <|.. RuleSet: realization CSSElement <|.. RuleSet: realization CSSListItem <|.. RuleSet: realization + RuleContainer <|.. RuleSet: realization RuleSet <|-- AtRuleSet: inheritance AtRule <|.. AtRuleSet: realization Renderable <|.. Selector: realization diff --git a/src/CSSList/CSSBlockList.php b/src/CSSList/CSSBlockList.php index 90643968..2dfd284f 100644 --- a/src/CSSList/CSSBlockList.php +++ b/src/CSSList/CSSBlockList.php @@ -8,6 +8,7 @@ use Sabberworm\CSS\Property\Selector; use Sabberworm\CSS\Rule\Rule; use Sabberworm\CSS\RuleSet\DeclarationBlock; +use Sabberworm\CSS\RuleSet\RuleContainer; use Sabberworm\CSS\RuleSet\RuleSet; use Sabberworm\CSS\Value\CSSFunction; use Sabberworm\CSS\Value\Value; @@ -95,7 +96,7 @@ public function getAllValues( ); } } - } elseif ($element instanceof RuleSet) { + } elseif ($element instanceof RuleContainer) { foreach ($element->getRules($ruleSearchPattern) as $rule) { $result = \array_merge( $result, diff --git a/src/RuleSet/RuleContainer.php b/src/RuleSet/RuleContainer.php new file mode 100644 index 00000000..0c6c5936 --- /dev/null +++ b/src/RuleSet/RuleContainer.php @@ -0,0 +1,36 @@ + $rules + */ + public function setRules(array $rules): void; + + /** + * @return array, Rule> + */ + public function getRules(?string $searchPattern = null): array; + + /** + * @return array + */ + public function getRulesAssoc(?string $searchPattern = null): array; +} diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index e1a07f57..1fc1295e 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -27,7 +27,7 @@ * Note that `CSSListItem` extends both `Commentable` and `Renderable`, * so those interfaces must also be implemented by concrete subclasses. */ -abstract class RuleSet implements CSSElement, CSSListItem, Positionable +abstract class RuleSet implements CSSElement, CSSListItem, Positionable, RuleContainer { use CommentContainer; use Position; diff --git a/tests/Unit/RuleSet/RuleSetTest.php b/tests/Unit/RuleSet/RuleSetTest.php index 774f37e7..03783b8e 100644 --- a/tests/Unit/RuleSet/RuleSetTest.php +++ b/tests/Unit/RuleSet/RuleSetTest.php @@ -8,6 +8,7 @@ use Sabberworm\CSS\CSSElement; use Sabberworm\CSS\CSSList\CSSListItem; use Sabberworm\CSS\Rule\Rule; +use Sabberworm\CSS\RuleSet\RuleContainer; use Sabberworm\CSS\Tests\Unit\RuleSet\Fixtures\ConcreteRuleSet; /** @@ -41,6 +42,14 @@ public function implementsCSSListItem(): void self::assertInstanceOf(CSSListItem::class, $this->subject); } + /** + * @test + */ + public function implementsRuleContainer(): void + { + self::assertInstanceOf(RuleContainer::class, $this->subject); + } + /** * @return array, 1: string, 2: list}> */ From 9ed1a4c0128606cd31613f44d18013805f01bf0d Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Thu, 22 May 2025 08:55:03 +0100 Subject: [PATCH 445/555] [TASK] Have `setPosition()` implement fluent interface (#1259) This will aid writing tests for `RuleSet`. Note that the PHP type annotation cannot be `self` because an interface is involved and PHP 7 is still supported. Part of #974. --- CHANGELOG.md | 2 ++ src/Position/Position.php | 6 +++++- src/Position/Positionable.php | 4 +++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05c0f51d..e603df7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,8 @@ Please also have a look at our ### Changed +- `setPosition()` (in `Rule` and other classes) now has fluent interface, + returning itself (#1259) - `RuleSet::removeRule()` now only allows `Rule` as the parameter (implementing classes are `AtRuleSet` and `DeclarationBlock`); use `removeMatchingRules()` or `removeAllRules()` for other functions (#1255) diff --git a/src/Position/Position.php b/src/Position/Position.php index 655f34b9..0771453b 100644 --- a/src/Position/Position.php +++ b/src/Position/Position.php @@ -58,11 +58,15 @@ public function getColNo(): int /** * @param int<0, max>|null $lineNumber * @param int<0, max>|null $columnNumber + * + * @return $this fluent interface */ - public function setPosition(?int $lineNumber, ?int $columnNumber = null): void + public function setPosition(?int $lineNumber, ?int $columnNumber = null): Positionable { // The conditional is for backwards compatibility (backcompat); `0` will not be allowed in future. $this->lineNumber = $lineNumber !== 0 ? $lineNumber : null; $this->columnNumber = $columnNumber; + + return $this; } } diff --git a/src/Position/Positionable.php b/src/Position/Positionable.php index 25a8295a..675fb55f 100644 --- a/src/Position/Positionable.php +++ b/src/Position/Positionable.php @@ -40,6 +40,8 @@ public function getColNo(): int; * 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 + * + * @return $this fluent interface */ - public function setPosition(?int $lineNumber, ?int $columnNumber = null): void; + public function setPosition(?int $lineNumber, ?int $columnNumber = null): Positionable; } From 042515ca195caf07d0bc4363dda205e0f36935a8 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Fri, 23 May 2025 08:54:04 +0100 Subject: [PATCH 446/555] [BUGFIX] Don't return objects from data providers (#1260) 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. --- tests/Unit/RuleSet/RuleSetTest.php | 89 +++++++++++++++++------------- 1 file changed, 51 insertions(+), 38 deletions(-) diff --git a/tests/Unit/RuleSet/RuleSetTest.php b/tests/Unit/RuleSet/RuleSetTest.php index 03783b8e..a9a33773 100644 --- a/tests/Unit/RuleSet/RuleSetTest.php +++ b/tests/Unit/RuleSet/RuleSetTest.php @@ -51,38 +51,38 @@ public function implementsRuleContainer(): void } /** - * @return array, 1: string, 2: list}> + * @return array, 1: string, 2: list}> */ - public static function provideRulesAndPropertyNameToRemoveAndExpectedRemainingPropertyNames(): array + public static function providePropertyNamesAndPropertyNameToRemoveAndExpectedRemainingPropertyNames(): array { 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'], ], @@ -92,7 +92,7 @@ public static function provideRulesAndPropertyNameToRemoveAndExpectedRemainingPr [], ], 'removing nonexistent rule from nonempty list' => [ - [new Rule('color'), new Rule('display')], + ['color', 'display'], 'width', ['color', 'display'], ], @@ -102,60 +102,60 @@ public static function provideRulesAndPropertyNameToRemoveAndExpectedRemainingPr /** * @test * - * @param list $rules + * @param list $initialPropertyNames * @param list $expectedRemainingPropertyNames * - * @dataProvider provideRulesAndPropertyNameToRemoveAndExpectedRemainingPropertyNames + * @dataProvider providePropertyNamesAndPropertyNameToRemoveAndExpectedRemainingPropertyNames */ public function removeMatchingRulesRemovesRulesByPropertyNameAndKeepsOthers( - array $rules, - string $propertyName, + array $initialPropertyNames, + string $propertyNameToRemove, array $expectedRemainingPropertyNames ): void { - $this->subject->setRules($rules); + $this->setRulesFromPropertyNames($initialPropertyNames); - $this->subject->removeMatchingRules($propertyName); + $this->subject->removeMatchingRules($propertyNameToRemove); $remainingRules = $this->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(): array + public static function providePropertyNamesAndPropertyNamePrefixToRemoveAndExpectedRemainingPropertyNames(): array { 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'], ], @@ -165,18 +165,18 @@ public static function provideRulesAndPropertyNamePrefixToRemoveAndExpectedRemai /** * @test * - * @param list $rules + * @param list $initialPropertyNames * @param list $expectedRemainingPropertyNames * - * @dataProvider provideRulesAndPropertyNamePrefixToRemoveAndExpectedRemainingPropertyNames + * @dataProvider providePropertyNamesAndPropertyNamePrefixToRemoveAndExpectedRemainingPropertyNames */ public function removeMatchingRulesRemovesRulesByPropertyNamePrefixAndKeepsOthers( - array $rules, + array $initialPropertyNames, string $propertyNamePrefix, array $expectedRemainingPropertyNames ): void { $propertyNamePrefixWithHyphen = $propertyNamePrefix . '-'; - $this->subject->setRules($rules); + $this->setRulesFromPropertyNames($initialPropertyNames); $this->subject->removeMatchingRules($propertyNamePrefixWithHyphen); @@ -191,31 +191,44 @@ public function removeMatchingRulesRemovesRulesByPropertyNamePrefixAndKeepsOther } /** - * @return array}> + * @return array}> */ - public static function provideRulesToRemove(): array + public static function providePropertyNamesToRemove(): array { 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): void + public function removeAllRulesRemovesAllRules(array $propertyNamesToRemove): void { - $this->subject->setRules($rules); + $this->setRulesFromPropertyNames($propertyNamesToRemove); $this->subject->removeAllRules(); self::assertSame([], $this->subject->getRules()); } + + /** + * @param list $propertyNames + */ + private function setRulesFromPropertyNames(array $propertyNames): void + { + $this->subject->setRules(\array_map( + static function (string $propertyName): Rule { + return new Rule($propertyName); + }, + $propertyNames + )); + } } From 9bc6622bd56c5c6eaa4b1db736484b42c793a92b Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Fri, 23 May 2025 19:36:56 +0100 Subject: [PATCH 447/555] [TASK] Add tests for `RuleSet::addRule()` without sibling argument (#1261) Some are currently skipped, pending some minor bug fixes. --- tests/Unit/RuleSet/RuleSetTest.php | 149 ++++++++++++++++++++++++++--- 1 file changed, 135 insertions(+), 14 deletions(-) diff --git a/tests/Unit/RuleSet/RuleSetTest.php b/tests/Unit/RuleSet/RuleSetTest.php index a9a33773..1fba6a22 100644 --- a/tests/Unit/RuleSet/RuleSetTest.php +++ b/tests/Unit/RuleSet/RuleSetTest.php @@ -10,6 +10,7 @@ use Sabberworm\CSS\Rule\Rule; use Sabberworm\CSS\RuleSet\RuleContainer; use Sabberworm\CSS\Tests\Unit\RuleSet\Fixtures\ConcreteRuleSet; +use TRegx\PhpUnit\DataProviders\DataProvider; /** * @covers \Sabberworm\CSS\RuleSet\RuleSet @@ -50,6 +51,139 @@ public function implementsRuleContainer(): void self::assertInstanceOf(RuleContainer::class, $this->subject); } + /** + * @return array}> + */ + public static function providePropertyNamesToBeSetInitially(): array + { + return [ + 'no properties' => [[]], + 'one property' => [['color']], + 'two different properties' => [['color', 'display']], + 'two of the same property' => [['color', 'color']], + ]; + } + + /** + * @return array + */ + public static function providePropertyNameToAdd(): array + { + 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 DataProvider, 1: string}> + */ + public static function provideInitialPropertyNamesAndPropertyNameToAdd(): DataProvider + { + return DataProvider::cross(self::providePropertyNamesToBeSetInitially(), self::providePropertyNameToAdd()); + } + + /** + * @test + * + * @param list $initialPropertyNames + * + * @dataProvider provideInitialPropertyNamesAndPropertyNameToAdd + */ + public function addRuleWithoutSiblingAddsRuleAfterInitialRulesAndSetsValidLineAndColumnNumbers( + array $initialPropertyNames, + string $propertyNameToAdd + ): void { + if ($initialPropertyNames === []) { + self::markTestSkipped('currently broken - first rule added does not have valid line number set'); + } + + $ruleToAdd = new Rule($propertyNameToAdd); + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->addRule($ruleToAdd); + + $rules = $this->subject->getRules(); + self::assertSame($ruleToAdd, \end($rules)); + self::assertIsInt($ruleToAdd->getLineNumber(), 'line number not set'); + self::assertGreaterThanOrEqual(1, $ruleToAdd->getLineNumber(), 'line number not valid'); + self::assertIsInt($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 + ): void { + self::markTestSkipped('currently broken - does not set column number'); + + $ruleToAdd = new Rule($propertyNameToAdd); + $ruleToAdd->setPosition(42); + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->addRule($ruleToAdd); + + self::assertContains($ruleToAdd, $this->subject->getRules()); + self::assertIsInt($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 + ): void { + self::markTestSkipped('currently broken - does not preserve column number'); + + $ruleToAdd = new Rule($propertyNameToAdd); + $ruleToAdd->setPosition(null, 42); + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->addRule($ruleToAdd); + + self::assertContains($ruleToAdd, $this->subject->getRules()); + self::assertIsInt($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 + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $ruleToAdd->setPosition(42, 64); + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->addRule($ruleToAdd); + + self::assertContains($ruleToAdd, $this->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}> */ @@ -190,25 +324,12 @@ public function removeMatchingRulesRemovesRulesByPropertyNamePrefixAndKeepsOther } } - /** - * @return array}> - */ - public static function providePropertyNamesToRemove(): array - { - 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): void { From 6c565cf8bdfd9bf48800575fae62ae0804660b7e Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Tue, 27 May 2025 09:28:39 +0100 Subject: [PATCH 448/555] [BUGFIX] Ensure column number set after `RuleSet::addRule()` (#1263) Note that this bug (or inconsistency) only occurs following the addtion of `getColumnNumber()` returning a nullable `int` (#1221 and #1225). These changes are not yet included in any release. Part of #974. --- CHANGELOG.md | 2 +- src/RuleSet/RuleSet.php | 2 ++ tests/Unit/RuleSet/RuleSetTest.php | 2 -- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e603df7f..258499c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ Please also have a look at our - 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) - Partial support for CSS Color Module Level 4: diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 1fc1295e..d92636df 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -118,6 +118,8 @@ public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void $last = $rules[$rulesCount - 1]; $ruleToAdd->setPosition($last->getLineNo() + 1, 0); } + } elseif ($ruleToAdd->getColumnNumber() === null) { + $ruleToAdd->setPosition($ruleToAdd->getLineNumber(), 0); } \array_splice($this->rules[$propertyName], $position, 0, [$ruleToAdd]); diff --git a/tests/Unit/RuleSet/RuleSetTest.php b/tests/Unit/RuleSet/RuleSetTest.php index 1fba6a22..294dff26 100644 --- a/tests/Unit/RuleSet/RuleSetTest.php +++ b/tests/Unit/RuleSet/RuleSetTest.php @@ -123,8 +123,6 @@ public function addRuleWithOnlyLineNumberAddsRuleAndSetsColumnNumberPreservingLi array $initialPropertyNames, string $propertyNameToAdd ): void { - self::markTestSkipped('currently broken - does not set column number'); - $ruleToAdd = new Rule($propertyNameToAdd); $ruleToAdd->setPosition(42); $this->setRulesFromPropertyNames($initialPropertyNames); From be60456434e8e9efb476c1a4ee78716d9857fc0d Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Wed, 28 May 2025 09:12:40 +0100 Subject: [PATCH 449/555] [BUGFIX] Ensure valid position for first `Rule` added to `RuleSet` (#1262) Part of #974. --- CHANGELOG.md | 1 + src/RuleSet/RuleSet.php | 2 ++ tests/Unit/RuleSet/RuleSetTest.php | 4 ---- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 258499c0..71fdf758 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,6 +105,7 @@ Please also have a look at our ### Fixed +- Ensure first rule added with `RuleSet::addRule()` has valid position (#1262) - Don't render `rgb` colors with percentage values using hex notation (#803) ### Documentation diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index d92636df..beb3deff 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -117,6 +117,8 @@ public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void if ($rulesCount > 0) { $last = $rules[$rulesCount - 1]; $ruleToAdd->setPosition($last->getLineNo() + 1, 0); + } else { + $ruleToAdd->setPosition(1, 0); } } elseif ($ruleToAdd->getColumnNumber() === null) { $ruleToAdd->setPosition($ruleToAdd->getLineNumber(), 0); diff --git a/tests/Unit/RuleSet/RuleSetTest.php b/tests/Unit/RuleSet/RuleSetTest.php index 294dff26..9e7c8381 100644 --- a/tests/Unit/RuleSet/RuleSetTest.php +++ b/tests/Unit/RuleSet/RuleSetTest.php @@ -95,10 +95,6 @@ public function addRuleWithoutSiblingAddsRuleAfterInitialRulesAndSetsValidLineAn array $initialPropertyNames, string $propertyNameToAdd ): void { - if ($initialPropertyNames === []) { - self::markTestSkipped('currently broken - first rule added does not have valid line number set'); - } - $ruleToAdd = new Rule($propertyNameToAdd); $this->setRulesFromPropertyNames($initialPropertyNames); From 4012ec5d1c5162480fd68cacae6f47303ec83d7f Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Tue, 3 Jun 2025 08:54:50 +0100 Subject: [PATCH 450/555] [BUGFIX] Set line number for `AddRule()` with only column number (#1265) Continue to preserve the column number. Also tighten the test to confirm the `Rule` is added at the end. Note that the reason for `markTestSkipped()` was incorrect - the line number was not being set at all. Part of #974. --- CHANGELOG.md | 2 ++ src/RuleSet/RuleSet.php | 7 ++++--- tests/Unit/RuleSet/RuleSetTest.php | 7 +++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71fdf758..005255af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,6 +105,8 @@ Please also have a look at our ### 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) - Don't render `rgb` colors with percentage values using hex notation (#803) diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index beb3deff..d211277e 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -110,15 +110,16 @@ public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void $ruleToAdd->setPosition($sibling->getLineNo(), $sibling->getColNo() - 1); } } - if ($ruleToAdd->getLineNo() === 0 && $ruleToAdd->getColNo() === 0) { + if ($ruleToAdd->getLineNumber() === null) { //this node is added manually, give it the next best line + $columnNumber = $ruleToAdd->getColumnNumber() ?? 0; $rules = $this->getRules(); $rulesCount = \count($rules); if ($rulesCount > 0) { $last = $rules[$rulesCount - 1]; - $ruleToAdd->setPosition($last->getLineNo() + 1, 0); + $ruleToAdd->setPosition($last->getLineNo() + 1, $columnNumber); } else { - $ruleToAdd->setPosition(1, 0); + $ruleToAdd->setPosition(1, $columnNumber); } } elseif ($ruleToAdd->getColumnNumber() === null) { $ruleToAdd->setPosition($ruleToAdd->getLineNumber(), 0); diff --git a/tests/Unit/RuleSet/RuleSetTest.php b/tests/Unit/RuleSet/RuleSetTest.php index 9e7c8381..384fc59f 100644 --- a/tests/Unit/RuleSet/RuleSetTest.php +++ b/tests/Unit/RuleSet/RuleSetTest.php @@ -138,19 +138,18 @@ public function addRuleWithOnlyLineNumberAddsRuleAndSetsColumnNumberPreservingLi * * @param list $initialPropertyNames */ - public function addRuleWithOnlyColumnNumberAddsRuleAndSetsLineNumberPreservingColumnNumber( + public function addRuleWithOnlyColumnNumberAddsRuleAfterInitialRulesAndSetsLineNumberPreservingColumnNumber( array $initialPropertyNames, string $propertyNameToAdd ): void { - self::markTestSkipped('currently broken - does not preserve column number'); - $ruleToAdd = new Rule($propertyNameToAdd); $ruleToAdd->setPosition(null, 42); $this->setRulesFromPropertyNames($initialPropertyNames); $this->subject->addRule($ruleToAdd); - self::assertContains($ruleToAdd, $this->subject->getRules()); + $rules = $this->subject->getRules(); + self::assertSame($ruleToAdd, \end($rules)); self::assertIsInt($ruleToAdd->getLineNumber(), 'line number not set'); self::assertGreaterThanOrEqual(1, $ruleToAdd->getLineNumber(), 'line number not valid'); self::assertSame(42, $ruleToAdd->getColumnNumber(), 'column number not preserved'); From a2239e059f8db2d733b567c554253ee9aa8e0c6e Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Tue, 3 Jun 2025 08:59:05 +0100 Subject: [PATCH 451/555] [TASK] Add tests for `addRule()` with a sibling (#1266) Some tests are skipped pending bug fixes. Part of #974. --- tests/Unit/RuleSet/RuleSetTest.php | 132 +++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) diff --git a/tests/Unit/RuleSet/RuleSetTest.php b/tests/Unit/RuleSet/RuleSetTest.php index 384fc59f..75aaf30b 100644 --- a/tests/Unit/RuleSet/RuleSetTest.php +++ b/tests/Unit/RuleSet/RuleSetTest.php @@ -177,6 +177,138 @@ public function addRuleWithCompletePositionAddsRuleAndPreservesPosition( self::assertSame(64, $ruleToAdd->getColumnNumber(), 'column number not preserved'); } + /** + * @return array, 1: int<0, max>}> + */ + public static function provideInitialPropertyNamesAndSiblingIndex(): array + { + $initialPropertyNamesSets = self::providePropertyNamesToBeSetInitially(); + + // Provide sets with each possible sibling index for the initially set `Rule`s. + $initialPropertyNamesAndSiblingIndexSets = []; + foreach ($initialPropertyNamesSets as $setName => $data) { + $initialPropertyNames = $data[0]; + for ($siblingIndex = 0; $siblingIndex < \count($initialPropertyNames); ++$siblingIndex) { + $initialPropertyNamesAndSiblingIndexSets[$setName . ', sibling index ' . $siblingIndex] = + [$initialPropertyNames, $siblingIndex]; + } + } + + return $initialPropertyNamesAndSiblingIndexSets; + } + + /** + * @return DataProvider, 1: int<0, max>, 2: string}> + */ + public static function provideInitialPropertyNamesAndSiblingIndexAndPropertyNameToAdd(): DataProvider + { + return DataProvider::cross( + self::provideInitialPropertyNamesAndSiblingIndex(), + self::providePropertyNameToAdd() + ); + } + + /** + * @test + * + * @param list $initialPropertyNames + * @param int<0, max> $siblingIndex + * + * @dataProvider provideInitialPropertyNamesAndSiblingIndexAndPropertyNameToAdd + */ + public function addRuleWithSiblingInsertsRuleBeforeSibling( + array $initialPropertyNames, + int $siblingIndex, + string $propertyNameToAdd + ): void { + self::markTestSkipped('tofix: if property names don\'t match, sibling is ignored'); + + $ruleToAdd = new Rule($propertyNameToAdd); + $this->setRulesFromPropertyNames($initialPropertyNames); + $sibling = $this->subject->getRules()[$siblingIndex]; + + $this->subject->addRule($ruleToAdd, $sibling); + + $rules = $this->subject->getRules(); + $siblingPosition = \array_search($sibling, $rules, true); + self::assertIsInt($siblingPosition); + self::assertSame($siblingPosition - 1, \array_search($ruleToAdd, $rules, true)); + } + + /** + * @test + * + * @param list $initialPropertyNames + * @param int<0, max> $siblingIndex + * + * @dataProvider provideInitialPropertyNamesAndSiblingIndexAndPropertyNameToAdd + */ + public function addRuleWithSiblingSetsValidLineNumber( + array $initialPropertyNames, + int $siblingIndex, + string $propertyNameToAdd + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $this->setRulesFromPropertyNames($initialPropertyNames); + $sibling = $this->subject->getRules()[$siblingIndex]; + + $this->subject->addRule($ruleToAdd, $sibling); + + self::assertIsInt($ruleToAdd->getLineNumber(), 'line number not set'); + self::assertGreaterThanOrEqual(1, $ruleToAdd->getLineNumber(), 'line number not valid'); + } + + /** + * @test + * + * @param list $initialPropertyNames + * @param int<0, max> $siblingIndex + * + * @dataProvider provideInitialPropertyNamesAndSiblingIndexAndPropertyNameToAdd + */ + public function addRuleWithSiblingSetsValidColumnNumber( + array $initialPropertyNames, + int $siblingIndex, + string $propertyNameToAdd + ): void { + self::markTestSkipped('tofix: sometimes a negative column number results'); + + $ruleToAdd = new Rule($propertyNameToAdd); + $this->setRulesFromPropertyNames($initialPropertyNames); + $sibling = $this->subject->getRules()[$siblingIndex]; + + $this->subject->addRule($ruleToAdd, $sibling); + + self::assertIsInt($ruleToAdd->getColumnNumber(), 'column number not set'); + self::assertGreaterThanOrEqual(0, $ruleToAdd->getColumnNumber(), 'column number not valid'); + } + + /** + * @test + * + * @param list $initialPropertyNames + * + * @dataProvider provideInitialPropertyNamesAndPropertyNameToAdd + */ + public function addRuleWithSiblingNotInRuleSetAddsRuleAfterInitialRulesAndSetsValidLineAndColumnNumbers( + array $initialPropertyNames, + string $propertyNameToAdd + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $this->setRulesFromPropertyNames($initialPropertyNames); + + // `display` is sometimes in `$initialPropertyNames` and sometimes the `$propertyNameToAdd`. + // Choosing this for the bogus sibling allows testing all combinations of whether it is or isn't. + $this->subject->addRule($ruleToAdd, new Rule('display')); + + $rules = $this->subject->getRules(); + self::assertSame($ruleToAdd, \end($rules)); + self::assertIsInt($ruleToAdd->getLineNumber(), 'line number not set'); + self::assertGreaterThanOrEqual(1, $ruleToAdd->getLineNumber(), 'line number not valid'); + self::assertIsInt($ruleToAdd->getColumnNumber(), 'column number not set'); + self::assertGreaterThanOrEqual(0, $ruleToAdd->getColumnNumber(), 'column number not valid'); + } + /** * @return array, 1: string, 2: list}> */ From a51fc821f7521928a47d58a11319c9be69e24498 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Tue, 3 Jun 2025 14:51:52 +0100 Subject: [PATCH 452/555] [BUGFIX] Ensure non-negative column number in `RuleSet` (#1268) When inserting a `Rule` before a sibling, increment the column number of other `Rule`s, instead of assigning a lower column number. Part of #974. --- CHANGELOG.md | 2 ++ src/RuleSet/RuleSet.php | 15 ++++++++++++++- tests/Unit/RuleSet/RuleSetTest.php | 2 -- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 005255af..5255f87e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,6 +105,8 @@ Please also have a look at our ### Fixed +- Ensure `RuleSet::addRule()` sets non-negative column number when sibling + provided (#1268) - Set line number when `RuleSet::addRule()` called with only column number set (#1265) - Ensure first rule added with `RuleSet::addRule()` has valid position (#1262) diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index d211277e..a1f26c66 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -107,7 +107,20 @@ public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void $siblingPosition = \array_search($sibling, $this->rules[$propertyName], true); if ($siblingPosition !== false) { $position = $siblingPosition; - $ruleToAdd->setPosition($sibling->getLineNo(), $sibling->getColNo() - 1); + // Increment column number of all existing rules on same line, starting at sibling + $siblingLineNumber = $sibling->getLineNumber(); + $siblingColumnNumber = $sibling->getColumnNumber(); + foreach ($this->rules as $rulesForAProperty) { + foreach ($rulesForAProperty as $rule) { + if ( + $rule->getLineNumber() === $siblingLineNumber && + $rule->getColumnNumber() >= $siblingColumnNumber + ) { + $rule->setPosition($siblingLineNumber, $rule->getColumnNumber() + 1); + } + } + } + $ruleToAdd->setPosition($siblingLineNumber, $siblingColumnNumber); } } if ($ruleToAdd->getLineNumber() === null) { diff --git a/tests/Unit/RuleSet/RuleSetTest.php b/tests/Unit/RuleSet/RuleSetTest.php index 75aaf30b..5b2bfa7a 100644 --- a/tests/Unit/RuleSet/RuleSetTest.php +++ b/tests/Unit/RuleSet/RuleSetTest.php @@ -271,8 +271,6 @@ public function addRuleWithSiblingSetsValidColumnNumber( int $siblingIndex, string $propertyNameToAdd ): void { - self::markTestSkipped('tofix: sometimes a negative column number results'); - $ruleToAdd = new Rule($propertyNameToAdd); $this->setRulesFromPropertyNames($initialPropertyNames); $sibling = $this->subject->getRules()[$siblingIndex]; From afdca11edbaaa6733f5e97bc094320769d607dc0 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Wed, 4 Jun 2025 19:40:19 +0100 Subject: [PATCH 453/555] [CLEANUP] Extract method `RuleSet::comparePositionable` (#1272) As well as being used with `usort()`, it may have other uses. The deprecated `getLineNo()` and `getColNo()` are still used for now. Replacing these will be done separately. Relates to #974. --- src/RuleSet/RuleSet.php | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index a1f26c66..b24eefab 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -173,12 +173,7 @@ public function getRules(?string $searchPattern = null): array $result = \array_merge($result, $rules); } } - \usort($result, static function (Rule $first, Rule $second): int { - if ($first->getLineNo() === $second->getLineNo()) { - return $first->getColNo() - $second->getColNo(); - } - return $first->getLineNo() - $second->getLineNo(); - }); + \usort($result, [self::class, 'comparePositionable']); return $result; } @@ -299,4 +294,15 @@ protected function renderRules(OutputFormat $outputFormat): string return $formatter->removeLastSemicolon($result); } + + /** + * @return int negative if `$first` is before `$second`; zero if they have the same position; positive otherwise + */ + private static function comparePositionable(Positionable $first, Positionable $second): int + { + if ($first->getLineNo() === $second->getLineNo()) { + return $first->getColNo() - $second->getColNo(); + } + return $first->getLineNo() - $second->getLineNo(); + } } From fdbb925ca0bc945b1cd246e8a6e47cc4a33d344e Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Thu, 5 Jun 2025 15:17:41 +0100 Subject: [PATCH 454/555] [BUGFIX] `AddRule` before sibling with different property name (#1270) Part of #974. --- CHANGELOG.md | 2 ++ src/RuleSet/RuleSet.php | 28 ++++++++++++++++++++++++++++ tests/Unit/RuleSet/RuleSetTest.php | 2 -- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5255f87e..7f69624e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,6 +105,8 @@ Please also have a look at our ### Fixed +- Insert `Rule` before sibling even with different property name + (in `RuleSet::addRule()`) (#1270) - Ensure `RuleSet::addRule()` sets non-negative column number when sibling provided (#1268) - Set line number when `RuleSet::addRule()` called with only column number set diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index b24eefab..8e0e8ae5 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -104,9 +104,25 @@ public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void $position = \count($this->rules[$propertyName]); if ($sibling !== null) { + $siblingIsInSet = false; $siblingPosition = \array_search($sibling, $this->rules[$propertyName], true); if ($siblingPosition !== false) { + $siblingIsInSet = true; $position = $siblingPosition; + } else { + $siblingIsInSet = $this->hasRule($sibling); + if ($siblingIsInSet) { + // Maintain ordering within `$this->rules[$propertyName]` + // by inserting before first `Rule` with a same-or-later position than the sibling. + foreach ($this->rules[$propertyName] as $index => $rule) { + if (self::comparePositionable($rule, $sibling) >= 0) { + $position = $index; + break; + } + } + } + } + if ($siblingIsInSet) { // Increment column number of all existing rules on same line, starting at sibling $siblingLineNumber = $sibling->getLineNumber(); $siblingColumnNumber = $sibling->getColumnNumber(); @@ -123,6 +139,7 @@ public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void $ruleToAdd->setPosition($siblingLineNumber, $siblingColumnNumber); } } + if ($ruleToAdd->getLineNumber() === null) { //this node is added manually, give it the next best line $columnNumber = $ruleToAdd->getColumnNumber() ?? 0; @@ -305,4 +322,15 @@ private static function comparePositionable(Positionable $first, Positionable $s } return $first->getLineNo() - $second->getLineNo(); } + + private function hasRule(Rule $rule): bool + { + foreach ($this->rules as $rulesForAProperty) { + if (\in_array($rule, $rulesForAProperty, true)) { + return true; + } + } + + return false; + } } diff --git a/tests/Unit/RuleSet/RuleSetTest.php b/tests/Unit/RuleSet/RuleSetTest.php index 5b2bfa7a..03cacd85 100644 --- a/tests/Unit/RuleSet/RuleSetTest.php +++ b/tests/Unit/RuleSet/RuleSetTest.php @@ -221,8 +221,6 @@ public function addRuleWithSiblingInsertsRuleBeforeSibling( int $siblingIndex, string $propertyNameToAdd ): void { - self::markTestSkipped('tofix: if property names don\'t match, sibling is ignored'); - $ruleToAdd = new Rule($propertyNameToAdd); $this->setRulesFromPropertyNames($initialPropertyNames); $sibling = $this->subject->getRules()[$siblingIndex]; From d13d953526cb211092ba9d48d8fb45c9e95fe84d Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Tue, 10 Jun 2025 16:25:44 +0100 Subject: [PATCH 455/555] [TASK] Add unit tests for `RuleSet::removeRule` (#1273) This re-uses some data providers, which have been renamed to reflect their more generic usage. Also, the PHPDoc type has been tightened to `non-empty-list` where applicable. Part of #974. --- tests/Unit/RuleSet/RuleSetTest.php | 96 +++++++++++++++++++++++------- 1 file changed, 76 insertions(+), 20 deletions(-) diff --git a/tests/Unit/RuleSet/RuleSetTest.php b/tests/Unit/RuleSet/RuleSetTest.php index 03cacd85..933229c3 100644 --- a/tests/Unit/RuleSet/RuleSetTest.php +++ b/tests/Unit/RuleSet/RuleSetTest.php @@ -67,7 +67,7 @@ public static function providePropertyNamesToBeSetInitially(): array /** * @return array */ - public static function providePropertyNameToAdd(): array + public static function provideAnotherPropertyName(): array { return [ 'property name `color` maybe matching that of existing declaration' => ['color'], @@ -79,9 +79,9 @@ public static function providePropertyNameToAdd(): array /** * @return DataProvider, 1: string}> */ - public static function provideInitialPropertyNamesAndPropertyNameToAdd(): DataProvider + public static function provideInitialPropertyNamesAndAnotherPropertyName(): DataProvider { - return DataProvider::cross(self::providePropertyNamesToBeSetInitially(), self::providePropertyNameToAdd()); + return DataProvider::cross(self::providePropertyNamesToBeSetInitially(), self::provideAnotherPropertyName()); } /** @@ -89,7 +89,7 @@ public static function provideInitialPropertyNamesAndPropertyNameToAdd(): DataPr * * @param list $initialPropertyNames * - * @dataProvider provideInitialPropertyNamesAndPropertyNameToAdd + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName */ public function addRuleWithoutSiblingAddsRuleAfterInitialRulesAndSetsValidLineAndColumnNumbers( array $initialPropertyNames, @@ -111,7 +111,7 @@ public function addRuleWithoutSiblingAddsRuleAfterInitialRulesAndSetsValidLineAn /** * @test * - * @dataProvider provideInitialPropertyNamesAndPropertyNameToAdd + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName * * @param list $initialPropertyNames */ @@ -134,7 +134,7 @@ public function addRuleWithOnlyLineNumberAddsRuleAndSetsColumnNumberPreservingLi /** * @test * - * @dataProvider provideInitialPropertyNamesAndPropertyNameToAdd + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName * * @param list $initialPropertyNames */ @@ -158,7 +158,7 @@ public function addRuleWithOnlyColumnNumberAddsRuleAfterInitialRulesAndSetsLineN /** * @test * - * @dataProvider provideInitialPropertyNamesAndPropertyNameToAdd + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName * * @param list $initialPropertyNames */ @@ -180,21 +180,21 @@ public function addRuleWithCompletePositionAddsRuleAndPreservesPosition( /** * @return array, 1: int<0, max>}> */ - public static function provideInitialPropertyNamesAndSiblingIndex(): array + public static function provideInitialPropertyNamesAndIndexOfOne(): array { $initialPropertyNamesSets = self::providePropertyNamesToBeSetInitially(); - // Provide sets with each possible sibling index for the initially set `Rule`s. - $initialPropertyNamesAndSiblingIndexSets = []; + // Provide sets with each possible index for the initially set `Rule`s. + $initialPropertyNamesAndIndexSets = []; foreach ($initialPropertyNamesSets as $setName => $data) { $initialPropertyNames = $data[0]; - for ($siblingIndex = 0; $siblingIndex < \count($initialPropertyNames); ++$siblingIndex) { - $initialPropertyNamesAndSiblingIndexSets[$setName . ', sibling index ' . $siblingIndex] = - [$initialPropertyNames, $siblingIndex]; + for ($index = 0; $index < \count($initialPropertyNames); ++$index) { + $initialPropertyNamesAndIndexSets[$setName . ', index ' . $index] = + [$initialPropertyNames, $index]; } } - return $initialPropertyNamesAndSiblingIndexSets; + return $initialPropertyNamesAndIndexSets; } /** @@ -203,15 +203,15 @@ public static function provideInitialPropertyNamesAndSiblingIndex(): array public static function provideInitialPropertyNamesAndSiblingIndexAndPropertyNameToAdd(): DataProvider { return DataProvider::cross( - self::provideInitialPropertyNamesAndSiblingIndex(), - self::providePropertyNameToAdd() + self::provideInitialPropertyNamesAndIndexOfOne(), + self::provideAnotherPropertyName() ); } /** * @test * - * @param list $initialPropertyNames + * @param non-empty-list $initialPropertyNames * @param int<0, max> $siblingIndex * * @dataProvider provideInitialPropertyNamesAndSiblingIndexAndPropertyNameToAdd @@ -236,7 +236,7 @@ public function addRuleWithSiblingInsertsRuleBeforeSibling( /** * @test * - * @param list $initialPropertyNames + * @param non-empty-list $initialPropertyNames * @param int<0, max> $siblingIndex * * @dataProvider provideInitialPropertyNamesAndSiblingIndexAndPropertyNameToAdd @@ -259,7 +259,7 @@ public function addRuleWithSiblingSetsValidLineNumber( /** * @test * - * @param list $initialPropertyNames + * @param non-empty-list $initialPropertyNames * @param int<0, max> $siblingIndex * * @dataProvider provideInitialPropertyNamesAndSiblingIndexAndPropertyNameToAdd @@ -284,7 +284,7 @@ public function addRuleWithSiblingSetsValidColumnNumber( * * @param list $initialPropertyNames * - * @dataProvider provideInitialPropertyNamesAndPropertyNameToAdd + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName */ public function addRuleWithSiblingNotInRuleSetAddsRuleAfterInitialRulesAndSetsValidLineAndColumnNumbers( array $initialPropertyNames, @@ -305,6 +305,62 @@ public function addRuleWithSiblingNotInRuleSetAddsRuleAfterInitialRulesAndSetsVa self::assertGreaterThanOrEqual(0, $ruleToAdd->getColumnNumber(), 'column number not valid'); } + /** + * @test + * + * @param non-empty-list $initialPropertyNames + * @param int<0, max> $indexToRemove + * + * @dataProvider provideInitialPropertyNamesAndIndexOfOne + */ + public function removeRuleRemovesRuleInSet(array $initialPropertyNames, int $indexToRemove): void + { + $this->setRulesFromPropertyNames($initialPropertyNames); + $ruleToRemove = $this->subject->getRules()[$indexToRemove]; + + $this->subject->removeRule($ruleToRemove); + + self::assertNotContains($ruleToRemove, $this->subject->getRules()); + } + + /** + * @test + * + * @param non-empty-list $initialPropertyNames + * @param int<0, max> $indexToRemove + * + * @dataProvider provideInitialPropertyNamesAndIndexOfOne + */ + public function removeRuleRemovesExactlyOneRule(array $initialPropertyNames, int $indexToRemove): void + { + $this->setRulesFromPropertyNames($initialPropertyNames); + $ruleToRemove = $this->subject->getRules()[$indexToRemove]; + + $this->subject->removeRule($ruleToRemove); + + self::assertCount(\count($initialPropertyNames) - 1, $this->subject->getRules()); + } + + /** + * @test + * + * @param list $initialPropertyNames + * + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName + */ + public function removeRuleWithRuleNotInSetKeepsSetUnchanged( + array $initialPropertyNames, + string $propertyNameToRemove + ): void { + $this->setRulesFromPropertyNames($initialPropertyNames); + $initialRules = $this->subject->getRules(); + $ruleToRemove = new Rule($propertyNameToRemove); + + $this->subject->removeRule($ruleToRemove); + + self::assertSame($initialRules, $this->subject->getRules()); + } + /** * @return array, 1: string, 2: list}> */ From 601b70653c1c4a38eb9357e557b3143a201bd4d2 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Wed, 11 Jun 2025 14:14:17 +0100 Subject: [PATCH 456/555] [CLEANUP] Separate some test methods in `RuleSetTest` (#1274) These were previously testing more than one aspect of a `RuleSet` method. Now each behaviour has a dedicated test method (albeit with some duplication of the set-up). Also added an additional assertion when there are no expected remaining items following removal, so that an assertion is made. --- tests/Unit/RuleSet/RuleSetTest.php | 235 +++++++++++++++++++++++++++-- 1 file changed, 224 insertions(+), 11 deletions(-) diff --git a/tests/Unit/RuleSet/RuleSetTest.php b/tests/Unit/RuleSet/RuleSetTest.php index 933229c3..3c9f1bdb 100644 --- a/tests/Unit/RuleSet/RuleSetTest.php +++ b/tests/Unit/RuleSet/RuleSetTest.php @@ -91,7 +91,7 @@ public static function provideInitialPropertyNamesAndAnotherPropertyName(): Data * * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName */ - public function addRuleWithoutSiblingAddsRuleAfterInitialRulesAndSetsValidLineAndColumnNumbers( + public function addRuleWithoutPositionWithoutSiblingAddsRuleAfterInitialRules( array $initialPropertyNames, string $propertyNameToAdd ): void { @@ -102,8 +102,44 @@ public function addRuleWithoutSiblingAddsRuleAfterInitialRulesAndSetsValidLineAn $rules = $this->subject->getRules(); self::assertSame($ruleToAdd, \end($rules)); + } + + /** + * @test + * + * @param list $initialPropertyNames + * + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName + */ + public function addRuleWithoutPositionWithoutSiblingSetsValidLineNumber( + array $initialPropertyNames, + string $propertyNameToAdd + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->addRule($ruleToAdd); + self::assertIsInt($ruleToAdd->getLineNumber(), 'line number not set'); self::assertGreaterThanOrEqual(1, $ruleToAdd->getLineNumber(), 'line number not valid'); + } + + /** + * @test + * + * @param list $initialPropertyNames + * + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName + */ + public function addRuleWithoutPositionWithoutSiblingSetsValidColumnNumber( + array $initialPropertyNames, + string $propertyNameToAdd + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->addRule($ruleToAdd); + self::assertIsInt($ruleToAdd->getColumnNumber(), 'column number not set'); self::assertGreaterThanOrEqual(0, $ruleToAdd->getColumnNumber(), 'column number not valid'); } @@ -115,7 +151,7 @@ public function addRuleWithoutSiblingAddsRuleAfterInitialRulesAndSetsValidLineAn * * @param list $initialPropertyNames */ - public function addRuleWithOnlyLineNumberAddsRuleAndSetsColumnNumberPreservingLineNumber( + public function addRuleWithOnlyLineNumberWithoutSiblingAddsRule( array $initialPropertyNames, string $propertyNameToAdd ): void { @@ -126,8 +162,46 @@ public function addRuleWithOnlyLineNumberAddsRuleAndSetsColumnNumberPreservingLi $this->subject->addRule($ruleToAdd); self::assertContains($ruleToAdd, $this->subject->getRules()); + } + + /** + * @test + * + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName + * + * @param list $initialPropertyNames + */ + public function addRuleWithOnlyLineNumberWithoutSiblingSetsColumnNumber( + array $initialPropertyNames, + string $propertyNameToAdd + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $ruleToAdd->setPosition(42); + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->addRule($ruleToAdd); + self::assertIsInt($ruleToAdd->getColumnNumber(), 'column number not set'); self::assertGreaterThanOrEqual(0, $ruleToAdd->getColumnNumber(), 'column number not valid'); + } + + /** + * @test + * + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName + * + * @param list $initialPropertyNames + */ + public function addRuleWithOnlyLineNumberWithoutSiblingPreservesLineNumber( + array $initialPropertyNames, + string $propertyNameToAdd + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $ruleToAdd->setPosition(42); + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->addRule($ruleToAdd); + self::assertSame(42, $ruleToAdd->getLineNumber(), 'line number not preserved'); } @@ -138,7 +212,7 @@ public function addRuleWithOnlyLineNumberAddsRuleAndSetsColumnNumberPreservingLi * * @param list $initialPropertyNames */ - public function addRuleWithOnlyColumnNumberAddsRuleAfterInitialRulesAndSetsLineNumberPreservingColumnNumber( + public function addRuleWithOnlyColumnNumberWithoutSiblingAddsRuleAfterInitialRules( array $initialPropertyNames, string $propertyNameToAdd ): void { @@ -150,8 +224,46 @@ public function addRuleWithOnlyColumnNumberAddsRuleAfterInitialRulesAndSetsLineN $rules = $this->subject->getRules(); self::assertSame($ruleToAdd, \end($rules)); + } + + /** + * @test + * + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName + * + * @param list $initialPropertyNames + */ + public function addRuleWithOnlyColumnNumberWithoutSiblingSetsLineNumber( + array $initialPropertyNames, + string $propertyNameToAdd + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $ruleToAdd->setPosition(null, 42); + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->addRule($ruleToAdd); + self::assertIsInt($ruleToAdd->getLineNumber(), 'line number not set'); self::assertGreaterThanOrEqual(1, $ruleToAdd->getLineNumber(), 'line number not valid'); + } + + /** + * @test + * + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName + * + * @param list $initialPropertyNames + */ + public function addRuleWithOnlyColumnNumberWithoutSiblingPreservesColumnNumber( + array $initialPropertyNames, + string $propertyNameToAdd + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $ruleToAdd->setPosition(null, 42); + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->addRule($ruleToAdd); + self::assertSame(42, $ruleToAdd->getColumnNumber(), 'column number not preserved'); } @@ -162,7 +274,7 @@ public function addRuleWithOnlyColumnNumberAddsRuleAfterInitialRulesAndSetsLineN * * @param list $initialPropertyNames */ - public function addRuleWithCompletePositionAddsRuleAndPreservesPosition( + public function addRuleWithCompletePositionWithoutSiblingAddsRule( array $initialPropertyNames, string $propertyNameToAdd ): void { @@ -173,6 +285,25 @@ public function addRuleWithCompletePositionAddsRuleAndPreservesPosition( $this->subject->addRule($ruleToAdd); self::assertContains($ruleToAdd, $this->subject->getRules()); + } + + /** + * @test + * + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName + * + * @param list $initialPropertyNames + */ + public function addRuleWithCompletePositionWithoutSiblingPreservesPosition( + array $initialPropertyNames, + string $propertyNameToAdd + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $ruleToAdd->setPosition(42, 64); + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->addRule($ruleToAdd); + self::assertSame(42, $ruleToAdd->getLineNumber(), 'line number not preserved'); self::assertSame(64, $ruleToAdd->getColumnNumber(), 'column number not preserved'); } @@ -286,7 +417,7 @@ public function addRuleWithSiblingSetsValidColumnNumber( * * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName */ - public function addRuleWithSiblingNotInRuleSetAddsRuleAfterInitialRulesAndSetsValidLineAndColumnNumbers( + public function addRuleWithSiblingNotInSetAddsRuleAfterInitialRules( array $initialPropertyNames, string $propertyNameToAdd ): void { @@ -299,8 +430,48 @@ public function addRuleWithSiblingNotInRuleSetAddsRuleAfterInitialRulesAndSetsVa $rules = $this->subject->getRules(); self::assertSame($ruleToAdd, \end($rules)); + } + + /** + * @test + * + * @param list $initialPropertyNames + * + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName + */ + public function addRuleWithSiblingNotInSetSetsValidLineNumber( + array $initialPropertyNames, + string $propertyNameToAdd + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $this->setRulesFromPropertyNames($initialPropertyNames); + + // `display` is sometimes in `$initialPropertyNames` and sometimes the `$propertyNameToAdd`. + // Choosing this for the bogus sibling allows testing all combinations of whether it is or isn't. + $this->subject->addRule($ruleToAdd, new Rule('display')); + self::assertIsInt($ruleToAdd->getLineNumber(), 'line number not set'); self::assertGreaterThanOrEqual(1, $ruleToAdd->getLineNumber(), 'line number not valid'); + } + + /** + * @test + * + * @param list $initialPropertyNames + * + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName + */ + public function addRuleWithSiblingNotInSetSetsValidColumnNumber( + array $initialPropertyNames, + string $propertyNameToAdd + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $this->setRulesFromPropertyNames($initialPropertyNames); + + // `display` is sometimes in `$initialPropertyNames` and sometimes the `$propertyNameToAdd`. + // Choosing this for the bogus sibling allows testing all combinations of whether it is or isn't. + $this->subject->addRule($ruleToAdd, new Rule('display')); + self::assertIsInt($ruleToAdd->getColumnNumber(), 'column number not set'); self::assertGreaterThanOrEqual(0, $ruleToAdd->getColumnNumber(), 'column number not valid'); } @@ -410,6 +581,24 @@ public static function providePropertyNamesAndPropertyNameToRemoveAndExpectedRem ]; } + /** + * @test + * + * @param list $initialPropertyNames + * + * @dataProvider providePropertyNamesAndPropertyNameToRemoveAndExpectedRemainingPropertyNames + */ + public function removeMatchingRulesRemovesRulesWithPropertyName( + array $initialPropertyNames, + string $propertyNameToRemove + ): void { + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->removeMatchingRules($propertyNameToRemove); + + self::assertArrayNotHasKey($propertyNameToRemove, $this->subject->getRulesAssoc()); + } + /** * @test * @@ -418,7 +607,7 @@ public static function providePropertyNamesAndPropertyNameToRemoveAndExpectedRem * * @dataProvider providePropertyNamesAndPropertyNameToRemoveAndExpectedRemainingPropertyNames */ - public function removeMatchingRulesRemovesRulesByPropertyNameAndKeepsOthers( + public function removeMatchingRulesWithPropertyNameKeepsOtherRules( array $initialPropertyNames, string $propertyNameToRemove, array $expectedRemainingPropertyNames @@ -428,7 +617,9 @@ public function removeMatchingRulesRemovesRulesByPropertyNameAndKeepsOthers( $this->subject->removeMatchingRules($propertyNameToRemove); $remainingRules = $this->subject->getRulesAssoc(); - self::assertArrayNotHasKey($propertyNameToRemove, $remainingRules); + if ($expectedRemainingPropertyNames === []) { + self::assertSame([], $remainingRules); + } foreach ($expectedRemainingPropertyNames as $expectedPropertyName) { self::assertArrayHasKey($expectedPropertyName, $remainingRules); } @@ -477,14 +668,12 @@ public static function providePropertyNamesAndPropertyNamePrefixToRemoveAndExpec * @test * * @param list $initialPropertyNames - * @param list $expectedRemainingPropertyNames * * @dataProvider providePropertyNamesAndPropertyNamePrefixToRemoveAndExpectedRemainingPropertyNames */ - public function removeMatchingRulesRemovesRulesByPropertyNamePrefixAndKeepsOthers( + public function removeMatchingRulesRemovesRulesWithPropertyNamePrefix( array $initialPropertyNames, - string $propertyNamePrefix, - array $expectedRemainingPropertyNames + string $propertyNamePrefix ): void { $propertyNamePrefixWithHyphen = $propertyNamePrefix . '-'; $this->setRulesFromPropertyNames($initialPropertyNames); @@ -496,6 +685,30 @@ public function removeMatchingRulesRemovesRulesByPropertyNamePrefixAndKeepsOther foreach (\array_keys($remainingRules) as $remainingPropertyName) { self::assertStringStartsNotWith($propertyNamePrefixWithHyphen, $remainingPropertyName); } + } + + /** + * @test + * + * @param list $initialPropertyNames + * @param list $expectedRemainingPropertyNames + * + * @dataProvider providePropertyNamesAndPropertyNamePrefixToRemoveAndExpectedRemainingPropertyNames + */ + public function removeMatchingRulesWithPropertyNamePrefixKeepsOtherRules( + array $initialPropertyNames, + string $propertyNamePrefix, + array $expectedRemainingPropertyNames + ): void { + $propertyNamePrefixWithHyphen = $propertyNamePrefix . '-'; + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->removeMatchingRules($propertyNamePrefixWithHyphen); + + $remainingRules = $this->subject->getRulesAssoc(); + if ($expectedRemainingPropertyNames === []) { + self::assertSame([], $remainingRules); + } foreach ($expectedRemainingPropertyNames as $expectedPropertyName) { self::assertArrayHasKey($expectedPropertyName, $remainingRules); } From 9841e9ff8f27428c4321531ea5c3b71b9ef097a6 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 12 Jun 2025 11:39:17 +0200 Subject: [PATCH 457/555] [DOCS] Switch to the new CoC email address (#1275) --- CODE_OF_CONDUCT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index d50e40b4..1b87c093 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -60,7 +60,7 @@ representative at an online or offline event. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at -(coc-github at myintervals dot com). +(myintervals-coc at gaggle dot email). All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the From fec1489c2aa6fd403783420fa4dbfda4619646cc Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Fri, 13 Jun 2025 18:41:55 +0100 Subject: [PATCH 458/555] [TASK] Add unit tests for `RuleSet::setRules` (#1276) Also rename a data provider to indicate its (now) more generic purpose. --- tests/Unit/RuleSet/RuleSetTest.php | 160 +++++++++++++++++++++++++++-- 1 file changed, 153 insertions(+), 7 deletions(-) diff --git a/tests/Unit/RuleSet/RuleSetTest.php b/tests/Unit/RuleSet/RuleSetTest.php index 3c9f1bdb..f36db888 100644 --- a/tests/Unit/RuleSet/RuleSetTest.php +++ b/tests/Unit/RuleSet/RuleSetTest.php @@ -54,7 +54,7 @@ public function implementsRuleContainer(): void /** * @return array}> */ - public static function providePropertyNamesToBeSetInitially(): array + public static function providePropertyNames(): array { return [ 'no properties' => [[]], @@ -81,7 +81,7 @@ public static function provideAnotherPropertyName(): array */ public static function provideInitialPropertyNamesAndAnotherPropertyName(): DataProvider { - return DataProvider::cross(self::providePropertyNamesToBeSetInitially(), self::provideAnotherPropertyName()); + return DataProvider::cross(self::providePropertyNames(), self::provideAnotherPropertyName()); } /** @@ -313,7 +313,7 @@ public function addRuleWithCompletePositionWithoutSiblingPreservesPosition( */ public static function provideInitialPropertyNamesAndIndexOfOne(): array { - $initialPropertyNamesSets = self::providePropertyNamesToBeSetInitially(); + $initialPropertyNamesSets = self::providePropertyNames(); // Provide sets with each possible index for the initially set `Rule`s. $initialPropertyNamesAndIndexSets = []; @@ -719,7 +719,7 @@ public function removeMatchingRulesWithPropertyNamePrefixKeepsOtherRules( * * @param list $propertyNamesToRemove * - * @dataProvider providePropertyNamesToBeSetInitially + * @dataProvider providePropertyNames */ public function removeAllRulesRemovesAllRules(array $propertyNamesToRemove): void { @@ -730,16 +730,162 @@ public function removeAllRulesRemovesAllRules(array $propertyNamesToRemove): voi self::assertSame([], $this->subject->getRules()); } + /** + * @test + * + * @param list $propertyNamesToSet + * + * @dataProvider providePropertyNames + */ + public function setRulesOnVirginSetsRulesWithoutPositionInOrder(array $propertyNamesToSet): void + { + $rulesToSet = self::createRulesFromPropertyNames($propertyNamesToSet); + + $this->subject->setRules($rulesToSet); + + self::assertSame($rulesToSet, $this->subject->getRules()); + } + + /** + * @return DataProvider, 1: list}> + */ + public static function provideInitialPropertyNamesAndPropertyNamesToSet(): DataProvider + { + return DataProvider::cross(self::providePropertyNames(), self::providePropertyNames()); + } + + /** + * @test + * + * @param list $initialPropertyNames + * @param list $propertyNamesToSet + * + * @dataProvider provideInitialPropertyNamesAndPropertyNamesToSet + */ + public function setRulesReplacesRules(array $initialPropertyNames, array $propertyNamesToSet): void + { + $rulesToSet = self::createRulesFromPropertyNames($propertyNamesToSet); + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->setRules($rulesToSet); + + self::assertSame($rulesToSet, $this->subject->getRules()); + } + + /** + * @test + */ + public function setRulesWithRuleWithoutPositionSetsValidLineNumber(): void + { + $ruleToSet = new Rule('color'); + + $this->subject->setRules([$ruleToSet]); + + self::assertIsInt($ruleToSet->getLineNumber(), 'line number not set'); + self::assertGreaterThanOrEqual(1, $ruleToSet->getLineNumber(), 'line number not valid'); + } + + /** + * @test + */ + public function setRulesWithRuleWithoutPositionSetsValidColumnNumber(): void + { + $ruleToSet = new Rule('color'); + + $this->subject->setRules([$ruleToSet]); + + self::assertIsInt($ruleToSet->getColumnNumber(), 'column number not set'); + self::assertGreaterThanOrEqual(0, $ruleToSet->getColumnNumber(), 'column number not valid'); + } + + /** + * @test + */ + public function setRulesWithRuleWithOnlyLineNumberSetsColumnNumber(): void + { + $ruleToSet = new Rule('color'); + $ruleToSet->setPosition(42); + + $this->subject->setRules([$ruleToSet]); + + self::assertIsInt($ruleToSet->getColumnNumber(), 'column number not set'); + self::assertGreaterThanOrEqual(0, $ruleToSet->getColumnNumber(), 'column number not valid'); + } + + /** + * @test + */ + public function setRulesWithRuleWithOnlyLineNumberPreservesLineNumber(): void + { + $ruleToSet = new Rule('color'); + $ruleToSet->setPosition(42); + + $this->subject->setRules([$ruleToSet]); + + self::assertSame(42, $ruleToSet->getLineNumber(), 'line number not preserved'); + } + + /** + * @test + */ + public function setRulesWithRuleWithOnlyColumnNumberSetsLineNumber(): void + { + $ruleToSet = new Rule('color'); + $ruleToSet->setPosition(null, 42); + + $this->subject->setRules([$ruleToSet]); + + self::assertIsInt($ruleToSet->getLineNumber(), 'line number not set'); + self::assertGreaterThanOrEqual(1, $ruleToSet->getLineNumber(), 'line number not valid'); + } + + /** + * @test + */ + public function setRulesWithRuleWithOnlyColumnNumberPreservesColumnNumber(): void + { + $ruleToSet = new Rule('color'); + $ruleToSet->setPosition(null, 42); + + $this->subject->setRules([$ruleToSet]); + + self::assertSame(42, $ruleToSet->getColumnNumber(), 'column number not preserved'); + } + + /** + * @test + */ + public function setRulesWithRuleWithCompletePositionPreservesPosition(): void + { + $ruleToSet = new Rule('color'); + $ruleToSet->setPosition(42, 64); + + $this->subject->setRules([$ruleToSet]); + + self::assertSame(42, $ruleToSet->getLineNumber(), 'line number not preserved'); + self::assertSame(64, $ruleToSet->getColumnNumber(), 'column number not preserved'); + } + /** * @param list $propertyNames */ private function setRulesFromPropertyNames(array $propertyNames): void { - $this->subject->setRules(\array_map( - static function (string $propertyName): Rule { + $this->subject->setRules(self::createRulesFromPropertyNames($propertyNames)); + } + + /** + * @param list $propertyNames + * + * @return list + */ + private static function createRulesFromPropertyNames(array $propertyNames): array + { + return \array_map( + function (string $propertyName): Rule { return new Rule($propertyName); }, $propertyNames - )); + ); } } From 2433e6f505d3d65b55027a2369179e2b8dd9c514 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Fri, 13 Jun 2025 20:14:51 +0100 Subject: [PATCH 459/555] [TASK] Add unit tests for `RuleSet::getRules` (#1277) Part of #974. --- tests/Unit/RuleSet/RuleSetTest.php | 47 ++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/Unit/RuleSet/RuleSetTest.php b/tests/Unit/RuleSet/RuleSetTest.php index f36db888..68ae348e 100644 --- a/tests/Unit/RuleSet/RuleSetTest.php +++ b/tests/Unit/RuleSet/RuleSetTest.php @@ -866,6 +866,53 @@ public function setRulesWithRuleWithCompletePositionPreservesPosition(): void self::assertSame(64, $ruleToSet->getColumnNumber(), 'column number not preserved'); } + /** + * @test + * + * @param list $propertyNamesToSet + * + * @dataProvider providePropertyNames + */ + public function getRulesReturnsRulesSet(array $propertyNamesToSet): void + { + $rulesToSet = self::createRulesFromPropertyNames($propertyNamesToSet); + $this->subject->setRules($rulesToSet); + + $result = $this->subject->getRules(); + + self::assertSame($rulesToSet, $result); + } + + /** + * @test + */ + public function getRulesOrdersByLineNumber(): void + { + $first = (new Rule('color'))->setPosition(1, 64); + $second = (new Rule('display'))->setPosition(19, 42); + $third = (new Rule('color'))->setPosition(55, 11); + $this->subject->setRules([$third, $second, $first]); + + $result = $this->subject->getRules(); + + self::assertSame([$first, $second, $third], $result); + } + + /** + * @test + */ + public function getRulesOrdersRulesWithSameLineNumberByColumnNumber(): void + { + $first = (new Rule('color'))->setPosition(1, 11); + $second = (new Rule('display'))->setPosition(1, 42); + $third = (new Rule('color'))->setPosition(1, 64); + $this->subject->setRules([$third, $second, $first]); + + $result = $this->subject->getRules(); + + self::assertSame([$first, $second, $third], $result); + } + /** * @param list $propertyNames */ From 2bbb89db7b0b372d559c62f7b11591cfc42d4ce9 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sat, 14 Jun 2025 21:37:59 +0100 Subject: [PATCH 460/555] [TASK] Add tests for `RuleSet::getRules` with `$searchPattern` (#1278) Part of #974. --- tests/Unit/RuleSet/RuleSetTest.php | 157 +++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/tests/Unit/RuleSet/RuleSetTest.php b/tests/Unit/RuleSet/RuleSetTest.php index 68ae348e..3f6ff9e8 100644 --- a/tests/Unit/RuleSet/RuleSetTest.php +++ b/tests/Unit/RuleSet/RuleSetTest.php @@ -913,6 +913,163 @@ public function getRulesOrdersRulesWithSameLineNumberByColumnNumber(): void self::assertSame([$first, $second, $third], $result); } + /** + * @return array, 1: string, 2: list}> + */ + public static function providePropertyNamesAndSearchPatternAndMatchingPropertyNames(): array + { + return [ + 'single rule matched' => [ + ['color'], + 'color', + ['color'], + ], + 'first rule matched' => [ + ['color', 'display'], + 'color', + ['color'], + ], + 'last rule matched' => [ + ['color', 'display'], + 'display', + ['display'], + ], + 'middle rule matched' => [ + ['color', 'display', 'width'], + 'display', + ['display'], + ], + 'multiple rules for the same property matched' => [ + ['color', 'color'], + 'color', + ['color'], + ], + 'multiple rules for the same property matched in haystack' => [ + ['color', 'display', 'color', 'width'], + 'color', + ['color'], + ], + 'no match in empty list' => [ + [], + 'color', + [], + ], + 'no match for property not in list' => [ + ['color', 'display'], + 'width', + [], + ], + 'shorthand rule matched' => [ + ['font'], + 'font-', + ['font'], + ], + 'longhand rule matched' => [ + ['font-size'], + 'font-', + ['font-size'], + ], + 'shorthand and longhand rule matched' => [ + ['font', 'font-size'], + 'font-', + ['font', 'font-size'], + ], + 'shorthand rule matched in haystack' => [ + ['font', 'color'], + 'font-', + ['font'], + ], + 'longhand rule matched in haystack' => [ + ['font-size', 'color'], + 'font-', + ['font-size'], + ], + 'rules whose property names begin with the same characters not matched with pattern match' => [ + ['contain', 'container', 'container-type'], + 'contain-', + ['contain'], + ], + 'rules whose property names begin with the same characters not matched with exact match' => [ + ['contain', 'container', 'container-type'], + 'contain', + ['contain'], + ], + ]; + } + + /** + * @test + * + * @param list $propertyNamesToSet + * @param list $matchingPropertyNames + * + * @dataProvider providePropertyNamesAndSearchPatternAndMatchingPropertyNames + */ + public function getRulesWithPatternReturnsAllMatchingRules( + array $propertyNamesToSet, + string $searchPattern, + array $matchingPropertyNames + ): void { + $rulesToSet = self::createRulesFromPropertyNames($propertyNamesToSet); + $matchingRules = \array_filter( + $rulesToSet, + static function (Rule $rule) use ($matchingPropertyNames): bool { + return \in_array($rule->getRule(), $matchingPropertyNames, true); + } + ); + $this->subject->setRules($rulesToSet); + + $result = $this->subject->getRules($searchPattern); + + if ($matchingRules === []) { + self::assertSame([], $result); + } + foreach ($matchingRules as $expectedMatchingRule) { + self::assertContains($expectedMatchingRule, $result); + } + } + + /** + * @test + * + * @param list $propertyNamesToSet + * @param list $matchingPropertyNames + * + * @dataProvider providePropertyNamesAndSearchPatternAndMatchingPropertyNames + */ + public function getRulesWithPatternFiltersNonMatchingRules( + array $propertyNamesToSet, + string $searchPattern, + array $matchingPropertyNames + ): void { + $this->setRulesFromPropertyNames($propertyNamesToSet); + + $result = $this->subject->getRules($searchPattern); + + if ($result === []) { + self::expectNotToPerformAssertions(); + } + foreach ($result as $resultRule) { + // 'expected' and 'actual' are transposed here due to necessity + self::assertContains($resultRule->getRule(), $matchingPropertyNames); + } + } + + /** + * @test + */ + public function getRulesWithPatternOrdersRulesByPosition(): void + { + $first = (new Rule('color'))->setPosition(1, 42); + $second = (new Rule('color'))->setPosition(1, 64); + $third = (new Rule('color'))->setPosition(55, 7); + $this->subject->setRules([$third, $second, $first]); + + $result = $this->subject->getRules('color'); + + self::assertSame([$first, $second, $third], $result); + } + /** * @param list $propertyNames */ From 4f1fcf3b274f9b5e092cfcaa31f6b7f353f1e404 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Thu, 19 Jun 2025 11:15:05 +0100 Subject: [PATCH 461/555] [TASK] Add unit tests for `RuleSet::getRulesAssoc` (#1279) Part of #974. --- tests/Unit/RuleSet/RuleSetTest.php | 72 ++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/tests/Unit/RuleSet/RuleSetTest.php b/tests/Unit/RuleSet/RuleSetTest.php index 3f6ff9e8..a4d284e0 100644 --- a/tests/Unit/RuleSet/RuleSetTest.php +++ b/tests/Unit/RuleSet/RuleSetTest.php @@ -1070,6 +1070,78 @@ public function getRulesWithPatternOrdersRulesByPosition(): void self::assertSame([$first, $second, $third], $result); } + /** + * @return array}> + */ + public static function provideDistinctPropertyNames(): array + { + return [ + 'no properties' => [[]], + 'one property' => [['color']], + 'two properties' => [['color', 'display']], + ]; + } + + /** + * @test + * + * @param list $propertyNamesToSet + * + * @dataProvider provideDistinctPropertyNames + */ + public function getRulesAssocReturnsAllRulesWithDistinctPropertyNames(array $propertyNamesToSet): void + { + $rulesToSet = self::createRulesFromPropertyNames($propertyNamesToSet); + $this->subject->setRules($rulesToSet); + + $result = $this->subject->getRulesAssoc(); + + self::assertSame($rulesToSet, \array_values($result)); + } + + /** + * @test + */ + public function getRulesAssocReturnsLastRuleWithSamePropertyName(): void + { + $firstRule = new Rule('color'); + $lastRule = new Rule('color'); + $this->subject->setRules([$firstRule, $lastRule]); + + $result = $this->subject->getRulesAssoc(); + + self::assertSame([$lastRule], \array_values($result)); + } + + /** + * @test + */ + public function getRulesAssocOrdersRulesByPosition(): void + { + $first = (new Rule('color'))->setPosition(1, 42); + $second = (new Rule('display'))->setPosition(1, 64); + $third = (new Rule('width'))->setPosition(55, 7); + $this->subject->setRules([$third, $second, $first]); + + $result = $this->subject->getRulesAssoc(); + + self::assertSame([$first, $second, $third], \array_values($result)); + } + + /** + * @test + */ + public function getRulesAssocKeysRulesByPropertyName(): void + { + $this->subject->setRules([new Rule('color'), new Rule('display')]); + + $result = $this->subject->getRulesAssoc(); + + foreach ($result as $key => $rule) { + self::assertSame($rule->getRule(), $key); + } + } + /** * @param list $propertyNames */ From 6a0c56d641e6fdb6b192a34a2512431be658c4e4 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Thu, 19 Jun 2025 18:39:21 +0100 Subject: [PATCH 462/555] [CLEANUP] Split data provider for search pattern (#1281) A separate data provider now provides patterns which don't match any property names, and a separate test caters for the non-matching situation. --- tests/Unit/RuleSet/RuleSetTest.php | 55 +++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/tests/Unit/RuleSet/RuleSetTest.php b/tests/Unit/RuleSet/RuleSetTest.php index a4d284e0..53123466 100644 --- a/tests/Unit/RuleSet/RuleSetTest.php +++ b/tests/Unit/RuleSet/RuleSetTest.php @@ -949,16 +949,6 @@ public static function providePropertyNamesAndSearchPatternAndMatchingPropertyNa 'color', ['color'], ], - 'no match in empty list' => [ - [], - 'color', - [], - ], - 'no match for property not in list' => [ - ['color', 'display'], - 'width', - [], - ], 'shorthand rule matched' => [ ['font'], 'font-', @@ -1021,9 +1011,6 @@ static function (Rule $rule) use ($matchingPropertyNames): bool { $result = $this->subject->getRules($searchPattern); - if ($matchingRules === []) { - self::assertSame([], $result); - } foreach ($matchingRules as $expectedMatchingRule) { self::assertContains($expectedMatchingRule, $result); } @@ -1046,15 +1033,51 @@ public function getRulesWithPatternFiltersNonMatchingRules( $result = $this->subject->getRules($searchPattern); - if ($result === []) { - self::expectNotToPerformAssertions(); - } foreach ($result as $resultRule) { // 'expected' and 'actual' are transposed here due to necessity self::assertContains($resultRule->getRule(), $matchingPropertyNames); } } + /** + * @return array, 1: string}> + */ + public static function providePropertyNamesAndNonMatchingSearchPattern(): array + { + return [ + 'no match in empty list' => [ + [], + 'color', + ], + 'no match for different property' => [ + ['color'], + 'display', + ], + 'no match for property not in list' => [ + ['color', 'display'], + 'width', + ], + ]; + } + + /** + * @test + * + * @param list $propertyNamesToSet + * + * @dataProvider providePropertyNamesAndNonMatchingSearchPattern + */ + public function getRulesWithNonMatchingPatternReturnsEmptyArray( + array $propertyNamesToSet, + string $searchPattern + ): void { + $this->setRulesFromPropertyNames($propertyNamesToSet); + + $result = $this->subject->getRules($searchPattern); + + self::assertSame([], $result); + } + /** * @test */ From b961840e0712f2a069c29248c4dbb1216e9929c3 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sat, 21 Jun 2025 09:32:04 +0100 Subject: [PATCH 463/555] [TASK] Add tests for `RuleSet::getRulesAssoc` with `$searchPattern` (#1280) Part of #974. --- tests/Unit/RuleSet/RuleSetTest.php | 56 ++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/tests/Unit/RuleSet/RuleSetTest.php b/tests/Unit/RuleSet/RuleSetTest.php index 53123466..8127efdb 100644 --- a/tests/Unit/RuleSet/RuleSetTest.php +++ b/tests/Unit/RuleSet/RuleSetTest.php @@ -1165,6 +1165,62 @@ public function getRulesAssocKeysRulesByPropertyName(): void } } + /** + * @test + * + * @param list $propertyNamesToSet + * @param list $matchingPropertyNames + * + * @dataProvider providePropertyNamesAndSearchPatternAndMatchingPropertyNames + */ + public function getRulesAssocWithPatternReturnsAllMatchingPropertyNames( + array $propertyNamesToSet, + string $searchPattern, + array $matchingPropertyNames + ): void { + $this->setRulesFromPropertyNames($propertyNamesToSet); + + $result = $this->subject->getRulesAssoc($searchPattern); + + $resultPropertyNames = \array_keys($result); + \sort($matchingPropertyNames); + \sort($resultPropertyNames); + self::assertSame($matchingPropertyNames, $resultPropertyNames); + } + + /** + * @test + * + * @param list $propertyNamesToSet + * + * @dataProvider providePropertyNamesAndNonMatchingSearchPattern + */ + public function getRulesAssocWithNonMatchingPatternReturnsEmptyArray( + array $propertyNamesToSet, + string $searchPattern + ): void { + $this->setRulesFromPropertyNames($propertyNamesToSet); + + $result = $this->subject->getRulesAssoc($searchPattern); + + self::assertSame([], $result); + } + + /** + * @test + */ + public function getRulesAssocWithPatternOrdersRulesByPosition(): void + { + $first = (new Rule('font'))->setPosition(1, 42); + $second = (new Rule('font-family'))->setPosition(1, 64); + $third = (new Rule('font-weight'))->setPosition(55, 7); + $this->subject->setRules([$third, $second, $first]); + + $result = $this->subject->getRules('font-'); + + self::assertSame([$first, $second, $third], \array_values($result)); + } + /** * @param list $propertyNames */ From b0ce7af8987990e77c5a22511d821d953a6722b8 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Mon, 23 Jun 2025 21:00:44 +0100 Subject: [PATCH 464/555] [CLEANUP] Streamline tests for `getRules` with matching pattern (#1282) Combine two tests into one, by asserting an exact set match, instead of two-way subset matches. --- tests/Unit/RuleSet/RuleSetTest.php | 41 ++++++++---------------------- 1 file changed, 10 insertions(+), 31 deletions(-) diff --git a/tests/Unit/RuleSet/RuleSetTest.php b/tests/Unit/RuleSet/RuleSetTest.php index 8127efdb..bf763a78 100644 --- a/tests/Unit/RuleSet/RuleSetTest.php +++ b/tests/Unit/RuleSet/RuleSetTest.php @@ -1001,42 +1001,21 @@ public function getRulesWithPatternReturnsAllMatchingRules( array $matchingPropertyNames ): void { $rulesToSet = self::createRulesFromPropertyNames($propertyNamesToSet); - $matchingRules = \array_filter( - $rulesToSet, - static function (Rule $rule) use ($matchingPropertyNames): bool { - return \in_array($rule->getRule(), $matchingPropertyNames, true); - } + // Use `array_values` to ensure canonical numeric array, since `array_filter` does not reindex. + $matchingRules = \array_values( + \array_filter( + $rulesToSet, + static function (Rule $rule) use ($matchingPropertyNames): bool { + return \in_array($rule->getRule(), $matchingPropertyNames, true); + } + ) ); $this->subject->setRules($rulesToSet); $result = $this->subject->getRules($searchPattern); - foreach ($matchingRules as $expectedMatchingRule) { - self::assertContains($expectedMatchingRule, $result); - } - } - - /** - * @test - * - * @param list $propertyNamesToSet - * @param list $matchingPropertyNames - * - * @dataProvider providePropertyNamesAndSearchPatternAndMatchingPropertyNames - */ - public function getRulesWithPatternFiltersNonMatchingRules( - array $propertyNamesToSet, - string $searchPattern, - array $matchingPropertyNames - ): void { - $this->setRulesFromPropertyNames($propertyNamesToSet); - - $result = $this->subject->getRules($searchPattern); - - foreach ($result as $resultRule) { - // 'expected' and 'actual' are transposed here due to necessity - self::assertContains($resultRule->getRule(), $matchingPropertyNames); - } + // `Rule`s without pre-set positions are returned in the order set. This is tested separately. + self::assertSame($matchingRules, $result); } /** From 43a3735bae9cbe6bf9056ea913f844efd089552c Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Tue, 24 Jun 2025 11:34:26 +0100 Subject: [PATCH 465/555] [TASK] Update `RuleSet::addRule` to use `getLineNumber` (#1284) Part of #974 --- src/RuleSet/RuleSet.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 8e0e8ae5..203e49fd 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -94,6 +94,11 @@ public static function parseRuleSet(ParserState $parserState, RuleSet $ruleSet): $parserState->consume('}'); } + /** + * @throws \UnexpectedValueException + * if the last `Rule` is needed as a basis for setting position, but does not have a valid position, + * which should never happen + */ public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void { $propertyName = $ruleToAdd->getRule(); @@ -147,7 +152,14 @@ public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void $rulesCount = \count($rules); if ($rulesCount > 0) { $last = $rules[$rulesCount - 1]; - $ruleToAdd->setPosition($last->getLineNo() + 1, $columnNumber); + $lastsLineNumber = $last->getLineNumber(); + if (!\is_int($lastsLineNumber)) { + throw new \UnexpectedValueException( + 'A Rule without a line number was found during addRule', + 1750718399 + ); + } + $ruleToAdd->setPosition($lastsLineNumber + 1, $columnNumber); } else { $ruleToAdd->setPosition(1, $columnNumber); } From 4086767756ae174d22743e86a7de56ac011029d5 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Tue, 24 Jun 2025 11:35:57 +0100 Subject: [PATCH 466/555] [TASK] Update `RuleSet::comparePositionable` to use new methods (#1283) `getLineNo` and `getColNo` are deprecated. When the titled method was extracted, use of the above-mentioned methods was retained to ease backporting and transition to their replacement counterparts: `getLineNumber` and `getColumnNumber`, which differ by returning `null` in the case of 'not set'. This replaces all instances of calls to `getColNo`. Part of #974 --- src/RuleSet/RuleSet.php | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 203e49fd..2a9e2846 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -326,13 +326,33 @@ protected function renderRules(OutputFormat $outputFormat): string /** * @return int negative if `$first` is before `$second`; zero if they have the same position; positive otherwise + * + * @throws \UnexpectedValueException if either argument does not have a valid position, which should never happen */ private static function comparePositionable(Positionable $first, Positionable $second): int { - if ($first->getLineNo() === $second->getLineNo()) { - return $first->getColNo() - $second->getColNo(); + $firstsLineNumber = $first->getLineNumber(); + $secondsLineNumber = $second->getLineNumber(); + if (!\is_int($firstsLineNumber) || !\is_int($secondsLineNumber)) { + throw new \UnexpectedValueException( + 'A Rule without a line number was passed to comparePositionable', + 1750637683 + ); } - return $first->getLineNo() - $second->getLineNo(); + + if ($firstsLineNumber === $secondsLineNumber) { + $firstsColumnNumber = $first->getColumnNumber(); + $secondsColumnNumber = $second->getColumnNumber(); + if (!\is_int($firstsColumnNumber) || !\is_int($secondsColumnNumber)) { + throw new \UnexpectedValueException( + 'A Rule without a column number was passed to comparePositionable', + 1750637761 + ); + } + return $firstsColumnNumber - $secondsColumnNumber; + } + + return $firstsLineNumber - $secondsLineNumber; } private function hasRule(Rule $rule): bool From 2353f57e14a4e97b5004b02a48721841e32d9e09 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Tue, 24 Jun 2025 21:07:41 +0100 Subject: [PATCH 467/555] [TASK] Add tests for `getLineNumber` (#1286) These correspond to the existing tests for `getLineNo` for classes that implement `Positionable`. Also correct an existing test method name to refer to `getLineNo`. --- tests/Unit/CSSList/AtRuleBlockListTest.php | 21 +++++++++++++++++ tests/Unit/CSSList/CSSListTest.php | 21 +++++++++++++++++ tests/Unit/CSSList/KeyFrameTest.php | 21 +++++++++++++++++ tests/Unit/Comment/CommentTest.php | 21 +++++++++++++++++ tests/Unit/Parsing/OutputExceptionTest.php | 21 +++++++++++++++++ tests/Unit/Parsing/SourceExceptionTest.php | 21 +++++++++++++++++ .../Parsing/UnexpectedEOFExceptionTest.php | 21 +++++++++++++++++ .../Parsing/UnexpectedTokenExceptionTest.php | 21 +++++++++++++++++ tests/Unit/Value/CSSStringTest.php | 21 +++++++++++++++++ tests/Unit/Value/CalcRuleValueListTest.php | 23 ++++++++++++++++++- tests/Unit/Value/URLTest.php | 21 +++++++++++++++++ 11 files changed, 232 insertions(+), 1 deletion(-) diff --git a/tests/Unit/CSSList/AtRuleBlockListTest.php b/tests/Unit/CSSList/AtRuleBlockListTest.php index 0252f7d3..9c777bed 100644 --- a/tests/Unit/CSSList/AtRuleBlockListTest.php +++ b/tests/Unit/CSSList/AtRuleBlockListTest.php @@ -126,6 +126,27 @@ public function getLineNoReturnsLineNumberProvidedToConstructor(): void self::assertSame($lineNumber, $subject->getLineNo()); } + /** + * @test + */ + public function getLineNumberByDefaultReturnsNull(): void + { + $subject = new AtRuleBlockList(''); + + self::assertNull($subject->getLineNumber()); + } + + /** + * @test + */ + public function getLineNumberReturnsLineNumberProvidedToConstructor(): void + { + $lineNumber = 42; + $subject = new AtRuleBlockList('', '', $lineNumber); + + self::assertSame($lineNumber, $subject->getLineNumber()); + } + /** * @test */ diff --git a/tests/Unit/CSSList/CSSListTest.php b/tests/Unit/CSSList/CSSListTest.php index 03539533..ef30c9a3 100644 --- a/tests/Unit/CSSList/CSSListTest.php +++ b/tests/Unit/CSSList/CSSListTest.php @@ -79,6 +79,27 @@ public function getLineNoReturnsLineNumberProvidedToConstructor(): void self::assertSame($lineNumber, $subject->getLineNo()); } + /** + * @test + */ + public function getLineNumberByDefaultReturnsNull(): void + { + $subject = new ConcreteCSSList(); + + self::assertNull($subject->getLineNumber()); + } + + /** + * @test + */ + public function getLineNumberReturnsLineNumberProvidedToConstructor(): void + { + $lineNumber = 42; + $subject = new ConcreteCSSList($lineNumber); + + self::assertSame($lineNumber, $subject->getLineNumber()); + } + /** * @test */ diff --git a/tests/Unit/CSSList/KeyFrameTest.php b/tests/Unit/CSSList/KeyFrameTest.php index a3a0d43a..15c92795 100644 --- a/tests/Unit/CSSList/KeyFrameTest.php +++ b/tests/Unit/CSSList/KeyFrameTest.php @@ -80,6 +80,27 @@ public function getLineNoReturnsLineNumberProvidedToConstructor(): void self::assertSame($lineNumber, $subject->getLineNo()); } + /** + * @test + */ + public function getLineNumberByDefaultReturnsNull(): void + { + $subject = new KeyFrame(); + + self::assertNull($subject->getLineNumber()); + } + + /** + * @test + */ + public function getLineNumberReturnsLineNumberProvidedToConstructor(): void + { + $lineNumber = 42; + $subject = new KeyFrame($lineNumber); + + self::assertSame($lineNumber, $subject->getLineNumber()); + } + /** * @test */ diff --git a/tests/Unit/Comment/CommentTest.php b/tests/Unit/Comment/CommentTest.php index 2bfe670c..0db0349a 100644 --- a/tests/Unit/Comment/CommentTest.php +++ b/tests/Unit/Comment/CommentTest.php @@ -77,4 +77,25 @@ public function getLineNoInitiallyReturnsLineNumberPassedToConstructor(): void self::assertSame($lineNumber, $subject->getLineNo()); } + + /** + * @test + */ + public function getLineNumberByDefaultReturnsNull(): void + { + $subject = new Comment(); + + self::assertNull($subject->getLineNumber()); + } + + /** + * @test + */ + public function getLineNumberReturnsLineNumberProvidedToConstructor(): void + { + $lineNumber = 42; + $subject = new Comment('', $lineNumber); + + self::assertSame($lineNumber, $subject->getLineNumber()); + } } diff --git a/tests/Unit/Parsing/OutputExceptionTest.php b/tests/Unit/Parsing/OutputExceptionTest.php index d3409aa4..768982d0 100644 --- a/tests/Unit/Parsing/OutputExceptionTest.php +++ b/tests/Unit/Parsing/OutputExceptionTest.php @@ -53,6 +53,27 @@ public function getLineNoReturnsLineNumberProvidedToConstructor(): void self::assertSame($lineNumber, $exception->getLineNo()); } + /** + * @test + */ + public function getLineNumberByDefaultReturnsNull(): void + { + $subject = new OutputException('foo'); + + self::assertNull($subject->getLineNumber()); + } + + /** + * @test + */ + public function getLineNumberReturnsLineNumberProvidedToConstructor(): void + { + $lineNumber = 42; + $subject = new OutputException('foo', $lineNumber); + + self::assertSame($lineNumber, $subject->getLineNumber()); + } + /** * @test */ diff --git a/tests/Unit/Parsing/SourceExceptionTest.php b/tests/Unit/Parsing/SourceExceptionTest.php index b497ff52..ea35f67d 100644 --- a/tests/Unit/Parsing/SourceExceptionTest.php +++ b/tests/Unit/Parsing/SourceExceptionTest.php @@ -44,6 +44,27 @@ public function getLineNoReturnsLineNumberProvidedToConstructor(): void self::assertSame($lineNumber, $exception->getLineNo()); } + /** + * @test + */ + public function getLineNumberByDefaultReturnsNull(): void + { + $subject = new SourceException('foo'); + + self::assertNull($subject->getLineNumber()); + } + + /** + * @test + */ + public function getLineNumberReturnsLineNumberProvidedToConstructor(): void + { + $lineNumber = 42; + $subject = new SourceException('foo', $lineNumber); + + self::assertSame($lineNumber, $subject->getLineNumber()); + } + /** * @test */ diff --git a/tests/Unit/Parsing/UnexpectedEOFExceptionTest.php b/tests/Unit/Parsing/UnexpectedEOFExceptionTest.php index 929609ef..4c904070 100644 --- a/tests/Unit/Parsing/UnexpectedEOFExceptionTest.php +++ b/tests/Unit/Parsing/UnexpectedEOFExceptionTest.php @@ -42,6 +42,27 @@ public function getLineNoReturnsLineNumberProvidedToConstructor(): void self::assertSame($lineNumber, $exception->getLineNo()); } + /** + * @test + */ + public function getLineNumberByDefaultReturnsNull(): void + { + $subject = new UnexpectedEOFException('expected', 'found'); + + self::assertNull($subject->getLineNumber()); + } + + /** + * @test + */ + public function getLineNumberReturnsLineNumberProvidedToConstructor(): void + { + $lineNumber = 42; + $subject = new UnexpectedEOFException('expected', 'found', 'literal', $lineNumber); + + self::assertSame($lineNumber, $subject->getLineNumber()); + } + /** * @test */ diff --git a/tests/Unit/Parsing/UnexpectedTokenExceptionTest.php b/tests/Unit/Parsing/UnexpectedTokenExceptionTest.php index e5c7a64d..6bd2a8d7 100644 --- a/tests/Unit/Parsing/UnexpectedTokenExceptionTest.php +++ b/tests/Unit/Parsing/UnexpectedTokenExceptionTest.php @@ -42,6 +42,27 @@ public function getLineNoReturnsLineNumberProvidedToConstructor(): void self::assertSame($lineNumber, $exception->getLineNo()); } + /** + * @test + */ + public function getLineNumberByDefaultReturnsNull(): void + { + $subject = new UnexpectedTokenException('expected', 'found'); + + self::assertNull($subject->getLineNumber()); + } + + /** + * @test + */ + public function getLineNumberReturnsLineNumberProvidedToConstructor(): void + { + $lineNumber = 42; + $subject = new UnexpectedTokenException('expected', 'found', 'literal', $lineNumber); + + self::assertSame($lineNumber, $subject->getLineNumber()); + } + /** * @test */ diff --git a/tests/Unit/Value/CSSStringTest.php b/tests/Unit/Value/CSSStringTest.php index e88f3554..ca0bf95a 100644 --- a/tests/Unit/Value/CSSStringTest.php +++ b/tests/Unit/Value/CSSStringTest.php @@ -81,4 +81,25 @@ public function getLineNoReturnsLineNumberProvidedToConstructor(): void self::assertSame($lineNumber, $subject->getLineNo()); } + + /** + * @test + */ + public function getLineNumberByDefaultReturnsNull(): void + { + $subject = new CSSString(''); + + self::assertNull($subject->getLineNumber()); + } + + /** + * @test + */ + public function getLineNumberReturnsLineNumberProvidedToConstructor(): void + { + $lineNumber = 42; + $subject = new CSSString('', $lineNumber); + + self::assertSame($lineNumber, $subject->getLineNumber()); + } } diff --git a/tests/Unit/Value/CalcRuleValueListTest.php b/tests/Unit/Value/CalcRuleValueListTest.php index 5d73d9e9..f891d0f5 100644 --- a/tests/Unit/Value/CalcRuleValueListTest.php +++ b/tests/Unit/Value/CalcRuleValueListTest.php @@ -29,7 +29,7 @@ public function isRuleValueList(): void /** * @test */ - public function getLineNumberByDefaultReturnsZero(): void + public function getLineNoByDefaultReturnsZero(): void { $subject = new CalcRuleValueList(); @@ -48,6 +48,27 @@ public function getLineNoReturnsLineNumberProvidedToConstructor(): void self::assertSame($lineNumber, $subject->getLineNo()); } + /** + * @test + */ + public function getLineNumberByDefaultReturnsNull(): void + { + $subject = new CalcRuleValueList(); + + self::assertNull($subject->getLineNumber()); + } + + /** + * @test + */ + public function getLineNumberReturnsLineNumberProvidedToConstructor(): void + { + $lineNumber = 42; + $subject = new CalcRuleValueList($lineNumber); + + self::assertSame($lineNumber, $subject->getLineNumber()); + } + /** * @test */ diff --git a/tests/Unit/Value/URLTest.php b/tests/Unit/Value/URLTest.php index 42d96e29..cb29ee54 100644 --- a/tests/Unit/Value/URLTest.php +++ b/tests/Unit/Value/URLTest.php @@ -81,4 +81,25 @@ public function getLineNoReturnsLineNumberProvidedToConstructor(): void self::assertSame($lineNumber, $subject->getLineNo()); } + + /** + * @test + */ + public function getLineNumberByDefaultReturnsNull(): void + { + $subject = new URL(new CSSString('http://example.com')); + + self::assertNull($subject->getLineNumber()); + } + + /** + * @test + */ + public function getLineNumberReturnsLineNumberProvidedToConstructor(): void + { + $lineNumber = 42; + $subject = new URL(new CSSString('http://example.com'), $lineNumber); + + self::assertSame($lineNumber, $subject->getLineNumber()); + } } From 1dec64160b81b4c375a23aa4a19a4374217723a3 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Tue, 24 Jun 2025 22:10:17 +0100 Subject: [PATCH 468/555] [TASK] Use `getLineNumber` in `ParserTest` (#1285) `getLineNo` is deprecated and will be removed. Part of #974 --- tests/ParserTest.php | 48 ++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/ParserTest.php b/tests/ParserTest.php index 656d943b..da991ee7 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -105,50 +105,50 @@ public function colorParsing(): void $colorRuleValue = $colorRules[0]->getValue(); self::assertInstanceOf(Color::class, $colorRuleValue); self::assertEquals([ - 'r' => new Size(35.0, null, true, $colorRuleValue->getLineNo()), - 'g' => new Size(35.0, null, true, $colorRuleValue->getLineNo()), - 'b' => new Size(35.0, null, true, $colorRuleValue->getLineNo()), + 'r' => new Size(35.0, null, true, $colorRuleValue->getLineNumber()), + 'g' => new Size(35.0, null, true, $colorRuleValue->getLineNumber()), + 'b' => new Size(35.0, null, true, $colorRuleValue->getLineNumber()), ], $colorRuleValue->getColor()); $colorRules = $ruleSet->getRules('border-color'); $colorRuleValue = $colorRules[0]->getValue(); self::assertInstanceOf(Color::class, $colorRuleValue); self::assertEquals([ - 'r' => new Size(10.0, null, true, $colorRuleValue->getLineNo()), - 'g' => new Size(100.0, null, true, $colorRuleValue->getLineNo()), - 'b' => new Size(230.0, null, true, $colorRuleValue->getLineNo()), + 'r' => new Size(10.0, null, true, $colorRuleValue->getLineNumber()), + 'g' => new Size(100.0, null, true, $colorRuleValue->getLineNumber()), + 'b' => new Size(230.0, null, true, $colorRuleValue->getLineNumber()), ], $colorRuleValue->getColor()); $colorRuleValue = $colorRules[1]->getValue(); self::assertInstanceOf(Color::class, $colorRuleValue); self::assertEquals([ - 'r' => new Size(10.0, null, true, $colorRuleValue->getLineNo()), - 'g' => new Size(100.0, null, true, $colorRuleValue->getLineNo()), - 'b' => new Size(231.0, null, true, $colorRuleValue->getLineNo()), - 'a' => new Size('0000.3', null, true, $colorRuleValue->getLineNo()), + 'r' => new Size(10.0, null, true, $colorRuleValue->getLineNumber()), + 'g' => new Size(100.0, null, true, $colorRuleValue->getLineNumber()), + 'b' => new Size(231.0, null, true, $colorRuleValue->getLineNumber()), + 'a' => new Size('0000.3', null, true, $colorRuleValue->getLineNumber()), ], $colorRuleValue->getColor()); $colorRules = $ruleSet->getRules('outline-color'); $colorRuleValue = $colorRules[0]->getValue(); self::assertInstanceOf(Color::class, $colorRuleValue); self::assertEquals([ - 'r' => new Size(34.0, null, true, $colorRuleValue->getLineNo()), - 'g' => new Size(34.0, null, true, $colorRuleValue->getLineNo()), - 'b' => new Size(34.0, null, true, $colorRuleValue->getLineNo()), + 'r' => new Size(34.0, null, true, $colorRuleValue->getLineNumber()), + 'g' => new Size(34.0, null, true, $colorRuleValue->getLineNumber()), + 'b' => new Size(34.0, null, true, $colorRuleValue->getLineNumber()), ], $colorRuleValue->getColor()); } elseif ($selector === '#yours') { $colorRules = $ruleSet->getRules('background-color'); $colorRuleValue = $colorRules[0]->getValue(); self::assertInstanceOf(Color::class, $colorRuleValue); self::assertEquals([ - 'h' => new Size(220.0, null, true, $colorRuleValue->getLineNo()), - 's' => new Size(10.0, '%', true, $colorRuleValue->getLineNo()), - 'l' => new Size(220.0, '%', true, $colorRuleValue->getLineNo()), + 'h' => new Size(220.0, null, true, $colorRuleValue->getLineNumber()), + 's' => new Size(10.0, '%', true, $colorRuleValue->getLineNumber()), + 'l' => new Size(220.0, '%', true, $colorRuleValue->getLineNumber()), ], $colorRuleValue->getColor()); $colorRuleValue = $colorRules[1]->getValue(); self::assertInstanceOf(Color::class, $colorRuleValue); self::assertEquals([ - 'h' => new Size(220.0, null, true, $colorRuleValue->getLineNo()), - 's' => new Size(10.0, '%', true, $colorRuleValue->getLineNo()), - 'l' => new Size(220.0, '%', true, $colorRuleValue->getLineNo()), - 'a' => new Size(0000.3, null, true, $colorRuleValue->getLineNo()), + 'h' => new Size(220.0, null, true, $colorRuleValue->getLineNumber()), + 's' => new Size(10.0, '%', true, $colorRuleValue->getLineNumber()), + 'l' => new Size(220.0, '%', true, $colorRuleValue->getLineNumber()), + 'a' => new Size(0000.3, null, true, $colorRuleValue->getLineNumber()), ], $colorRuleValue->getColor()); $colorRules = $ruleSet->getRules('outline-color'); self::assertEmpty($colorRules); @@ -990,7 +990,7 @@ public function lineNumbersParsing(): void $actualLineNumbers = []; foreach ($document->getAllValues() as $value) { if ($value instanceof URL) { - $actualLineNumbers[] = $value->getLineNo(); + $actualLineNumbers[] = $value->getLineNumber(); } } @@ -1003,11 +1003,11 @@ public function lineNumbersParsing(): void // Choose the 2nd one $valueOfSecondRule = $rules[1]->getValue(); self::assertInstanceOf(Color::class, $valueOfSecondRule); - self::assertSame(27, $rules[1]->getLineNo()); + self::assertSame(27, $rules[1]->getLineNumber()); $actualColorLineNumbers = []; foreach ($valueOfSecondRule->getColor() as $size) { - $actualColorLineNumbers[] = $size->getLineNo(); + $actualColorLineNumbers[] = $size->getLineNumber(); } self::assertSame($expectedColorLineNumbers, $actualColorLineNumbers); @@ -1026,7 +1026,7 @@ public function unexpectedTokenExceptionLineNo(): void try { $parser->parse(); } catch (UnexpectedTokenException $e) { - self::assertSame(2, $e->getLineNo()); + self::assertSame(2, $e->getLineNumber()); throw $e; } } From 710dab9ad7e45a27a676e81b1b3fe2332927b73b Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Tue, 24 Jun 2025 23:02:30 +0100 Subject: [PATCH 469/555] [TASK] Remove `getColNo()` (#1287) Note that the removed tests are in `UnitDeprecated`. Equivalent tests already exist for the replacement `getColumnNumber()`. Part of #974 --- CHANGELOG.md | 1 + src/Position/Position.php | 8 ----- src/Position/Positionable.php | 7 ---- .../UnitDeprecated/Position/PositionTest.php | 32 ------------------- 4 files changed, 1 insertion(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f69624e..e317a1e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,6 +80,7 @@ Please also have a look at our ### Removed +- Remove `Rule::getColNo()` (use `getColumnNumber()` instead) (#1287) - Passing a string as the first argument to `getAllValues()` is no longer supported and will not work; the search pattern should now be passed as the second argument (#1243) diff --git a/src/Position/Position.php b/src/Position/Position.php index 0771453b..0729b05e 100644 --- a/src/Position/Position.php +++ b/src/Position/Position.php @@ -47,14 +47,6 @@ public function getColumnNumber(): ?int return $this->columnNumber; } - /** - * @return int<0, max> - */ - public function getColNo(): int - { - return $this->getColumnNumber() ?? 0; - } - /** * @param int<0, max>|null $lineNumber * @param int<0, max>|null $columnNumber diff --git a/src/Position/Positionable.php b/src/Position/Positionable.php index 675fb55f..65ba40fb 100644 --- a/src/Position/Positionable.php +++ b/src/Position/Positionable.php @@ -28,13 +28,6 @@ public function getLineNo(): int; */ public function getColumnNumber(): ?int; - /** - * @return int<0, max> - * - * @deprecated in version 8.9.0, will be removed in v9.0. Use `getColumnNumber()` instead. - */ - public function getColNo(): int; - /** * @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. diff --git a/tests/UnitDeprecated/Position/PositionTest.php b/tests/UnitDeprecated/Position/PositionTest.php index 2e06cc6d..ef77d443 100644 --- a/tests/UnitDeprecated/Position/PositionTest.php +++ b/tests/UnitDeprecated/Position/PositionTest.php @@ -77,38 +77,6 @@ public function getLineNoReturnsZeroAfterLineNumberCleared(): void self::assertSame(0, $this->subject->getLineNo()); } - /** - * @test - */ - public function getColNoInitiallyReturnsZero(): void - { - self::assertSame(0, $this->subject->getColNo()); - } - - /** - * @test - * - * @dataProvider provideColumnNumber - */ - public function getColNoReturnsColumnNumberSet(int $columnNumber): void - { - $this->subject->setPosition(1, $columnNumber); - - self::assertSame($columnNumber, $this->subject->getColNo()); - } - - /** - * @test - */ - public function getColNoReturnsZeroAfterColumnNumberCleared(): void - { - $this->subject->setPosition(1, 99); - - $this->subject->setPosition(2); - - self::assertSame(0, $this->subject->getColNo()); - } - /** * @test */ From f9685bb4406686d7657952aa84e719040d2a1fb3 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Thu, 26 Jun 2025 16:38:13 +0100 Subject: [PATCH 470/555] [TASK] Set line number to `null` by default (#1288) No longer allow or support `0` as a default line or column number. Part of #974 --- CHANGELOG.md | 1 + src/CSSList/AtRuleBlockList.php | 4 ++-- src/CSSList/CSSList.php | 4 ++-- src/Comment/Comment.php | 4 ++-- src/Parsing/SourceException.php | 4 ++-- src/Parsing/UnexpectedTokenException.php | 4 ++-- src/Position/Position.php | 5 ++--- src/Position/Positionable.php | 4 +--- src/Property/CSSNamespace.php | 4 ++-- src/Property/Charset.php | 4 ++-- src/Property/Import.php | 4 ++-- src/Rule/Rule.php | 6 +++--- src/RuleSet/AtRuleSet.php | 4 ++-- src/RuleSet/RuleSet.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/RuleValueList.php | 4 ++-- src/Value/Size.php | 4 ++-- src/Value/URL.php | 4 ++-- src/Value/Value.php | 4 ++-- src/Value/ValueList.php | 4 ++-- tests/UnitDeprecated/Position/PositionTest.php | 12 ------------ 25 files changed, 47 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e317a1e2..ba74cfa5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ Please also have a look at our ### Changed +- The default line (and column) number is now `null` (not zero) (#1288) - `setPosition()` (in `Rule` and other classes) now has fluent interface, returning itself (#1259) - `RuleSet::removeRule()` now only allows `Rule` as the parameter diff --git a/src/CSSList/AtRuleBlockList.php b/src/CSSList/AtRuleBlockList.php index 8ee62ae4..e9487cf2 100644 --- a/src/CSSList/AtRuleBlockList.php +++ b/src/CSSList/AtRuleBlockList.php @@ -24,9 +24,9 @@ class AtRuleBlockList extends CSSBlockList implements AtRule /** * @param non-empty-string $type - * @param int<0, max> $lineNumber + * @param int<1, max>|null $lineNumber */ - public function __construct(string $type, string $arguments = '', int $lineNumber = 0) + public function __construct(string $type, string $arguments = '', ?int $lineNumber = null) { parent::__construct($lineNumber); $this->type = $type; diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 1942d8c9..34246813 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -48,9 +48,9 @@ abstract class CSSList implements CSSElement, CSSListItem, Positionable protected $contents = []; /** - * @param int<0, max> $lineNumber + * @param int<1, max>|null $lineNumber */ - public function __construct(int $lineNumber = 0) + public function __construct(?int $lineNumber = null) { $this->setPosition($lineNumber); } diff --git a/src/Comment/Comment.php b/src/Comment/Comment.php index 7a56624c..33188988 100644 --- a/src/Comment/Comment.php +++ b/src/Comment/Comment.php @@ -21,9 +21,9 @@ class Comment implements Positionable, Renderable protected $commentText; /** - * @param int<0, max> $lineNumber + * @param int<1, max>|null $lineNumber */ - public function __construct(string $commentText = '', int $lineNumber = 0) + public function __construct(string $commentText = '', ?int $lineNumber = null) { $this->commentText = $commentText; $this->setPosition($lineNumber); diff --git a/src/Parsing/SourceException.php b/src/Parsing/SourceException.php index ca07cc48..b96c1878 100644 --- a/src/Parsing/SourceException.php +++ b/src/Parsing/SourceException.php @@ -12,9 +12,9 @@ class SourceException extends \Exception implements Positionable use Position; /** - * @param int<0, max> $lineNumber + * @param int<1, max>|null $lineNumber */ - public function __construct(string $message, int $lineNumber = 0) + public function __construct(string $message, ?int $lineNumber = null) { $this->setPosition($lineNumber); if ($lineNumber !== 0) { diff --git a/src/Parsing/UnexpectedTokenException.php b/src/Parsing/UnexpectedTokenException.php index 8a16794c..e485d822 100644 --- a/src/Parsing/UnexpectedTokenException.php +++ b/src/Parsing/UnexpectedTokenException.php @@ -11,9 +11,9 @@ class UnexpectedTokenException extends SourceException { /** * @param 'literal'|'identifier'|'count'|'expression'|'search'|'custom' $matchType - * @param int<0, max> $lineNumber + * @param int<1, max>|null $lineNumber */ - public function __construct(string $expected, string $found, string $matchType = 'literal', int $lineNumber = 0) + public function __construct(string $expected, string $found, string $matchType = 'literal', ?int $lineNumber = null) { $message = "Token “{$expected}” ({$matchType}) not found. Got “{$found}”."; if ($matchType === 'search') { diff --git a/src/Position/Position.php b/src/Position/Position.php index 0729b05e..12afc5e1 100644 --- a/src/Position/Position.php +++ b/src/Position/Position.php @@ -48,15 +48,14 @@ public function getColumnNumber(): ?int } /** - * @param int<0, max>|null $lineNumber + * @param int<1, max>|null $lineNumber * @param int<0, max>|null $columnNumber * * @return $this fluent interface */ public function setPosition(?int $lineNumber, ?int $columnNumber = null): Positionable { - // The conditional is for backwards compatibility (backcompat); `0` will not be allowed in future. - $this->lineNumber = $lineNumber !== 0 ? $lineNumber : null; + $this->lineNumber = $lineNumber; $this->columnNumber = $columnNumber; return $this; diff --git a/src/Position/Positionable.php b/src/Position/Positionable.php index 65ba40fb..c9f829a9 100644 --- a/src/Position/Positionable.php +++ b/src/Position/Positionable.php @@ -29,9 +29,7 @@ public function getLineNo(): int; public function getColumnNumber(): ?int; /** - * @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<1, max>|null $lineNumber * @param int<0, max>|null $columnNumber * * @return $this fluent interface diff --git a/src/Property/CSSNamespace.php b/src/Property/CSSNamespace.php index e6a2983c..66164a31 100644 --- a/src/Property/CSSNamespace.php +++ b/src/Property/CSSNamespace.php @@ -31,9 +31,9 @@ class CSSNamespace implements AtRule, Positionable /** * @param CSSString|URL $url - * @param int<0, max> $lineNumber + * @param int<1, max>|null $lineNumber */ - public function __construct($url, ?string $prefix = null, int $lineNumber = 0) + public function __construct($url, ?string $prefix = null, ?int $lineNumber = null) { $this->url = $url; $this->prefix = $prefix; diff --git a/src/Property/Charset.php b/src/Property/Charset.php index 90e7d5fb..c9488ad1 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -29,9 +29,9 @@ class Charset implements AtRule, Positionable private $charset; /** - * @param int<0, max> $lineNumber + * @param int<1, max>|null $lineNumber */ - public function __construct(CSSString $charset, int $lineNumber = 0) + public function __construct(CSSString $charset, ?int $lineNumber = null) { $this->charset = $charset; $this->setPosition($lineNumber); diff --git a/src/Property/Import.php b/src/Property/Import.php index 51c0e4ea..a9dabe97 100644 --- a/src/Property/Import.php +++ b/src/Property/Import.php @@ -29,9 +29,9 @@ class Import implements AtRule, Positionable private $mediaQuery; /** - * @param int<0, max> $lineNumber + * @param int<1, max>|null $lineNumber */ - public function __construct(URL $location, ?string $mediaQuery, int $lineNumber = 0) + public function __construct(URL $location, ?string $mediaQuery, ?int $lineNumber = null) { $this->location = $location; $this->mediaQuery = $mediaQuery; diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index 304e6628..2ccfa7c1 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -44,10 +44,10 @@ class Rule implements Commentable, CSSElement, Positionable /** * @param non-empty-string $rule - * @param int<0, max> $lineNumber - * @param int<0, max> $columnNumber + * @param int<1, max>|null $lineNumber + * @param int<0, max>|null $columnNumber */ - public function __construct(string $rule, int $lineNumber = 0, int $columnNumber = 0) + public function __construct(string $rule, ?int $lineNumber = null, ?int $columnNumber = null) { $this->rule = $rule; $this->setPosition($lineNumber, $columnNumber); diff --git a/src/RuleSet/AtRuleSet.php b/src/RuleSet/AtRuleSet.php index 0fda9638..4cd5acc2 100644 --- a/src/RuleSet/AtRuleSet.php +++ b/src/RuleSet/AtRuleSet.php @@ -27,9 +27,9 @@ class AtRuleSet extends RuleSet implements AtRule /** * @param non-empty-string $type - * @param int<0, max> $lineNumber + * @param int<1, max>|null $lineNumber */ - public function __construct(string $type, string $arguments = '', int $lineNumber = 0) + public function __construct(string $type, string $arguments = '', ?int $lineNumber = null) { parent::__construct($lineNumber); $this->type = $type; diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 2a9e2846..9ab5e203 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -41,9 +41,9 @@ abstract class RuleSet implements CSSElement, CSSListItem, Positionable, RuleCon private $rules = []; /** - * @param int<0, max> $lineNumber + * @param int<1, max>|null $lineNumber */ - public function __construct(int $lineNumber = 0) + public function __construct(?int $lineNumber = null) { $this->setPosition($lineNumber); } diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index f78f7cb6..86b56d9b 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -27,9 +27,9 @@ class CSSFunction extends ValueList * @param non-empty-string $name * @param RuleValueList|array $arguments * @param non-empty-string $separator - * @param int<0, max> $lineNumber + * @param int<1, max>|null $lineNumber */ - public function __construct(string $name, $arguments, string $separator = ',', int $lineNumber = 0) + public function __construct(string $name, $arguments, string $separator = ',', ?int $lineNumber = null) { if ($arguments instanceof RuleValueList) { $separator = $arguments->getListSeparator(); diff --git a/src/Value/CSSString.php b/src/Value/CSSString.php index 52b521e6..8c4cf656 100644 --- a/src/Value/CSSString.php +++ b/src/Value/CSSString.php @@ -23,9 +23,9 @@ class CSSString extends PrimitiveValue private $string; /** - * @param int<0, max> $lineNumber + * @param int<1, max>|null $lineNumber */ - public function __construct(string $string, int $lineNumber = 0) + public function __construct(string $string, ?int $lineNumber = null) { $this->string = $string; parent::__construct($lineNumber); diff --git a/src/Value/CalcRuleValueList.php b/src/Value/CalcRuleValueList.php index 3c0f24ce..f904f12c 100644 --- a/src/Value/CalcRuleValueList.php +++ b/src/Value/CalcRuleValueList.php @@ -9,9 +9,9 @@ class CalcRuleValueList extends RuleValueList { /** - * @param int<0, max> $lineNumber + * @param int<1, max>|null $lineNumber */ - public function __construct(int $lineNumber = 0) + public function __construct(?int $lineNumber = null) { parent::__construct(',', $lineNumber); } diff --git a/src/Value/Color.php b/src/Value/Color.php index 028ce856..74e7d1bf 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -17,9 +17,9 @@ class Color extends CSSFunction { /** * @param array $colorValues - * @param int<0, max> $lineNumber + * @param int<1, max>|null $lineNumber */ - public function __construct(array $colorValues, int $lineNumber = 0) + public function __construct(array $colorValues, ?int $lineNumber = null) { parent::__construct(\implode('', \array_keys($colorValues)), $colorValues, ',', $lineNumber); } diff --git a/src/Value/LineName.php b/src/Value/LineName.php index 791f0cc3..763cc48e 100644 --- a/src/Value/LineName.php +++ b/src/Value/LineName.php @@ -13,9 +13,9 @@ class LineName extends ValueList { /** * @param array $components - * @param int<0, max> $lineNumber + * @param int<1, max>|null $lineNumber */ - public function __construct(array $components = [], int $lineNumber = 0) + public function __construct(array $components = [], ?int $lineNumber = null) { parent::__construct($components, ' ', $lineNumber); } diff --git a/src/Value/RuleValueList.php b/src/Value/RuleValueList.php index 9884f5cf..37aa7cd6 100644 --- a/src/Value/RuleValueList.php +++ b/src/Value/RuleValueList.php @@ -13,9 +13,9 @@ class RuleValueList extends ValueList { /** * @param non-empty-string $separator - * @param int<0, max> $lineNumber + * @param int<1, max>|null $lineNumber */ - public function __construct(string $separator = ',', int $lineNumber = 0) + public function __construct(string $separator = ',', ?int $lineNumber = null) { parent::__construct([], $separator, $lineNumber); } diff --git a/src/Value/Size.php b/src/Value/Size.php index a5e15497..3882cea1 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -69,9 +69,9 @@ class Size extends PrimitiveValue /** * @param float|int|string $size - * @param int<0, max> $lineNumber + * @param int<1, max>|null $lineNumber */ - public function __construct($size, ?string $unit = null, bool $isColorComponent = false, int $lineNumber = 0) + public function __construct($size, ?string $unit = null, bool $isColorComponent = false, ?int $lineNumber = null) { parent::__construct($lineNumber); $this->size = (float) $size; diff --git a/src/Value/URL.php b/src/Value/URL.php index 4b4fb4c8..f6e7b974 100644 --- a/src/Value/URL.php +++ b/src/Value/URL.php @@ -21,9 +21,9 @@ class URL extends PrimitiveValue private $url; /** - * @param int<0, max> $lineNumber + * @param int<1, max>|null $lineNumber */ - public function __construct(CSSString $url, int $lineNumber = 0) + public function __construct(CSSString $url, ?int $lineNumber = null) { parent::__construct($lineNumber); $this->url = $url; diff --git a/src/Value/Value.php b/src/Value/Value.php index e33a2949..9f4fe1e6 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -21,9 +21,9 @@ abstract class Value implements CSSElement, Positionable use Position; /** - * @param int<0, max> $lineNumber + * @param int<1, max>|null $lineNumber */ - public function __construct(int $lineNumber = 0) + public function __construct(?int $lineNumber = null) { $this->setPosition($lineNumber); } diff --git a/src/Value/ValueList.php b/src/Value/ValueList.php index 1d26b74d..6f85f895 100644 --- a/src/Value/ValueList.php +++ b/src/Value/ValueList.php @@ -31,9 +31,9 @@ abstract class ValueList extends Value /** * @param array|Value|string $components * @param non-empty-string $separator - * @param int<0, max> $lineNumber + * @param int<1, max>|null $lineNumber */ - public function __construct($components = [], $separator = ',', int $lineNumber = 0) + public function __construct($components = [], $separator = ',', ?int $lineNumber = null) { parent::__construct($lineNumber); if (!\is_array($components)) { diff --git a/tests/UnitDeprecated/Position/PositionTest.php b/tests/UnitDeprecated/Position/PositionTest.php index ef77d443..ec34c1f1 100644 --- a/tests/UnitDeprecated/Position/PositionTest.php +++ b/tests/UnitDeprecated/Position/PositionTest.php @@ -77,18 +77,6 @@ public function getLineNoReturnsZeroAfterLineNumberCleared(): void self::assertSame(0, $this->subject->getLineNo()); } - /** - * @test - */ - public function setPositionWithZeroClearsLineNumber(): void - { - $this->subject->setPosition(99); - - $this->subject->setPosition(0); - - self::assertNull($this->subject->getLineNumber()); - } - /** * @test */ From 6a6e319d3095a36e4e79089c929e68b59a301961 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Thu, 26 Jun 2025 17:31:51 +0100 Subject: [PATCH 471/555] [TASK] Remove `getLineNo()` (#1258) Closes #974. --- CHANGELOG.md | 3 + src/Position/Position.php | 8 -- src/Position/Positionable.php | 7 -- tests/Unit/CSSList/AtRuleBlockListTest.php | 12 --- tests/Unit/CSSList/CSSListTest.php | 22 ----- tests/Unit/CSSList/KeyFrameTest.php | 12 --- tests/Unit/Comment/CommentTest.php | 21 ----- tests/Unit/Parsing/OutputExceptionTest.php | 21 ----- tests/Unit/Parsing/SourceExceptionTest.php | 21 ----- .../Parsing/UnexpectedEOFExceptionTest.php | 21 ----- .../Parsing/UnexpectedTokenExceptionTest.php | 21 ----- tests/Unit/Value/CSSStringTest.php | 22 ----- tests/Unit/Value/CalcRuleValueListTest.php | 22 ----- tests/Unit/Value/URLTest.php | 22 ----- tests/UnitDeprecated/.gitkeep | 0 .../UnitDeprecated/Position/PositionTest.php | 91 ------------------- 16 files changed, 3 insertions(+), 323 deletions(-) create mode 100644 tests/UnitDeprecated/.gitkeep delete mode 100644 tests/UnitDeprecated/Position/PositionTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index ba74cfa5..74a577d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -81,6 +81,9 @@ Please also have a look at our ### Removed +- Remove `getLineNo()` from these classes (use `getLineNumber()` instead): + `Comment`, `CSSList`, `SourceException`, `Charset`, `CSSNamespace`, `Import`, + `Rule`, `DeclarationBlock`, `RuleSet`, `CSSFunction`, `Value` (#1258) - Remove `Rule::getColNo()` (use `getColumnNumber()` instead) (#1287) - Passing a string as the first argument to `getAllValues()` is no longer supported and will not work; diff --git a/src/Position/Position.php b/src/Position/Position.php index 12afc5e1..7b75f8ea 100644 --- a/src/Position/Position.php +++ b/src/Position/Position.php @@ -31,14 +31,6 @@ public function getLineNumber(): ?int return $this->lineNumber; } - /** - * @return int<0, max> - */ - public function getLineNo(): int - { - return $this->getLineNumber() ?? 0; - } - /** * @return int<0, max>|null */ diff --git a/src/Position/Positionable.php b/src/Position/Positionable.php index c9f829a9..d23d26a3 100644 --- a/src/Position/Positionable.php +++ b/src/Position/Positionable.php @@ -16,13 +16,6 @@ interface Positionable */ public function getLineNumber(): ?int; - /** - * @return int<0, max> - * - * @deprecated in version 8.9.0, will be removed in v9.0. Use `getLineNumber()` instead. - */ - public function getLineNo(): int; - /** * @return int<0, max>|null */ diff --git a/tests/Unit/CSSList/AtRuleBlockListTest.php b/tests/Unit/CSSList/AtRuleBlockListTest.php index 9c777bed..3b61e437 100644 --- a/tests/Unit/CSSList/AtRuleBlockListTest.php +++ b/tests/Unit/CSSList/AtRuleBlockListTest.php @@ -114,18 +114,6 @@ public function atRuleArgsReturnsArgumentsProvidedToConstructor(): void self::assertSame($arguments, $subject->atRuleArgs()); } - /** - * @test - */ - public function getLineNoReturnsLineNumberProvidedToConstructor(): void - { - $lineNumber = 42; - - $subject = new AtRuleBlockList('', '', $lineNumber); - - self::assertSame($lineNumber, $subject->getLineNo()); - } - /** * @test */ diff --git a/tests/Unit/CSSList/CSSListTest.php b/tests/Unit/CSSList/CSSListTest.php index ef30c9a3..ce20caaf 100644 --- a/tests/Unit/CSSList/CSSListTest.php +++ b/tests/Unit/CSSList/CSSListTest.php @@ -57,28 +57,6 @@ public function implementsCSSListItem(): void self::assertInstanceOf(CSSListItem::class, $subject); } - /** - * @test - */ - public function getLineNoByDefaultReturnsZero(): void - { - $subject = new ConcreteCSSList(); - - self::assertSame(0, $subject->getLineNo()); - } - - /** - * @test - */ - public function getLineNoReturnsLineNumberProvidedToConstructor(): void - { - $lineNumber = 42; - - $subject = new ConcreteCSSList($lineNumber); - - self::assertSame($lineNumber, $subject->getLineNo()); - } - /** * @test */ diff --git a/tests/Unit/CSSList/KeyFrameTest.php b/tests/Unit/CSSList/KeyFrameTest.php index 15c92795..e191a440 100644 --- a/tests/Unit/CSSList/KeyFrameTest.php +++ b/tests/Unit/CSSList/KeyFrameTest.php @@ -68,18 +68,6 @@ public function isCSSList(): void self::assertInstanceOf(CSSList::class, $subject); } - /** - * @test - */ - public function getLineNoReturnsLineNumberProvidedToConstructor(): void - { - $lineNumber = 42; - - $subject = new KeyFrame($lineNumber); - - self::assertSame($lineNumber, $subject->getLineNo()); - } - /** * @test */ diff --git a/tests/Unit/Comment/CommentTest.php b/tests/Unit/Comment/CommentTest.php index 0db0349a..69572e90 100644 --- a/tests/Unit/Comment/CommentTest.php +++ b/tests/Unit/Comment/CommentTest.php @@ -57,27 +57,6 @@ public function setCommentSetsComments(): void self::assertSame($comment, $subject->getComment()); } - /** - * @test - */ - public function getLineNoOnEmptyInstanceReturnsZero(): void - { - $subject = new Comment(); - - self::assertSame(0, $subject->getLineNo()); - } - - /** - * @test - */ - public function getLineNoInitiallyReturnsLineNumberPassedToConstructor(): void - { - $lineNumber = 42; - $subject = new Comment('', $lineNumber); - - self::assertSame($lineNumber, $subject->getLineNo()); - } - /** * @test */ diff --git a/tests/Unit/Parsing/OutputExceptionTest.php b/tests/Unit/Parsing/OutputExceptionTest.php index 768982d0..73db4ebd 100644 --- a/tests/Unit/Parsing/OutputExceptionTest.php +++ b/tests/Unit/Parsing/OutputExceptionTest.php @@ -32,27 +32,6 @@ public function getMessageReturnsMessageProvidedToConstructor(): void self::assertStringContainsString($message, $exception->getMessage()); } - /** - * @test - */ - public function getLineNoByDefaultReturnsZero(): void - { - $exception = new OutputException('foo'); - - self::assertSame(0, $exception->getLineNo()); - } - - /** - * @test - */ - public function getLineNoReturnsLineNumberProvidedToConstructor(): void - { - $lineNumber = 17; - $exception = new OutputException('foo', $lineNumber); - - self::assertSame($lineNumber, $exception->getLineNo()); - } - /** * @test */ diff --git a/tests/Unit/Parsing/SourceExceptionTest.php b/tests/Unit/Parsing/SourceExceptionTest.php index ea35f67d..6e22bc96 100644 --- a/tests/Unit/Parsing/SourceExceptionTest.php +++ b/tests/Unit/Parsing/SourceExceptionTest.php @@ -23,27 +23,6 @@ public function getMessageReturnsMessageProvidedToConstructor(): void self::assertStringContainsString($message, $exception->getMessage()); } - /** - * @test - */ - public function getLineNoByDefaultReturnsZero(): void - { - $exception = new SourceException('foo'); - - self::assertSame(0, $exception->getLineNo()); - } - - /** - * @test - */ - public function getLineNoReturnsLineNumberProvidedToConstructor(): void - { - $lineNumber = 17; - $exception = new SourceException('foo', $lineNumber); - - self::assertSame($lineNumber, $exception->getLineNo()); - } - /** * @test */ diff --git a/tests/Unit/Parsing/UnexpectedEOFExceptionTest.php b/tests/Unit/Parsing/UnexpectedEOFExceptionTest.php index 4c904070..32a649a0 100644 --- a/tests/Unit/Parsing/UnexpectedEOFExceptionTest.php +++ b/tests/Unit/Parsing/UnexpectedEOFExceptionTest.php @@ -21,27 +21,6 @@ public function extendsUnexpectedTokenException(): void self::assertInstanceOf(UnexpectedTokenException::class, new UnexpectedEOFException('expected', 'found')); } - /** - * @test - */ - public function getLineNoByDefaultReturnsZero(): void - { - $exception = new UnexpectedEOFException('expected', 'found'); - - self::assertSame(0, $exception->getLineNo()); - } - - /** - * @test - */ - public function getLineNoReturnsLineNumberProvidedToConstructor(): void - { - $lineNumber = 17; - $exception = new UnexpectedEOFException('expected', 'found', 'literal', $lineNumber); - - self::assertSame($lineNumber, $exception->getLineNo()); - } - /** * @test */ diff --git a/tests/Unit/Parsing/UnexpectedTokenExceptionTest.php b/tests/Unit/Parsing/UnexpectedTokenExceptionTest.php index 6bd2a8d7..d4fa9eca 100644 --- a/tests/Unit/Parsing/UnexpectedTokenExceptionTest.php +++ b/tests/Unit/Parsing/UnexpectedTokenExceptionTest.php @@ -21,27 +21,6 @@ public function extendsSourceException(): void self::assertInstanceOf(SourceException::class, new UnexpectedTokenException('expected', 'found')); } - /** - * @test - */ - public function getLineNoByDefaultReturnsZero(): void - { - $exception = new UnexpectedTokenException('expected', 'found'); - - self::assertSame(0, $exception->getLineNo()); - } - - /** - * @test - */ - public function getLineNoReturnsLineNumberProvidedToConstructor(): void - { - $lineNumber = 17; - $exception = new UnexpectedTokenException('expected', 'found', 'literal', $lineNumber); - - self::assertSame($lineNumber, $exception->getLineNo()); - } - /** * @test */ diff --git a/tests/Unit/Value/CSSStringTest.php b/tests/Unit/Value/CSSStringTest.php index ca0bf95a..c54ecdd7 100644 --- a/tests/Unit/Value/CSSStringTest.php +++ b/tests/Unit/Value/CSSStringTest.php @@ -60,28 +60,6 @@ public function setStringSetsString(): void self::assertSame($string, $subject->getString()); } - /** - * @test - */ - public function getLineNoByDefaultReturnsZero(): void - { - $subject = new CSSString(''); - - self::assertSame(0, $subject->getLineNo()); - } - - /** - * @test - */ - public function getLineNoReturnsLineNumberProvidedToConstructor(): void - { - $lineNumber = 42; - - $subject = new CSSString('', $lineNumber); - - self::assertSame($lineNumber, $subject->getLineNo()); - } - /** * @test */ diff --git a/tests/Unit/Value/CalcRuleValueListTest.php b/tests/Unit/Value/CalcRuleValueListTest.php index f891d0f5..7314e3c5 100644 --- a/tests/Unit/Value/CalcRuleValueListTest.php +++ b/tests/Unit/Value/CalcRuleValueListTest.php @@ -26,28 +26,6 @@ public function isRuleValueList(): void self::assertInstanceOf(RuleValueList::class, $subject); } - /** - * @test - */ - public function getLineNoByDefaultReturnsZero(): void - { - $subject = new CalcRuleValueList(); - - self::assertSame(0, $subject->getLineNo()); - } - - /** - * @test - */ - public function getLineNoReturnsLineNumberProvidedToConstructor(): void - { - $lineNumber = 42; - - $subject = new CalcRuleValueList($lineNumber); - - self::assertSame($lineNumber, $subject->getLineNo()); - } - /** * @test */ diff --git a/tests/Unit/Value/URLTest.php b/tests/Unit/Value/URLTest.php index cb29ee54..66654e64 100644 --- a/tests/Unit/Value/URLTest.php +++ b/tests/Unit/Value/URLTest.php @@ -60,28 +60,6 @@ public function setUrlReplacesUrl(): void self::assertSame($newUrl, $subject->getURL()); } - /** - * @test - */ - public function getLineNoByDefaultReturnsZero(): void - { - $subject = new URL(new CSSString('http://example.com')); - - self::assertSame(0, $subject->getLineNo()); - } - - /** - * @test - */ - public function getLineNoReturnsLineNumberProvidedToConstructor(): void - { - $lineNumber = 17; - - $subject = new URL(new CSSString('http://example.com'), $lineNumber); - - self::assertSame($lineNumber, $subject->getLineNo()); - } - /** * @test */ diff --git a/tests/UnitDeprecated/.gitkeep b/tests/UnitDeprecated/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tests/UnitDeprecated/Position/PositionTest.php b/tests/UnitDeprecated/Position/PositionTest.php deleted file mode 100644 index ec34c1f1..00000000 --- a/tests/UnitDeprecated/Position/PositionTest.php +++ /dev/null @@ -1,91 +0,0 @@ -subject = new ConcretePosition(); - } - - /** - * @return array}> - */ - public function provideLineNumber(): array - { - return [ - 'line 1' => [1], - 'line 42' => [42], - ]; - } - - /** - * @return array}> - */ - public function provideColumnNumber(): array - { - return [ - 'column 0' => [0], - 'column 14' => [14], - 'column 39' => [39], - ]; - } - - /** - * @test - */ - public function getLineNoInitiallyReturnsZero(): void - { - self::assertSame(0, $this->subject->getLineNo()); - } - - /** - * @test - * - * @dataProvider provideLineNumber - */ - public function getLineNoReturnsLineNumberSet(int $lineNumber): void - { - $this->subject->setPosition($lineNumber); - - self::assertSame($lineNumber, $this->subject->getLineNo()); - } - - /** - * @test - */ - public function getLineNoReturnsZeroAfterLineNumberCleared(): void - { - $this->subject->setPosition(99); - - $this->subject->setPosition(null); - - self::assertSame(0, $this->subject->getLineNo()); - } - - /** - * @test - */ - public function getLineNoAfterSetPositionWithZeroReturnsZero(): void - { - $this->subject->setPosition(99); - - $this->subject->setPosition(0); - - self::assertSame(0, $this->subject->getLineNo()); - } -} From 1b6a9576152a2e53edc72087322b6cd1b81ac64c Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Thu, 26 Jun 2025 20:05:47 +0100 Subject: [PATCH 472/555] [BUGFIX] Exclude absent line number from exception message (#1290) The bug was introduced by #1288, so has not been included in any release; thus a changelog entry is not justified. --- src/Parsing/SourceException.php | 2 +- tests/Unit/Parsing/SourceExceptionTest.php | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Parsing/SourceException.php b/src/Parsing/SourceException.php index b96c1878..43b3faf0 100644 --- a/src/Parsing/SourceException.php +++ b/src/Parsing/SourceException.php @@ -17,7 +17,7 @@ class SourceException extends \Exception implements Positionable public function __construct(string $message, ?int $lineNumber = null) { $this->setPosition($lineNumber); - if ($lineNumber !== 0) { + if ($lineNumber !== null) { $message .= " [line no: $lineNumber]"; } parent::__construct($message); diff --git a/tests/Unit/Parsing/SourceExceptionTest.php b/tests/Unit/Parsing/SourceExceptionTest.php index 6e22bc96..923f6c49 100644 --- a/tests/Unit/Parsing/SourceExceptionTest.php +++ b/tests/Unit/Parsing/SourceExceptionTest.php @@ -20,7 +20,7 @@ public function getMessageReturnsMessageProvidedToConstructor(): void $message = 'The cake is a lie.'; $exception = new SourceException($message); - self::assertStringContainsString($message, $exception->getMessage()); + self::assertSame($message, $exception->getMessage()); } /** @@ -55,6 +55,17 @@ public function getMessageWithLineNumberProvidedIncludesLineNumber(): void self::assertStringContainsString(' [line no: ' . $lineNumber . ']', $exception->getMessage()); } + /** + * @test + */ + public function getMessageWithLineNumberProvidedIncludesMessage(): void + { + $message = 'There is no flatware.'; + $exception = new SourceException($message, 17); + + self::assertStringContainsString($message, $exception->getMessage()); + } + /** * @test */ From ec1f6dd89ef4ef2ff04f602928cd1cb01a8929e8 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Fri, 27 Jun 2025 13:32:42 +0100 Subject: [PATCH 473/555] [TASK] Add `RuleContainerTest` trait, use in `RuleSetTest` (#1291) The trait provides tests for classes implementing `RuleContainer`. The test methods and data providers have been moved verbatim to the trait from `RuleSetTest`. Will be needed for #1194 --- tests/Unit/RuleSet/RuleContainerTest.php | 1200 ++++++++++++++++++++++ tests/Unit/RuleSet/RuleSetTest.php | 1185 +-------------------- 2 files changed, 1202 insertions(+), 1183 deletions(-) create mode 100644 tests/Unit/RuleSet/RuleContainerTest.php diff --git a/tests/Unit/RuleSet/RuleContainerTest.php b/tests/Unit/RuleSet/RuleContainerTest.php new file mode 100644 index 00000000..bcb641a2 --- /dev/null +++ b/tests/Unit/RuleSet/RuleContainerTest.php @@ -0,0 +1,1200 @@ +subject); + } + + /** + * @return array}> + */ + public static function providePropertyNames(): array + { + return [ + 'no properties' => [[]], + 'one property' => [['color']], + 'two different properties' => [['color', 'display']], + 'two of the same property' => [['color', 'color']], + ]; + } + + /** + * @return array + */ + public static function provideAnotherPropertyName(): array + { + 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 DataProvider, 1: string}> + */ + public static function provideInitialPropertyNamesAndAnotherPropertyName(): DataProvider + { + return DataProvider::cross(self::providePropertyNames(), self::provideAnotherPropertyName()); + } + + /** + * @test + * + * @param list $initialPropertyNames + * + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName + */ + public function addRuleWithoutPositionWithoutSiblingAddsRuleAfterInitialRules( + array $initialPropertyNames, + string $propertyNameToAdd + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->addRule($ruleToAdd); + + $rules = $this->subject->getRules(); + self::assertSame($ruleToAdd, \end($rules)); + } + + /** + * @test + * + * @param list $initialPropertyNames + * + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName + */ + public function addRuleWithoutPositionWithoutSiblingSetsValidLineNumber( + array $initialPropertyNames, + string $propertyNameToAdd + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->addRule($ruleToAdd); + + self::assertIsInt($ruleToAdd->getLineNumber(), 'line number not set'); + self::assertGreaterThanOrEqual(1, $ruleToAdd->getLineNumber(), 'line number not valid'); + } + + /** + * @test + * + * @param list $initialPropertyNames + * + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName + */ + public function addRuleWithoutPositionWithoutSiblingSetsValidColumnNumber( + array $initialPropertyNames, + string $propertyNameToAdd + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->addRule($ruleToAdd); + + self::assertIsInt($ruleToAdd->getColumnNumber(), 'column number not set'); + self::assertGreaterThanOrEqual(0, $ruleToAdd->getColumnNumber(), 'column number not valid'); + } + + /** + * @test + * + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName + * + * @param list $initialPropertyNames + */ + public function addRuleWithOnlyLineNumberWithoutSiblingAddsRule( + array $initialPropertyNames, + string $propertyNameToAdd + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $ruleToAdd->setPosition(42); + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->addRule($ruleToAdd); + + self::assertContains($ruleToAdd, $this->subject->getRules()); + } + + /** + * @test + * + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName + * + * @param list $initialPropertyNames + */ + public function addRuleWithOnlyLineNumberWithoutSiblingSetsColumnNumber( + array $initialPropertyNames, + string $propertyNameToAdd + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $ruleToAdd->setPosition(42); + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->addRule($ruleToAdd); + + self::assertIsInt($ruleToAdd->getColumnNumber(), 'column number not set'); + self::assertGreaterThanOrEqual(0, $ruleToAdd->getColumnNumber(), 'column number not valid'); + } + + /** + * @test + * + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName + * + * @param list $initialPropertyNames + */ + public function addRuleWithOnlyLineNumberWithoutSiblingPreservesLineNumber( + array $initialPropertyNames, + string $propertyNameToAdd + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $ruleToAdd->setPosition(42); + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->addRule($ruleToAdd); + + self::assertSame(42, $ruleToAdd->getLineNumber(), 'line number not preserved'); + } + + /** + * @test + * + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName + * + * @param list $initialPropertyNames + */ + public function addRuleWithOnlyColumnNumberWithoutSiblingAddsRuleAfterInitialRules( + array $initialPropertyNames, + string $propertyNameToAdd + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $ruleToAdd->setPosition(null, 42); + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->addRule($ruleToAdd); + + $rules = $this->subject->getRules(); + self::assertSame($ruleToAdd, \end($rules)); + } + + /** + * @test + * + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName + * + * @param list $initialPropertyNames + */ + public function addRuleWithOnlyColumnNumberWithoutSiblingSetsLineNumber( + array $initialPropertyNames, + string $propertyNameToAdd + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $ruleToAdd->setPosition(null, 42); + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->addRule($ruleToAdd); + + self::assertIsInt($ruleToAdd->getLineNumber(), 'line number not set'); + self::assertGreaterThanOrEqual(1, $ruleToAdd->getLineNumber(), 'line number not valid'); + } + + /** + * @test + * + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName + * + * @param list $initialPropertyNames + */ + public function addRuleWithOnlyColumnNumberWithoutSiblingPreservesColumnNumber( + array $initialPropertyNames, + string $propertyNameToAdd + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $ruleToAdd->setPosition(null, 42); + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->addRule($ruleToAdd); + + self::assertSame(42, $ruleToAdd->getColumnNumber(), 'column number not preserved'); + } + + /** + * @test + * + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName + * + * @param list $initialPropertyNames + */ + public function addRuleWithCompletePositionWithoutSiblingAddsRule( + array $initialPropertyNames, + string $propertyNameToAdd + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $ruleToAdd->setPosition(42, 64); + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->addRule($ruleToAdd); + + self::assertContains($ruleToAdd, $this->subject->getRules()); + } + + /** + * @test + * + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName + * + * @param list $initialPropertyNames + */ + public function addRuleWithCompletePositionWithoutSiblingPreservesPosition( + array $initialPropertyNames, + string $propertyNameToAdd + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $ruleToAdd->setPosition(42, 64); + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->addRule($ruleToAdd); + + self::assertSame(42, $ruleToAdd->getLineNumber(), 'line number not preserved'); + self::assertSame(64, $ruleToAdd->getColumnNumber(), 'column number not preserved'); + } + + /** + * @return array, 1: int<0, max>}> + */ + public static function provideInitialPropertyNamesAndIndexOfOne(): array + { + $initialPropertyNamesSets = self::providePropertyNames(); + + // Provide sets with each possible index for the initially set `Rule`s. + $initialPropertyNamesAndIndexSets = []; + foreach ($initialPropertyNamesSets as $setName => $data) { + $initialPropertyNames = $data[0]; + for ($index = 0; $index < \count($initialPropertyNames); ++$index) { + $initialPropertyNamesAndIndexSets[$setName . ', index ' . $index] = + [$initialPropertyNames, $index]; + } + } + + return $initialPropertyNamesAndIndexSets; + } + + /** + * @return DataProvider, 1: int<0, max>, 2: string}> + */ + public static function provideInitialPropertyNamesAndSiblingIndexAndPropertyNameToAdd(): DataProvider + { + return DataProvider::cross( + self::provideInitialPropertyNamesAndIndexOfOne(), + self::provideAnotherPropertyName() + ); + } + + /** + * @test + * + * @param non-empty-list $initialPropertyNames + * @param int<0, max> $siblingIndex + * + * @dataProvider provideInitialPropertyNamesAndSiblingIndexAndPropertyNameToAdd + */ + public function addRuleWithSiblingInsertsRuleBeforeSibling( + array $initialPropertyNames, + int $siblingIndex, + string $propertyNameToAdd + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $this->setRulesFromPropertyNames($initialPropertyNames); + $sibling = $this->subject->getRules()[$siblingIndex]; + + $this->subject->addRule($ruleToAdd, $sibling); + + $rules = $this->subject->getRules(); + $siblingPosition = \array_search($sibling, $rules, true); + self::assertIsInt($siblingPosition); + self::assertSame($siblingPosition - 1, \array_search($ruleToAdd, $rules, true)); + } + + /** + * @test + * + * @param non-empty-list $initialPropertyNames + * @param int<0, max> $siblingIndex + * + * @dataProvider provideInitialPropertyNamesAndSiblingIndexAndPropertyNameToAdd + */ + public function addRuleWithSiblingSetsValidLineNumber( + array $initialPropertyNames, + int $siblingIndex, + string $propertyNameToAdd + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $this->setRulesFromPropertyNames($initialPropertyNames); + $sibling = $this->subject->getRules()[$siblingIndex]; + + $this->subject->addRule($ruleToAdd, $sibling); + + self::assertIsInt($ruleToAdd->getLineNumber(), 'line number not set'); + self::assertGreaterThanOrEqual(1, $ruleToAdd->getLineNumber(), 'line number not valid'); + } + + /** + * @test + * + * @param non-empty-list $initialPropertyNames + * @param int<0, max> $siblingIndex + * + * @dataProvider provideInitialPropertyNamesAndSiblingIndexAndPropertyNameToAdd + */ + public function addRuleWithSiblingSetsValidColumnNumber( + array $initialPropertyNames, + int $siblingIndex, + string $propertyNameToAdd + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $this->setRulesFromPropertyNames($initialPropertyNames); + $sibling = $this->subject->getRules()[$siblingIndex]; + + $this->subject->addRule($ruleToAdd, $sibling); + + self::assertIsInt($ruleToAdd->getColumnNumber(), 'column number not set'); + self::assertGreaterThanOrEqual(0, $ruleToAdd->getColumnNumber(), 'column number not valid'); + } + + /** + * @test + * + * @param list $initialPropertyNames + * + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName + */ + public function addRuleWithSiblingNotInSetAddsRuleAfterInitialRules( + array $initialPropertyNames, + string $propertyNameToAdd + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $this->setRulesFromPropertyNames($initialPropertyNames); + + // `display` is sometimes in `$initialPropertyNames` and sometimes the `$propertyNameToAdd`. + // Choosing this for the bogus sibling allows testing all combinations of whether it is or isn't. + $this->subject->addRule($ruleToAdd, new Rule('display')); + + $rules = $this->subject->getRules(); + self::assertSame($ruleToAdd, \end($rules)); + } + + /** + * @test + * + * @param list $initialPropertyNames + * + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName + */ + public function addRuleWithSiblingNotInSetSetsValidLineNumber( + array $initialPropertyNames, + string $propertyNameToAdd + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $this->setRulesFromPropertyNames($initialPropertyNames); + + // `display` is sometimes in `$initialPropertyNames` and sometimes the `$propertyNameToAdd`. + // Choosing this for the bogus sibling allows testing all combinations of whether it is or isn't. + $this->subject->addRule($ruleToAdd, new Rule('display')); + + self::assertIsInt($ruleToAdd->getLineNumber(), 'line number not set'); + self::assertGreaterThanOrEqual(1, $ruleToAdd->getLineNumber(), 'line number not valid'); + } + + /** + * @test + * + * @param list $initialPropertyNames + * + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName + */ + public function addRuleWithSiblingNotInSetSetsValidColumnNumber( + array $initialPropertyNames, + string $propertyNameToAdd + ): void { + $ruleToAdd = new Rule($propertyNameToAdd); + $this->setRulesFromPropertyNames($initialPropertyNames); + + // `display` is sometimes in `$initialPropertyNames` and sometimes the `$propertyNameToAdd`. + // Choosing this for the bogus sibling allows testing all combinations of whether it is or isn't. + $this->subject->addRule($ruleToAdd, new Rule('display')); + + self::assertIsInt($ruleToAdd->getColumnNumber(), 'column number not set'); + self::assertGreaterThanOrEqual(0, $ruleToAdd->getColumnNumber(), 'column number not valid'); + } + + /** + * @test + * + * @param non-empty-list $initialPropertyNames + * @param int<0, max> $indexToRemove + * + * @dataProvider provideInitialPropertyNamesAndIndexOfOne + */ + public function removeRuleRemovesRuleInSet(array $initialPropertyNames, int $indexToRemove): void + { + $this->setRulesFromPropertyNames($initialPropertyNames); + $ruleToRemove = $this->subject->getRules()[$indexToRemove]; + + $this->subject->removeRule($ruleToRemove); + + self::assertNotContains($ruleToRemove, $this->subject->getRules()); + } + + /** + * @test + * + * @param non-empty-list $initialPropertyNames + * @param int<0, max> $indexToRemove + * + * @dataProvider provideInitialPropertyNamesAndIndexOfOne + */ + public function removeRuleRemovesExactlyOneRule(array $initialPropertyNames, int $indexToRemove): void + { + $this->setRulesFromPropertyNames($initialPropertyNames); + $ruleToRemove = $this->subject->getRules()[$indexToRemove]; + + $this->subject->removeRule($ruleToRemove); + + self::assertCount(\count($initialPropertyNames) - 1, $this->subject->getRules()); + } + + /** + * @test + * + * @param list $initialPropertyNames + * + * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName + */ + public function removeRuleWithRuleNotInSetKeepsSetUnchanged( + array $initialPropertyNames, + string $propertyNameToRemove + ): void { + $this->setRulesFromPropertyNames($initialPropertyNames); + $initialRules = $this->subject->getRules(); + $ruleToRemove = new Rule($propertyNameToRemove); + + $this->subject->removeRule($ruleToRemove); + + self::assertSame($initialRules, $this->subject->getRules()); + } + + /** + * @return array, 1: string, 2: list}> + */ + public static function providePropertyNamesAndPropertyNameToRemoveAndExpectedRemainingPropertyNames(): array + { + return [ + 'removing single rule' => [ + ['color'], + 'color', + [], + ], + 'removing first rule' => [ + ['color', 'display'], + 'color', + ['display'], + ], + 'removing last rule' => [ + ['color', 'display'], + 'display', + ['color'], + ], + 'removing middle rule' => [ + ['color', 'display', 'width'], + 'display', + ['color', 'width'], + ], + 'removing multiple rules' => [ + ['color', 'color'], + 'color', + [], + ], + 'removing multiple rules with another kept' => [ + ['color', 'color', 'display'], + 'color', + ['display'], + ], + 'removing nonexistent rule from empty list' => [ + [], + 'color', + [], + ], + 'removing nonexistent rule from nonempty list' => [ + ['color', 'display'], + 'width', + ['color', 'display'], + ], + ]; + } + + /** + * @test + * + * @param list $initialPropertyNames + * + * @dataProvider providePropertyNamesAndPropertyNameToRemoveAndExpectedRemainingPropertyNames + */ + public function removeMatchingRulesRemovesRulesWithPropertyName( + array $initialPropertyNames, + string $propertyNameToRemove + ): void { + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->removeMatchingRules($propertyNameToRemove); + + self::assertArrayNotHasKey($propertyNameToRemove, $this->subject->getRulesAssoc()); + } + + /** + * @test + * + * @param list $initialPropertyNames + * @param list $expectedRemainingPropertyNames + * + * @dataProvider providePropertyNamesAndPropertyNameToRemoveAndExpectedRemainingPropertyNames + */ + public function removeMatchingRulesWithPropertyNameKeepsOtherRules( + array $initialPropertyNames, + string $propertyNameToRemove, + array $expectedRemainingPropertyNames + ): void { + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->removeMatchingRules($propertyNameToRemove); + + $remainingRules = $this->subject->getRulesAssoc(); + if ($expectedRemainingPropertyNames === []) { + self::assertSame([], $remainingRules); + } + foreach ($expectedRemainingPropertyNames as $expectedPropertyName) { + self::assertArrayHasKey($expectedPropertyName, $remainingRules); + } + } + + /** + * @return array, 1: string, 2: list}> + */ + public static function providePropertyNamesAndPropertyNamePrefixToRemoveAndExpectedRemainingPropertyNames(): array + { + return [ + 'removing shorthand rule' => [ + ['font'], + 'font', + [], + ], + 'removing longhand rule' => [ + ['font-size'], + 'font', + [], + ], + 'removing shorthand and longhand rule' => [ + ['font', 'font-size'], + 'font', + [], + ], + 'removing shorthand rule with another kept' => [ + ['font', 'color'], + 'font', + ['color'], + ], + 'removing longhand rule with another kept' => [ + ['font-size', 'color'], + 'font', + ['color'], + ], + 'keeping other rules whose property names begin with the same characters' => [ + ['contain', 'container', 'container-type'], + 'contain', + ['container', 'container-type'], + ], + ]; + } + + /** + * @test + * + * @param list $initialPropertyNames + * + * @dataProvider providePropertyNamesAndPropertyNamePrefixToRemoveAndExpectedRemainingPropertyNames + */ + public function removeMatchingRulesRemovesRulesWithPropertyNamePrefix( + array $initialPropertyNames, + string $propertyNamePrefix + ): void { + $propertyNamePrefixWithHyphen = $propertyNamePrefix . '-'; + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->removeMatchingRules($propertyNamePrefixWithHyphen); + + $remainingRules = $this->subject->getRulesAssoc(); + self::assertArrayNotHasKey($propertyNamePrefix, $remainingRules); + foreach (\array_keys($remainingRules) as $remainingPropertyName) { + self::assertStringStartsNotWith($propertyNamePrefixWithHyphen, $remainingPropertyName); + } + } + + /** + * @test + * + * @param list $initialPropertyNames + * @param list $expectedRemainingPropertyNames + * + * @dataProvider providePropertyNamesAndPropertyNamePrefixToRemoveAndExpectedRemainingPropertyNames + */ + public function removeMatchingRulesWithPropertyNamePrefixKeepsOtherRules( + array $initialPropertyNames, + string $propertyNamePrefix, + array $expectedRemainingPropertyNames + ): void { + $propertyNamePrefixWithHyphen = $propertyNamePrefix . '-'; + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->removeMatchingRules($propertyNamePrefixWithHyphen); + + $remainingRules = $this->subject->getRulesAssoc(); + if ($expectedRemainingPropertyNames === []) { + self::assertSame([], $remainingRules); + } + foreach ($expectedRemainingPropertyNames as $expectedPropertyName) { + self::assertArrayHasKey($expectedPropertyName, $remainingRules); + } + } + + /** + * @test + * + * @param list $propertyNamesToRemove + * + * @dataProvider providePropertyNames + */ + public function removeAllRulesRemovesAllRules(array $propertyNamesToRemove): void + { + $this->setRulesFromPropertyNames($propertyNamesToRemove); + + $this->subject->removeAllRules(); + + self::assertSame([], $this->subject->getRules()); + } + + /** + * @test + * + * @param list $propertyNamesToSet + * + * @dataProvider providePropertyNames + */ + public function setRulesOnVirginSetsRulesWithoutPositionInOrder(array $propertyNamesToSet): void + { + $rulesToSet = self::createRulesFromPropertyNames($propertyNamesToSet); + + $this->subject->setRules($rulesToSet); + + self::assertSame($rulesToSet, $this->subject->getRules()); + } + + /** + * @return DataProvider, 1: list}> + */ + public static function provideInitialPropertyNamesAndPropertyNamesToSet(): DataProvider + { + return DataProvider::cross(self::providePropertyNames(), self::providePropertyNames()); + } + + /** + * @test + * + * @param list $initialPropertyNames + * @param list $propertyNamesToSet + * + * @dataProvider provideInitialPropertyNamesAndPropertyNamesToSet + */ + public function setRulesReplacesRules(array $initialPropertyNames, array $propertyNamesToSet): void + { + $rulesToSet = self::createRulesFromPropertyNames($propertyNamesToSet); + $this->setRulesFromPropertyNames($initialPropertyNames); + + $this->subject->setRules($rulesToSet); + + self::assertSame($rulesToSet, $this->subject->getRules()); + } + + /** + * @test + */ + public function setRulesWithRuleWithoutPositionSetsValidLineNumber(): void + { + $ruleToSet = new Rule('color'); + + $this->subject->setRules([$ruleToSet]); + + self::assertIsInt($ruleToSet->getLineNumber(), 'line number not set'); + self::assertGreaterThanOrEqual(1, $ruleToSet->getLineNumber(), 'line number not valid'); + } + + /** + * @test + */ + public function setRulesWithRuleWithoutPositionSetsValidColumnNumber(): void + { + $ruleToSet = new Rule('color'); + + $this->subject->setRules([$ruleToSet]); + + self::assertIsInt($ruleToSet->getColumnNumber(), 'column number not set'); + self::assertGreaterThanOrEqual(0, $ruleToSet->getColumnNumber(), 'column number not valid'); + } + + /** + * @test + */ + public function setRulesWithRuleWithOnlyLineNumberSetsColumnNumber(): void + { + $ruleToSet = new Rule('color'); + $ruleToSet->setPosition(42); + + $this->subject->setRules([$ruleToSet]); + + self::assertIsInt($ruleToSet->getColumnNumber(), 'column number not set'); + self::assertGreaterThanOrEqual(0, $ruleToSet->getColumnNumber(), 'column number not valid'); + } + + /** + * @test + */ + public function setRulesWithRuleWithOnlyLineNumberPreservesLineNumber(): void + { + $ruleToSet = new Rule('color'); + $ruleToSet->setPosition(42); + + $this->subject->setRules([$ruleToSet]); + + self::assertSame(42, $ruleToSet->getLineNumber(), 'line number not preserved'); + } + + /** + * @test + */ + public function setRulesWithRuleWithOnlyColumnNumberSetsLineNumber(): void + { + $ruleToSet = new Rule('color'); + $ruleToSet->setPosition(null, 42); + + $this->subject->setRules([$ruleToSet]); + + self::assertIsInt($ruleToSet->getLineNumber(), 'line number not set'); + self::assertGreaterThanOrEqual(1, $ruleToSet->getLineNumber(), 'line number not valid'); + } + + /** + * @test + */ + public function setRulesWithRuleWithOnlyColumnNumberPreservesColumnNumber(): void + { + $ruleToSet = new Rule('color'); + $ruleToSet->setPosition(null, 42); + + $this->subject->setRules([$ruleToSet]); + + self::assertSame(42, $ruleToSet->getColumnNumber(), 'column number not preserved'); + } + + /** + * @test + */ + public function setRulesWithRuleWithCompletePositionPreservesPosition(): void + { + $ruleToSet = new Rule('color'); + $ruleToSet->setPosition(42, 64); + + $this->subject->setRules([$ruleToSet]); + + self::assertSame(42, $ruleToSet->getLineNumber(), 'line number not preserved'); + self::assertSame(64, $ruleToSet->getColumnNumber(), 'column number not preserved'); + } + + /** + * @test + * + * @param list $propertyNamesToSet + * + * @dataProvider providePropertyNames + */ + public function getRulesReturnsRulesSet(array $propertyNamesToSet): void + { + $rulesToSet = self::createRulesFromPropertyNames($propertyNamesToSet); + $this->subject->setRules($rulesToSet); + + $result = $this->subject->getRules(); + + self::assertSame($rulesToSet, $result); + } + + /** + * @test + */ + public function getRulesOrdersByLineNumber(): void + { + $first = (new Rule('color'))->setPosition(1, 64); + $second = (new Rule('display'))->setPosition(19, 42); + $third = (new Rule('color'))->setPosition(55, 11); + $this->subject->setRules([$third, $second, $first]); + + $result = $this->subject->getRules(); + + self::assertSame([$first, $second, $third], $result); + } + + /** + * @test + */ + public function getRulesOrdersRulesWithSameLineNumberByColumnNumber(): void + { + $first = (new Rule('color'))->setPosition(1, 11); + $second = (new Rule('display'))->setPosition(1, 42); + $third = (new Rule('color'))->setPosition(1, 64); + $this->subject->setRules([$third, $second, $first]); + + $result = $this->subject->getRules(); + + self::assertSame([$first, $second, $third], $result); + } + + /** + * @return array, 1: string, 2: list}> + */ + public static function providePropertyNamesAndSearchPatternAndMatchingPropertyNames(): array + { + return [ + 'single rule matched' => [ + ['color'], + 'color', + ['color'], + ], + 'first rule matched' => [ + ['color', 'display'], + 'color', + ['color'], + ], + 'last rule matched' => [ + ['color', 'display'], + 'display', + ['display'], + ], + 'middle rule matched' => [ + ['color', 'display', 'width'], + 'display', + ['display'], + ], + 'multiple rules for the same property matched' => [ + ['color', 'color'], + 'color', + ['color'], + ], + 'multiple rules for the same property matched in haystack' => [ + ['color', 'display', 'color', 'width'], + 'color', + ['color'], + ], + 'shorthand rule matched' => [ + ['font'], + 'font-', + ['font'], + ], + 'longhand rule matched' => [ + ['font-size'], + 'font-', + ['font-size'], + ], + 'shorthand and longhand rule matched' => [ + ['font', 'font-size'], + 'font-', + ['font', 'font-size'], + ], + 'shorthand rule matched in haystack' => [ + ['font', 'color'], + 'font-', + ['font'], + ], + 'longhand rule matched in haystack' => [ + ['font-size', 'color'], + 'font-', + ['font-size'], + ], + 'rules whose property names begin with the same characters not matched with pattern match' => [ + ['contain', 'container', 'container-type'], + 'contain-', + ['contain'], + ], + 'rules whose property names begin with the same characters not matched with exact match' => [ + ['contain', 'container', 'container-type'], + 'contain', + ['contain'], + ], + ]; + } + + /** + * @test + * + * @param list $propertyNamesToSet + * @param list $matchingPropertyNames + * + * @dataProvider providePropertyNamesAndSearchPatternAndMatchingPropertyNames + */ + public function getRulesWithPatternReturnsAllMatchingRules( + array $propertyNamesToSet, + string $searchPattern, + array $matchingPropertyNames + ): void { + $rulesToSet = self::createRulesFromPropertyNames($propertyNamesToSet); + // Use `array_values` to ensure canonical numeric array, since `array_filter` does not reindex. + $matchingRules = \array_values( + \array_filter( + $rulesToSet, + static function (Rule $rule) use ($matchingPropertyNames): bool { + return \in_array($rule->getRule(), $matchingPropertyNames, true); + } + ) + ); + $this->subject->setRules($rulesToSet); + + $result = $this->subject->getRules($searchPattern); + + // `Rule`s without pre-set positions are returned in the order set. This is tested separately. + self::assertSame($matchingRules, $result); + } + + /** + * @return array, 1: string}> + */ + public static function providePropertyNamesAndNonMatchingSearchPattern(): array + { + return [ + 'no match in empty list' => [ + [], + 'color', + ], + 'no match for different property' => [ + ['color'], + 'display', + ], + 'no match for property not in list' => [ + ['color', 'display'], + 'width', + ], + ]; + } + + /** + * @test + * + * @param list $propertyNamesToSet + * + * @dataProvider providePropertyNamesAndNonMatchingSearchPattern + */ + public function getRulesWithNonMatchingPatternReturnsEmptyArray( + array $propertyNamesToSet, + string $searchPattern + ): void { + $this->setRulesFromPropertyNames($propertyNamesToSet); + + $result = $this->subject->getRules($searchPattern); + + self::assertSame([], $result); + } + + /** + * @test + */ + public function getRulesWithPatternOrdersRulesByPosition(): void + { + $first = (new Rule('color'))->setPosition(1, 42); + $second = (new Rule('color'))->setPosition(1, 64); + $third = (new Rule('color'))->setPosition(55, 7); + $this->subject->setRules([$third, $second, $first]); + + $result = $this->subject->getRules('color'); + + self::assertSame([$first, $second, $third], $result); + } + + /** + * @return array}> + */ + public static function provideDistinctPropertyNames(): array + { + return [ + 'no properties' => [[]], + 'one property' => [['color']], + 'two properties' => [['color', 'display']], + ]; + } + + /** + * @test + * + * @param list $propertyNamesToSet + * + * @dataProvider provideDistinctPropertyNames + */ + public function getRulesAssocReturnsAllRulesWithDistinctPropertyNames(array $propertyNamesToSet): void + { + $rulesToSet = self::createRulesFromPropertyNames($propertyNamesToSet); + $this->subject->setRules($rulesToSet); + + $result = $this->subject->getRulesAssoc(); + + self::assertSame($rulesToSet, \array_values($result)); + } + + /** + * @test + */ + public function getRulesAssocReturnsLastRuleWithSamePropertyName(): void + { + $firstRule = new Rule('color'); + $lastRule = new Rule('color'); + $this->subject->setRules([$firstRule, $lastRule]); + + $result = $this->subject->getRulesAssoc(); + + self::assertSame([$lastRule], \array_values($result)); + } + + /** + * @test + */ + public function getRulesAssocOrdersRulesByPosition(): void + { + $first = (new Rule('color'))->setPosition(1, 42); + $second = (new Rule('display'))->setPosition(1, 64); + $third = (new Rule('width'))->setPosition(55, 7); + $this->subject->setRules([$third, $second, $first]); + + $result = $this->subject->getRulesAssoc(); + + self::assertSame([$first, $second, $third], \array_values($result)); + } + + /** + * @test + */ + public function getRulesAssocKeysRulesByPropertyName(): void + { + $this->subject->setRules([new Rule('color'), new Rule('display')]); + + $result = $this->subject->getRulesAssoc(); + + foreach ($result as $key => $rule) { + self::assertSame($rule->getRule(), $key); + } + } + + /** + * @test + * + * @param list $propertyNamesToSet + * @param list $matchingPropertyNames + * + * @dataProvider providePropertyNamesAndSearchPatternAndMatchingPropertyNames + */ + public function getRulesAssocWithPatternReturnsAllMatchingPropertyNames( + array $propertyNamesToSet, + string $searchPattern, + array $matchingPropertyNames + ): void { + $this->setRulesFromPropertyNames($propertyNamesToSet); + + $result = $this->subject->getRulesAssoc($searchPattern); + + $resultPropertyNames = \array_keys($result); + \sort($matchingPropertyNames); + \sort($resultPropertyNames); + self::assertSame($matchingPropertyNames, $resultPropertyNames); + } + + /** + * @test + * + * @param list $propertyNamesToSet + * + * @dataProvider providePropertyNamesAndNonMatchingSearchPattern + */ + public function getRulesAssocWithNonMatchingPatternReturnsEmptyArray( + array $propertyNamesToSet, + string $searchPattern + ): void { + $this->setRulesFromPropertyNames($propertyNamesToSet); + + $result = $this->subject->getRulesAssoc($searchPattern); + + self::assertSame([], $result); + } + + /** + * @test + */ + public function getRulesAssocWithPatternOrdersRulesByPosition(): void + { + $first = (new Rule('font'))->setPosition(1, 42); + $second = (new Rule('font-family'))->setPosition(1, 64); + $third = (new Rule('font-weight'))->setPosition(55, 7); + $this->subject->setRules([$third, $second, $first]); + + $result = $this->subject->getRules('font-'); + + self::assertSame([$first, $second, $third], \array_values($result)); + } + + /** + * @param list $propertyNames + */ + private function setRulesFromPropertyNames(array $propertyNames): void + { + $this->subject->setRules(self::createRulesFromPropertyNames($propertyNames)); + } + + /** + * @param list $propertyNames + * + * @return list + */ + private static function createRulesFromPropertyNames(array $propertyNames): array + { + return \array_map( + function (string $propertyName): Rule { + return new Rule($propertyName); + }, + $propertyNames + ); + } +} diff --git a/tests/Unit/RuleSet/RuleSetTest.php b/tests/Unit/RuleSet/RuleSetTest.php index bf763a78..92d091b7 100644 --- a/tests/Unit/RuleSet/RuleSetTest.php +++ b/tests/Unit/RuleSet/RuleSetTest.php @@ -7,16 +7,15 @@ use PHPUnit\Framework\TestCase; use Sabberworm\CSS\CSSElement; use Sabberworm\CSS\CSSList\CSSListItem; -use Sabberworm\CSS\Rule\Rule; -use Sabberworm\CSS\RuleSet\RuleContainer; use Sabberworm\CSS\Tests\Unit\RuleSet\Fixtures\ConcreteRuleSet; -use TRegx\PhpUnit\DataProviders\DataProvider; /** * @covers \Sabberworm\CSS\RuleSet\RuleSet */ final class RuleSetTest extends TestCase { + use RuleContainerTest; + /** * @var ConcreteRuleSet */ @@ -42,1184 +41,4 @@ public function implementsCSSListItem(): void { self::assertInstanceOf(CSSListItem::class, $this->subject); } - - /** - * @test - */ - public function implementsRuleContainer(): void - { - self::assertInstanceOf(RuleContainer::class, $this->subject); - } - - /** - * @return array}> - */ - public static function providePropertyNames(): array - { - return [ - 'no properties' => [[]], - 'one property' => [['color']], - 'two different properties' => [['color', 'display']], - 'two of the same property' => [['color', 'color']], - ]; - } - - /** - * @return array - */ - public static function provideAnotherPropertyName(): array - { - 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 DataProvider, 1: string}> - */ - public static function provideInitialPropertyNamesAndAnotherPropertyName(): DataProvider - { - return DataProvider::cross(self::providePropertyNames(), self::provideAnotherPropertyName()); - } - - /** - * @test - * - * @param list $initialPropertyNames - * - * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName - */ - public function addRuleWithoutPositionWithoutSiblingAddsRuleAfterInitialRules( - array $initialPropertyNames, - string $propertyNameToAdd - ): void { - $ruleToAdd = new Rule($propertyNameToAdd); - $this->setRulesFromPropertyNames($initialPropertyNames); - - $this->subject->addRule($ruleToAdd); - - $rules = $this->subject->getRules(); - self::assertSame($ruleToAdd, \end($rules)); - } - - /** - * @test - * - * @param list $initialPropertyNames - * - * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName - */ - public function addRuleWithoutPositionWithoutSiblingSetsValidLineNumber( - array $initialPropertyNames, - string $propertyNameToAdd - ): void { - $ruleToAdd = new Rule($propertyNameToAdd); - $this->setRulesFromPropertyNames($initialPropertyNames); - - $this->subject->addRule($ruleToAdd); - - self::assertIsInt($ruleToAdd->getLineNumber(), 'line number not set'); - self::assertGreaterThanOrEqual(1, $ruleToAdd->getLineNumber(), 'line number not valid'); - } - - /** - * @test - * - * @param list $initialPropertyNames - * - * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName - */ - public function addRuleWithoutPositionWithoutSiblingSetsValidColumnNumber( - array $initialPropertyNames, - string $propertyNameToAdd - ): void { - $ruleToAdd = new Rule($propertyNameToAdd); - $this->setRulesFromPropertyNames($initialPropertyNames); - - $this->subject->addRule($ruleToAdd); - - self::assertIsInt($ruleToAdd->getColumnNumber(), 'column number not set'); - self::assertGreaterThanOrEqual(0, $ruleToAdd->getColumnNumber(), 'column number not valid'); - } - - /** - * @test - * - * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName - * - * @param list $initialPropertyNames - */ - public function addRuleWithOnlyLineNumberWithoutSiblingAddsRule( - array $initialPropertyNames, - string $propertyNameToAdd - ): void { - $ruleToAdd = new Rule($propertyNameToAdd); - $ruleToAdd->setPosition(42); - $this->setRulesFromPropertyNames($initialPropertyNames); - - $this->subject->addRule($ruleToAdd); - - self::assertContains($ruleToAdd, $this->subject->getRules()); - } - - /** - * @test - * - * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName - * - * @param list $initialPropertyNames - */ - public function addRuleWithOnlyLineNumberWithoutSiblingSetsColumnNumber( - array $initialPropertyNames, - string $propertyNameToAdd - ): void { - $ruleToAdd = new Rule($propertyNameToAdd); - $ruleToAdd->setPosition(42); - $this->setRulesFromPropertyNames($initialPropertyNames); - - $this->subject->addRule($ruleToAdd); - - self::assertIsInt($ruleToAdd->getColumnNumber(), 'column number not set'); - self::assertGreaterThanOrEqual(0, $ruleToAdd->getColumnNumber(), 'column number not valid'); - } - - /** - * @test - * - * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName - * - * @param list $initialPropertyNames - */ - public function addRuleWithOnlyLineNumberWithoutSiblingPreservesLineNumber( - array $initialPropertyNames, - string $propertyNameToAdd - ): void { - $ruleToAdd = new Rule($propertyNameToAdd); - $ruleToAdd->setPosition(42); - $this->setRulesFromPropertyNames($initialPropertyNames); - - $this->subject->addRule($ruleToAdd); - - self::assertSame(42, $ruleToAdd->getLineNumber(), 'line number not preserved'); - } - - /** - * @test - * - * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName - * - * @param list $initialPropertyNames - */ - public function addRuleWithOnlyColumnNumberWithoutSiblingAddsRuleAfterInitialRules( - array $initialPropertyNames, - string $propertyNameToAdd - ): void { - $ruleToAdd = new Rule($propertyNameToAdd); - $ruleToAdd->setPosition(null, 42); - $this->setRulesFromPropertyNames($initialPropertyNames); - - $this->subject->addRule($ruleToAdd); - - $rules = $this->subject->getRules(); - self::assertSame($ruleToAdd, \end($rules)); - } - - /** - * @test - * - * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName - * - * @param list $initialPropertyNames - */ - public function addRuleWithOnlyColumnNumberWithoutSiblingSetsLineNumber( - array $initialPropertyNames, - string $propertyNameToAdd - ): void { - $ruleToAdd = new Rule($propertyNameToAdd); - $ruleToAdd->setPosition(null, 42); - $this->setRulesFromPropertyNames($initialPropertyNames); - - $this->subject->addRule($ruleToAdd); - - self::assertIsInt($ruleToAdd->getLineNumber(), 'line number not set'); - self::assertGreaterThanOrEqual(1, $ruleToAdd->getLineNumber(), 'line number not valid'); - } - - /** - * @test - * - * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName - * - * @param list $initialPropertyNames - */ - public function addRuleWithOnlyColumnNumberWithoutSiblingPreservesColumnNumber( - array $initialPropertyNames, - string $propertyNameToAdd - ): void { - $ruleToAdd = new Rule($propertyNameToAdd); - $ruleToAdd->setPosition(null, 42); - $this->setRulesFromPropertyNames($initialPropertyNames); - - $this->subject->addRule($ruleToAdd); - - self::assertSame(42, $ruleToAdd->getColumnNumber(), 'column number not preserved'); - } - - /** - * @test - * - * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName - * - * @param list $initialPropertyNames - */ - public function addRuleWithCompletePositionWithoutSiblingAddsRule( - array $initialPropertyNames, - string $propertyNameToAdd - ): void { - $ruleToAdd = new Rule($propertyNameToAdd); - $ruleToAdd->setPosition(42, 64); - $this->setRulesFromPropertyNames($initialPropertyNames); - - $this->subject->addRule($ruleToAdd); - - self::assertContains($ruleToAdd, $this->subject->getRules()); - } - - /** - * @test - * - * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName - * - * @param list $initialPropertyNames - */ - public function addRuleWithCompletePositionWithoutSiblingPreservesPosition( - array $initialPropertyNames, - string $propertyNameToAdd - ): void { - $ruleToAdd = new Rule($propertyNameToAdd); - $ruleToAdd->setPosition(42, 64); - $this->setRulesFromPropertyNames($initialPropertyNames); - - $this->subject->addRule($ruleToAdd); - - self::assertSame(42, $ruleToAdd->getLineNumber(), 'line number not preserved'); - self::assertSame(64, $ruleToAdd->getColumnNumber(), 'column number not preserved'); - } - - /** - * @return array, 1: int<0, max>}> - */ - public static function provideInitialPropertyNamesAndIndexOfOne(): array - { - $initialPropertyNamesSets = self::providePropertyNames(); - - // Provide sets with each possible index for the initially set `Rule`s. - $initialPropertyNamesAndIndexSets = []; - foreach ($initialPropertyNamesSets as $setName => $data) { - $initialPropertyNames = $data[0]; - for ($index = 0; $index < \count($initialPropertyNames); ++$index) { - $initialPropertyNamesAndIndexSets[$setName . ', index ' . $index] = - [$initialPropertyNames, $index]; - } - } - - return $initialPropertyNamesAndIndexSets; - } - - /** - * @return DataProvider, 1: int<0, max>, 2: string}> - */ - public static function provideInitialPropertyNamesAndSiblingIndexAndPropertyNameToAdd(): DataProvider - { - return DataProvider::cross( - self::provideInitialPropertyNamesAndIndexOfOne(), - self::provideAnotherPropertyName() - ); - } - - /** - * @test - * - * @param non-empty-list $initialPropertyNames - * @param int<0, max> $siblingIndex - * - * @dataProvider provideInitialPropertyNamesAndSiblingIndexAndPropertyNameToAdd - */ - public function addRuleWithSiblingInsertsRuleBeforeSibling( - array $initialPropertyNames, - int $siblingIndex, - string $propertyNameToAdd - ): void { - $ruleToAdd = new Rule($propertyNameToAdd); - $this->setRulesFromPropertyNames($initialPropertyNames); - $sibling = $this->subject->getRules()[$siblingIndex]; - - $this->subject->addRule($ruleToAdd, $sibling); - - $rules = $this->subject->getRules(); - $siblingPosition = \array_search($sibling, $rules, true); - self::assertIsInt($siblingPosition); - self::assertSame($siblingPosition - 1, \array_search($ruleToAdd, $rules, true)); - } - - /** - * @test - * - * @param non-empty-list $initialPropertyNames - * @param int<0, max> $siblingIndex - * - * @dataProvider provideInitialPropertyNamesAndSiblingIndexAndPropertyNameToAdd - */ - public function addRuleWithSiblingSetsValidLineNumber( - array $initialPropertyNames, - int $siblingIndex, - string $propertyNameToAdd - ): void { - $ruleToAdd = new Rule($propertyNameToAdd); - $this->setRulesFromPropertyNames($initialPropertyNames); - $sibling = $this->subject->getRules()[$siblingIndex]; - - $this->subject->addRule($ruleToAdd, $sibling); - - self::assertIsInt($ruleToAdd->getLineNumber(), 'line number not set'); - self::assertGreaterThanOrEqual(1, $ruleToAdd->getLineNumber(), 'line number not valid'); - } - - /** - * @test - * - * @param non-empty-list $initialPropertyNames - * @param int<0, max> $siblingIndex - * - * @dataProvider provideInitialPropertyNamesAndSiblingIndexAndPropertyNameToAdd - */ - public function addRuleWithSiblingSetsValidColumnNumber( - array $initialPropertyNames, - int $siblingIndex, - string $propertyNameToAdd - ): void { - $ruleToAdd = new Rule($propertyNameToAdd); - $this->setRulesFromPropertyNames($initialPropertyNames); - $sibling = $this->subject->getRules()[$siblingIndex]; - - $this->subject->addRule($ruleToAdd, $sibling); - - self::assertIsInt($ruleToAdd->getColumnNumber(), 'column number not set'); - self::assertGreaterThanOrEqual(0, $ruleToAdd->getColumnNumber(), 'column number not valid'); - } - - /** - * @test - * - * @param list $initialPropertyNames - * - * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName - */ - public function addRuleWithSiblingNotInSetAddsRuleAfterInitialRules( - array $initialPropertyNames, - string $propertyNameToAdd - ): void { - $ruleToAdd = new Rule($propertyNameToAdd); - $this->setRulesFromPropertyNames($initialPropertyNames); - - // `display` is sometimes in `$initialPropertyNames` and sometimes the `$propertyNameToAdd`. - // Choosing this for the bogus sibling allows testing all combinations of whether it is or isn't. - $this->subject->addRule($ruleToAdd, new Rule('display')); - - $rules = $this->subject->getRules(); - self::assertSame($ruleToAdd, \end($rules)); - } - - /** - * @test - * - * @param list $initialPropertyNames - * - * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName - */ - public function addRuleWithSiblingNotInSetSetsValidLineNumber( - array $initialPropertyNames, - string $propertyNameToAdd - ): void { - $ruleToAdd = new Rule($propertyNameToAdd); - $this->setRulesFromPropertyNames($initialPropertyNames); - - // `display` is sometimes in `$initialPropertyNames` and sometimes the `$propertyNameToAdd`. - // Choosing this for the bogus sibling allows testing all combinations of whether it is or isn't. - $this->subject->addRule($ruleToAdd, new Rule('display')); - - self::assertIsInt($ruleToAdd->getLineNumber(), 'line number not set'); - self::assertGreaterThanOrEqual(1, $ruleToAdd->getLineNumber(), 'line number not valid'); - } - - /** - * @test - * - * @param list $initialPropertyNames - * - * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName - */ - public function addRuleWithSiblingNotInSetSetsValidColumnNumber( - array $initialPropertyNames, - string $propertyNameToAdd - ): void { - $ruleToAdd = new Rule($propertyNameToAdd); - $this->setRulesFromPropertyNames($initialPropertyNames); - - // `display` is sometimes in `$initialPropertyNames` and sometimes the `$propertyNameToAdd`. - // Choosing this for the bogus sibling allows testing all combinations of whether it is or isn't. - $this->subject->addRule($ruleToAdd, new Rule('display')); - - self::assertIsInt($ruleToAdd->getColumnNumber(), 'column number not set'); - self::assertGreaterThanOrEqual(0, $ruleToAdd->getColumnNumber(), 'column number not valid'); - } - - /** - * @test - * - * @param non-empty-list $initialPropertyNames - * @param int<0, max> $indexToRemove - * - * @dataProvider provideInitialPropertyNamesAndIndexOfOne - */ - public function removeRuleRemovesRuleInSet(array $initialPropertyNames, int $indexToRemove): void - { - $this->setRulesFromPropertyNames($initialPropertyNames); - $ruleToRemove = $this->subject->getRules()[$indexToRemove]; - - $this->subject->removeRule($ruleToRemove); - - self::assertNotContains($ruleToRemove, $this->subject->getRules()); - } - - /** - * @test - * - * @param non-empty-list $initialPropertyNames - * @param int<0, max> $indexToRemove - * - * @dataProvider provideInitialPropertyNamesAndIndexOfOne - */ - public function removeRuleRemovesExactlyOneRule(array $initialPropertyNames, int $indexToRemove): void - { - $this->setRulesFromPropertyNames($initialPropertyNames); - $ruleToRemove = $this->subject->getRules()[$indexToRemove]; - - $this->subject->removeRule($ruleToRemove); - - self::assertCount(\count($initialPropertyNames) - 1, $this->subject->getRules()); - } - - /** - * @test - * - * @param list $initialPropertyNames - * - * @dataProvider provideInitialPropertyNamesAndAnotherPropertyName - */ - public function removeRuleWithRuleNotInSetKeepsSetUnchanged( - array $initialPropertyNames, - string $propertyNameToRemove - ): void { - $this->setRulesFromPropertyNames($initialPropertyNames); - $initialRules = $this->subject->getRules(); - $ruleToRemove = new Rule($propertyNameToRemove); - - $this->subject->removeRule($ruleToRemove); - - self::assertSame($initialRules, $this->subject->getRules()); - } - - /** - * @return array, 1: string, 2: list}> - */ - public static function providePropertyNamesAndPropertyNameToRemoveAndExpectedRemainingPropertyNames(): array - { - return [ - 'removing single rule' => [ - ['color'], - 'color', - [], - ], - 'removing first rule' => [ - ['color', 'display'], - 'color', - ['display'], - ], - 'removing last rule' => [ - ['color', 'display'], - 'display', - ['color'], - ], - 'removing middle rule' => [ - ['color', 'display', 'width'], - 'display', - ['color', 'width'], - ], - 'removing multiple rules' => [ - ['color', 'color'], - 'color', - [], - ], - 'removing multiple rules with another kept' => [ - ['color', 'color', 'display'], - 'color', - ['display'], - ], - 'removing nonexistent rule from empty list' => [ - [], - 'color', - [], - ], - 'removing nonexistent rule from nonempty list' => [ - ['color', 'display'], - 'width', - ['color', 'display'], - ], - ]; - } - - /** - * @test - * - * @param list $initialPropertyNames - * - * @dataProvider providePropertyNamesAndPropertyNameToRemoveAndExpectedRemainingPropertyNames - */ - public function removeMatchingRulesRemovesRulesWithPropertyName( - array $initialPropertyNames, - string $propertyNameToRemove - ): void { - $this->setRulesFromPropertyNames($initialPropertyNames); - - $this->subject->removeMatchingRules($propertyNameToRemove); - - self::assertArrayNotHasKey($propertyNameToRemove, $this->subject->getRulesAssoc()); - } - - /** - * @test - * - * @param list $initialPropertyNames - * @param list $expectedRemainingPropertyNames - * - * @dataProvider providePropertyNamesAndPropertyNameToRemoveAndExpectedRemainingPropertyNames - */ - public function removeMatchingRulesWithPropertyNameKeepsOtherRules( - array $initialPropertyNames, - string $propertyNameToRemove, - array $expectedRemainingPropertyNames - ): void { - $this->setRulesFromPropertyNames($initialPropertyNames); - - $this->subject->removeMatchingRules($propertyNameToRemove); - - $remainingRules = $this->subject->getRulesAssoc(); - if ($expectedRemainingPropertyNames === []) { - self::assertSame([], $remainingRules); - } - foreach ($expectedRemainingPropertyNames as $expectedPropertyName) { - self::assertArrayHasKey($expectedPropertyName, $remainingRules); - } - } - - /** - * @return array, 1: string, 2: list}> - */ - public static function providePropertyNamesAndPropertyNamePrefixToRemoveAndExpectedRemainingPropertyNames(): array - { - return [ - 'removing shorthand rule' => [ - ['font'], - 'font', - [], - ], - 'removing longhand rule' => [ - ['font-size'], - 'font', - [], - ], - 'removing shorthand and longhand rule' => [ - ['font', 'font-size'], - 'font', - [], - ], - 'removing shorthand rule with another kept' => [ - ['font', 'color'], - 'font', - ['color'], - ], - 'removing longhand rule with another kept' => [ - ['font-size', 'color'], - 'font', - ['color'], - ], - 'keeping other rules whose property names begin with the same characters' => [ - ['contain', 'container', 'container-type'], - 'contain', - ['container', 'container-type'], - ], - ]; - } - - /** - * @test - * - * @param list $initialPropertyNames - * - * @dataProvider providePropertyNamesAndPropertyNamePrefixToRemoveAndExpectedRemainingPropertyNames - */ - public function removeMatchingRulesRemovesRulesWithPropertyNamePrefix( - array $initialPropertyNames, - string $propertyNamePrefix - ): void { - $propertyNamePrefixWithHyphen = $propertyNamePrefix . '-'; - $this->setRulesFromPropertyNames($initialPropertyNames); - - $this->subject->removeMatchingRules($propertyNamePrefixWithHyphen); - - $remainingRules = $this->subject->getRulesAssoc(); - self::assertArrayNotHasKey($propertyNamePrefix, $remainingRules); - foreach (\array_keys($remainingRules) as $remainingPropertyName) { - self::assertStringStartsNotWith($propertyNamePrefixWithHyphen, $remainingPropertyName); - } - } - - /** - * @test - * - * @param list $initialPropertyNames - * @param list $expectedRemainingPropertyNames - * - * @dataProvider providePropertyNamesAndPropertyNamePrefixToRemoveAndExpectedRemainingPropertyNames - */ - public function removeMatchingRulesWithPropertyNamePrefixKeepsOtherRules( - array $initialPropertyNames, - string $propertyNamePrefix, - array $expectedRemainingPropertyNames - ): void { - $propertyNamePrefixWithHyphen = $propertyNamePrefix . '-'; - $this->setRulesFromPropertyNames($initialPropertyNames); - - $this->subject->removeMatchingRules($propertyNamePrefixWithHyphen); - - $remainingRules = $this->subject->getRulesAssoc(); - if ($expectedRemainingPropertyNames === []) { - self::assertSame([], $remainingRules); - } - foreach ($expectedRemainingPropertyNames as $expectedPropertyName) { - self::assertArrayHasKey($expectedPropertyName, $remainingRules); - } - } - - /** - * @test - * - * @param list $propertyNamesToRemove - * - * @dataProvider providePropertyNames - */ - public function removeAllRulesRemovesAllRules(array $propertyNamesToRemove): void - { - $this->setRulesFromPropertyNames($propertyNamesToRemove); - - $this->subject->removeAllRules(); - - self::assertSame([], $this->subject->getRules()); - } - - /** - * @test - * - * @param list $propertyNamesToSet - * - * @dataProvider providePropertyNames - */ - public function setRulesOnVirginSetsRulesWithoutPositionInOrder(array $propertyNamesToSet): void - { - $rulesToSet = self::createRulesFromPropertyNames($propertyNamesToSet); - - $this->subject->setRules($rulesToSet); - - self::assertSame($rulesToSet, $this->subject->getRules()); - } - - /** - * @return DataProvider, 1: list}> - */ - public static function provideInitialPropertyNamesAndPropertyNamesToSet(): DataProvider - { - return DataProvider::cross(self::providePropertyNames(), self::providePropertyNames()); - } - - /** - * @test - * - * @param list $initialPropertyNames - * @param list $propertyNamesToSet - * - * @dataProvider provideInitialPropertyNamesAndPropertyNamesToSet - */ - public function setRulesReplacesRules(array $initialPropertyNames, array $propertyNamesToSet): void - { - $rulesToSet = self::createRulesFromPropertyNames($propertyNamesToSet); - $this->setRulesFromPropertyNames($initialPropertyNames); - - $this->subject->setRules($rulesToSet); - - self::assertSame($rulesToSet, $this->subject->getRules()); - } - - /** - * @test - */ - public function setRulesWithRuleWithoutPositionSetsValidLineNumber(): void - { - $ruleToSet = new Rule('color'); - - $this->subject->setRules([$ruleToSet]); - - self::assertIsInt($ruleToSet->getLineNumber(), 'line number not set'); - self::assertGreaterThanOrEqual(1, $ruleToSet->getLineNumber(), 'line number not valid'); - } - - /** - * @test - */ - public function setRulesWithRuleWithoutPositionSetsValidColumnNumber(): void - { - $ruleToSet = new Rule('color'); - - $this->subject->setRules([$ruleToSet]); - - self::assertIsInt($ruleToSet->getColumnNumber(), 'column number not set'); - self::assertGreaterThanOrEqual(0, $ruleToSet->getColumnNumber(), 'column number not valid'); - } - - /** - * @test - */ - public function setRulesWithRuleWithOnlyLineNumberSetsColumnNumber(): void - { - $ruleToSet = new Rule('color'); - $ruleToSet->setPosition(42); - - $this->subject->setRules([$ruleToSet]); - - self::assertIsInt($ruleToSet->getColumnNumber(), 'column number not set'); - self::assertGreaterThanOrEqual(0, $ruleToSet->getColumnNumber(), 'column number not valid'); - } - - /** - * @test - */ - public function setRulesWithRuleWithOnlyLineNumberPreservesLineNumber(): void - { - $ruleToSet = new Rule('color'); - $ruleToSet->setPosition(42); - - $this->subject->setRules([$ruleToSet]); - - self::assertSame(42, $ruleToSet->getLineNumber(), 'line number not preserved'); - } - - /** - * @test - */ - public function setRulesWithRuleWithOnlyColumnNumberSetsLineNumber(): void - { - $ruleToSet = new Rule('color'); - $ruleToSet->setPosition(null, 42); - - $this->subject->setRules([$ruleToSet]); - - self::assertIsInt($ruleToSet->getLineNumber(), 'line number not set'); - self::assertGreaterThanOrEqual(1, $ruleToSet->getLineNumber(), 'line number not valid'); - } - - /** - * @test - */ - public function setRulesWithRuleWithOnlyColumnNumberPreservesColumnNumber(): void - { - $ruleToSet = new Rule('color'); - $ruleToSet->setPosition(null, 42); - - $this->subject->setRules([$ruleToSet]); - - self::assertSame(42, $ruleToSet->getColumnNumber(), 'column number not preserved'); - } - - /** - * @test - */ - public function setRulesWithRuleWithCompletePositionPreservesPosition(): void - { - $ruleToSet = new Rule('color'); - $ruleToSet->setPosition(42, 64); - - $this->subject->setRules([$ruleToSet]); - - self::assertSame(42, $ruleToSet->getLineNumber(), 'line number not preserved'); - self::assertSame(64, $ruleToSet->getColumnNumber(), 'column number not preserved'); - } - - /** - * @test - * - * @param list $propertyNamesToSet - * - * @dataProvider providePropertyNames - */ - public function getRulesReturnsRulesSet(array $propertyNamesToSet): void - { - $rulesToSet = self::createRulesFromPropertyNames($propertyNamesToSet); - $this->subject->setRules($rulesToSet); - - $result = $this->subject->getRules(); - - self::assertSame($rulesToSet, $result); - } - - /** - * @test - */ - public function getRulesOrdersByLineNumber(): void - { - $first = (new Rule('color'))->setPosition(1, 64); - $second = (new Rule('display'))->setPosition(19, 42); - $third = (new Rule('color'))->setPosition(55, 11); - $this->subject->setRules([$third, $second, $first]); - - $result = $this->subject->getRules(); - - self::assertSame([$first, $second, $third], $result); - } - - /** - * @test - */ - public function getRulesOrdersRulesWithSameLineNumberByColumnNumber(): void - { - $first = (new Rule('color'))->setPosition(1, 11); - $second = (new Rule('display'))->setPosition(1, 42); - $third = (new Rule('color'))->setPosition(1, 64); - $this->subject->setRules([$third, $second, $first]); - - $result = $this->subject->getRules(); - - self::assertSame([$first, $second, $third], $result); - } - - /** - * @return array, 1: string, 2: list}> - */ - public static function providePropertyNamesAndSearchPatternAndMatchingPropertyNames(): array - { - return [ - 'single rule matched' => [ - ['color'], - 'color', - ['color'], - ], - 'first rule matched' => [ - ['color', 'display'], - 'color', - ['color'], - ], - 'last rule matched' => [ - ['color', 'display'], - 'display', - ['display'], - ], - 'middle rule matched' => [ - ['color', 'display', 'width'], - 'display', - ['display'], - ], - 'multiple rules for the same property matched' => [ - ['color', 'color'], - 'color', - ['color'], - ], - 'multiple rules for the same property matched in haystack' => [ - ['color', 'display', 'color', 'width'], - 'color', - ['color'], - ], - 'shorthand rule matched' => [ - ['font'], - 'font-', - ['font'], - ], - 'longhand rule matched' => [ - ['font-size'], - 'font-', - ['font-size'], - ], - 'shorthand and longhand rule matched' => [ - ['font', 'font-size'], - 'font-', - ['font', 'font-size'], - ], - 'shorthand rule matched in haystack' => [ - ['font', 'color'], - 'font-', - ['font'], - ], - 'longhand rule matched in haystack' => [ - ['font-size', 'color'], - 'font-', - ['font-size'], - ], - 'rules whose property names begin with the same characters not matched with pattern match' => [ - ['contain', 'container', 'container-type'], - 'contain-', - ['contain'], - ], - 'rules whose property names begin with the same characters not matched with exact match' => [ - ['contain', 'container', 'container-type'], - 'contain', - ['contain'], - ], - ]; - } - - /** - * @test - * - * @param list $propertyNamesToSet - * @param list $matchingPropertyNames - * - * @dataProvider providePropertyNamesAndSearchPatternAndMatchingPropertyNames - */ - public function getRulesWithPatternReturnsAllMatchingRules( - array $propertyNamesToSet, - string $searchPattern, - array $matchingPropertyNames - ): void { - $rulesToSet = self::createRulesFromPropertyNames($propertyNamesToSet); - // Use `array_values` to ensure canonical numeric array, since `array_filter` does not reindex. - $matchingRules = \array_values( - \array_filter( - $rulesToSet, - static function (Rule $rule) use ($matchingPropertyNames): bool { - return \in_array($rule->getRule(), $matchingPropertyNames, true); - } - ) - ); - $this->subject->setRules($rulesToSet); - - $result = $this->subject->getRules($searchPattern); - - // `Rule`s without pre-set positions are returned in the order set. This is tested separately. - self::assertSame($matchingRules, $result); - } - - /** - * @return array, 1: string}> - */ - public static function providePropertyNamesAndNonMatchingSearchPattern(): array - { - return [ - 'no match in empty list' => [ - [], - 'color', - ], - 'no match for different property' => [ - ['color'], - 'display', - ], - 'no match for property not in list' => [ - ['color', 'display'], - 'width', - ], - ]; - } - - /** - * @test - * - * @param list $propertyNamesToSet - * - * @dataProvider providePropertyNamesAndNonMatchingSearchPattern - */ - public function getRulesWithNonMatchingPatternReturnsEmptyArray( - array $propertyNamesToSet, - string $searchPattern - ): void { - $this->setRulesFromPropertyNames($propertyNamesToSet); - - $result = $this->subject->getRules($searchPattern); - - self::assertSame([], $result); - } - - /** - * @test - */ - public function getRulesWithPatternOrdersRulesByPosition(): void - { - $first = (new Rule('color'))->setPosition(1, 42); - $second = (new Rule('color'))->setPosition(1, 64); - $third = (new Rule('color'))->setPosition(55, 7); - $this->subject->setRules([$third, $second, $first]); - - $result = $this->subject->getRules('color'); - - self::assertSame([$first, $second, $third], $result); - } - - /** - * @return array}> - */ - public static function provideDistinctPropertyNames(): array - { - return [ - 'no properties' => [[]], - 'one property' => [['color']], - 'two properties' => [['color', 'display']], - ]; - } - - /** - * @test - * - * @param list $propertyNamesToSet - * - * @dataProvider provideDistinctPropertyNames - */ - public function getRulesAssocReturnsAllRulesWithDistinctPropertyNames(array $propertyNamesToSet): void - { - $rulesToSet = self::createRulesFromPropertyNames($propertyNamesToSet); - $this->subject->setRules($rulesToSet); - - $result = $this->subject->getRulesAssoc(); - - self::assertSame($rulesToSet, \array_values($result)); - } - - /** - * @test - */ - public function getRulesAssocReturnsLastRuleWithSamePropertyName(): void - { - $firstRule = new Rule('color'); - $lastRule = new Rule('color'); - $this->subject->setRules([$firstRule, $lastRule]); - - $result = $this->subject->getRulesAssoc(); - - self::assertSame([$lastRule], \array_values($result)); - } - - /** - * @test - */ - public function getRulesAssocOrdersRulesByPosition(): void - { - $first = (new Rule('color'))->setPosition(1, 42); - $second = (new Rule('display'))->setPosition(1, 64); - $third = (new Rule('width'))->setPosition(55, 7); - $this->subject->setRules([$third, $second, $first]); - - $result = $this->subject->getRulesAssoc(); - - self::assertSame([$first, $second, $third], \array_values($result)); - } - - /** - * @test - */ - public function getRulesAssocKeysRulesByPropertyName(): void - { - $this->subject->setRules([new Rule('color'), new Rule('display')]); - - $result = $this->subject->getRulesAssoc(); - - foreach ($result as $key => $rule) { - self::assertSame($rule->getRule(), $key); - } - } - - /** - * @test - * - * @param list $propertyNamesToSet - * @param list $matchingPropertyNames - * - * @dataProvider providePropertyNamesAndSearchPatternAndMatchingPropertyNames - */ - public function getRulesAssocWithPatternReturnsAllMatchingPropertyNames( - array $propertyNamesToSet, - string $searchPattern, - array $matchingPropertyNames - ): void { - $this->setRulesFromPropertyNames($propertyNamesToSet); - - $result = $this->subject->getRulesAssoc($searchPattern); - - $resultPropertyNames = \array_keys($result); - \sort($matchingPropertyNames); - \sort($resultPropertyNames); - self::assertSame($matchingPropertyNames, $resultPropertyNames); - } - - /** - * @test - * - * @param list $propertyNamesToSet - * - * @dataProvider providePropertyNamesAndNonMatchingSearchPattern - */ - public function getRulesAssocWithNonMatchingPatternReturnsEmptyArray( - array $propertyNamesToSet, - string $searchPattern - ): void { - $this->setRulesFromPropertyNames($propertyNamesToSet); - - $result = $this->subject->getRulesAssoc($searchPattern); - - self::assertSame([], $result); - } - - /** - * @test - */ - public function getRulesAssocWithPatternOrdersRulesByPosition(): void - { - $first = (new Rule('font'))->setPosition(1, 42); - $second = (new Rule('font-family'))->setPosition(1, 64); - $third = (new Rule('font-weight'))->setPosition(55, 7); - $this->subject->setRules([$third, $second, $first]); - - $result = $this->subject->getRules('font-'); - - self::assertSame([$first, $second, $third], \array_values($result)); - } - - /** - * @param list $propertyNames - */ - private function setRulesFromPropertyNames(array $propertyNames): void - { - $this->subject->setRules(self::createRulesFromPropertyNames($propertyNames)); - } - - /** - * @param list $propertyNames - * - * @return list - */ - private static function createRulesFromPropertyNames(array $propertyNames): array - { - return \array_map( - function (string $propertyName): Rule { - return new Rule($propertyName); - }, - $propertyNames - ); - } } From 11e634c0b44ebbf697f971c1343e0470d5f95adb Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sat, 28 Jun 2025 18:16:13 +0100 Subject: [PATCH 474/555] [BUGFIX] Allow comma in selectors (#1293) Also add a note that the specificity is incorrectly calculated in such cases. This will be addressed with a separate fix. --- CHANGELOG.md | 1 + src/Property/Selector.php | 6 +++--- tests/Unit/Property/SelectorTest.php | 3 +++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74a577d0..912d5dba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -110,6 +110,7 @@ Please also have a look at our ### Fixed +- Allow comma in selectors (e.g. `:not(html, body)`) (#1293) - Insert `Rule` before sibling even with different property name (in `RuleSet::addRule()`) (#1270) - Ensure `RuleSet::addRule()` sets non-negative column number when sibling diff --git a/src/Property/Selector.php b/src/Property/Selector.php index 180ffeba..daeed850 100644 --- a/src/Property/Selector.php +++ b/src/Property/Selector.php @@ -24,9 +24,9 @@ class Selector implements Renderable public const SELECTOR_VALIDATION_RX = '/ ^( (?: - [a-zA-Z0-9\\x{00A0}-\\x{FFFF}_^$|*="\'~\\[\\]()\\-\\s\\.:#+>]* # any sequence of valid unescaped characters - (?:\\\\.)? # a single escaped character - (?:([\'"]).*?(?,]* # any sequence of valid unescaped characters + (?:\\\\.)? # a single escaped character + (?:([\'"]).*?(? ['.help:hover', 20], 'ID' => ['#file', 100], 'ID and descendant class' => ['#test .help', 110], + '`not`' => [':not(#your-mug)', 100], + // TODO, broken: The specificity should be the highest of the `:not` arguments, not the sum. + '`not` with multiple arguments' => [':not(#your-mug, .their-mug)', 110], ]; } From 8e4519771017688101342efa00adf2f79735349d Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sat, 28 Jun 2025 19:50:23 +0100 Subject: [PATCH 475/555] [CLEANUP] Tidy up `DeclarationBlock::parse()` (#1294) - Assign the result of `ParserState::peek()` to a local variable, for efficiency; - Use a switch statement to branch on its value, for extensibility (e.g. #1292); - Don't unnecessarily test that a quote character is not escaped when not within a string. --- config/phpstan-baseline.neon | 6 ------ src/RuleSet/DeclarationBlock.php | 22 +++++++++++++++------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 9be2ebd7..8d882804 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -42,12 +42,6 @@ parameters: count: 1 path: ../src/Rule/Rule.php - - - message: '#^Loose comparison via "\!\=" is not allowed\.$#' - identifier: notEqual.notAllowed - count: 1 - path: ../src/RuleSet/DeclarationBlock.php - - message: '#^Parameters should have "string" types as the only types passed to this method$#' identifier: typePerfect.narrowPublicClassMethodParamType diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index e4125797..5280e5a1 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -41,17 +41,25 @@ public static function parse(ParserState $parserState, ?CSSList $list = null): ? $result = new DeclarationBlock($parserState->currentLine()); try { $selectorParts = []; + $stringWrapperCharacter = null; do { $selectorParts[] = $parserState->consume(1) . $parserState->consumeUntil(['{', '}', '\'', '"'], false, false, $comments); - if (\in_array($parserState->peek(), ['\'', '"'], true) && \substr(\end($selectorParts), -1) != '\\') { - if (!isset($stringWrapperCharacter)) { - $stringWrapperCharacter = $parserState->peek(); - } elseif ($stringWrapperCharacter === $parserState->peek()) { - unset($stringWrapperCharacter); - } + $nextCharacter = $parserState->peek(); + switch ($nextCharacter) { + case '\'': + // The fallthrough is intentional. + case '"': + if (!\is_string($stringWrapperCharacter)) { + $stringWrapperCharacter = $nextCharacter; + } elseif ($stringWrapperCharacter === $nextCharacter) { + if (\substr(\end($selectorParts), -1) !== '\\') { + $stringWrapperCharacter = null; + } + } + break; } - } while (!\in_array($parserState->peek(), ['{', '}'], true) || isset($stringWrapperCharacter)); + } while (!\in_array($nextCharacter, ['{', '}'], true) || \is_string($stringWrapperCharacter)); $result->setSelectors(\implode('', $selectorParts), $list); if ($parserState->comes('{')) { $parserState->consume(1); From 1dde3cb7d39ad44ab0332b71a9a38d82c8b2bb71 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sun, 29 Jun 2025 10:13:54 +0100 Subject: [PATCH 476/555] [TASK] Add `assertInstanceOf` tests for `DeclarationBlock` (#1295) --- tests/Unit/RuleSet/DeclarationBlockTest.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/Unit/RuleSet/DeclarationBlockTest.php b/tests/Unit/RuleSet/DeclarationBlockTest.php index b473da35..c84d1f9a 100644 --- a/tests/Unit/RuleSet/DeclarationBlockTest.php +++ b/tests/Unit/RuleSet/DeclarationBlockTest.php @@ -5,7 +5,9 @@ namespace Sabberworm\CSS\Tests\Unit\RuleSet; use PHPUnit\Framework\TestCase; +use Sabberworm\CSS\CSSElement; use Sabberworm\CSS\CSSList\CSSListItem; +use Sabberworm\CSS\Position\Positionable; use Sabberworm\CSS\RuleSet\DeclarationBlock; /** @@ -23,6 +25,14 @@ protected function setUp(): void $this->subject = new DeclarationBlock(); } + /** + * @test + */ + public function implementsCSSElement(): void + { + self::assertInstanceOf(CSSElement::class, $this->subject); + } + /** * @test */ @@ -30,4 +40,12 @@ public function implementsCSSListItem(): void { self::assertInstanceOf(CSSListItem::class, $this->subject); } + + /** + * @test + */ + public function implementsPositionable(): void + { + self::assertInstanceOf(Positionable::class, $this->subject); + } } From 1d8b85516cd46ce98392366beaa8481b9ee2b1ac Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sun, 29 Jun 2025 10:15:27 +0100 Subject: [PATCH 477/555] [BUGFIX] Correct an exception message (#1296) This correction is not worthy of a changelog entry or any tests. --- src/RuleSet/DeclarationBlock.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 5280e5a1..314bbae3 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -97,7 +97,7 @@ public function setSelectors($selectors, ?CSSList $list = null): void if (!Selector::isValid($selector)) { throw new UnexpectedTokenException( "Selector did not match '" . Selector::SELECTOR_VALIDATION_RX . "'.", - $selectors, + $selector, 'custom' ); } From 766238d8f5acf992a5d34f75e4337406314d157b Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 30 Jun 2025 04:04:07 +0200 Subject: [PATCH 478/555] [CLEANUP] Avoid spaces before colons in the Mermaid code (#1300) --- README.md | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 9ecdc3e7..b0ca81d0 100644 --- a/README.md +++ b/README.md @@ -781,28 +781,28 @@ classDiagram %% end of the generated part - Anchor --> "1" ParserState : parserState - CSSList --> "*" CSSList : contents - CSSList --> "*" Charset : contents - CSSList --> "*" Comment : comments - CSSList --> "*" Import : contents - CSSList --> "*" RuleSet : contents - CSSNamespace --> "*" Comment : comments - Charset --> "*" Comment : comments - Charset --> "1" CSSString : charset - DeclarationBlock --> "*" Selector : selectors - Import --> "*" Comment : comments - OutputFormat --> "1" OutputFormat : nextLevelFormat - OutputFormat --> "1" OutputFormatter : outputFormatter - OutputFormatter --> "1" OutputFormat : outputFormat - Parser --> "1" ParserState : parserState - ParserState --> "1" Settings : parserSettings - Rule --> "*" Comment : comments - Rule --> "1" RuleValueList : value - RuleSet --> "*" Comment : comments - RuleSet --> "*" Rule : rules - URL --> "1" CSSString : url - ValueList --> "*" Value : components + Anchor --> "1" ParserState: parserState + CSSList --> "*" CSSList: contents + CSSList --> "*" Charset: contents + CSSList --> "*" Comment: comments + CSSList --> "*" Import: contents + CSSList --> "*" RuleSet: contents + CSSNamespace --> "*" Comment: comments + Charset --> "*" Comment: comments + Charset --> "1" CSSString: charset + DeclarationBlock --> "*" Selector: selectors + Import --> "*" Comment: comments + OutputFormat --> "1" OutputFormat: nextLevelFormat + OutputFormat --> "1" OutputFormatter: outputFormatter + OutputFormatter --> "1" OutputFormat: outputFormat + Parser --> "1" ParserState: parserState + ParserState --> "1" Settings: parserSettings + Rule --> "*" Comment: comments + Rule --> "1" RuleValueList: value + RuleSet --> "*" Comment: comments + RuleSet --> "*" Rule: rules + URL --> "1" CSSString: url + ValueList --> "*" Value: components ``` ## API and deprecation policy From 1814dd73860d67706c37de95d477fe984051737c Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 30 Jun 2025 20:40:56 +0200 Subject: [PATCH 479/555] [CLEANUP] Order class names in the class diagram alphabetically (#1299) --- README.md | 104 +++++++++++++++++++++++++++--------------------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index b0ca81d0..c6373463 100644 --- a/README.md +++ b/README.md @@ -624,106 +624,106 @@ classDiagram %% Start of the part originally generated from the PHP code using tasuku43/mermaid-class-diagram - class CSSElement { - <> + class Anchor { } - class Renderable { + class AtRule { <> } - class Positionable { - <> + class AtRuleBlockList { } - class CSSListItem { - <> + class AtRuleSet { } - class RuleContainer { + class CSSBlockList { + <> + } + class CSSElement { <> } - class DeclarationBlock { + class CSSFunction { } - class RuleSet { + class CSSList { <> } - class AtRuleSet { + class CSSListItem { + <> } - class KeyframeSelector { + class CSSNamespace { } - class AtRule { - <> + class CSSString { + } + class CalcFunction { + } + class CalcRuleValueList { } class Charset { } - class Import { + class Color { } - class Selector { + class Comment { } - class CSSNamespace { + class Commentable { + <> } - class Settings { + class DeclarationBlock { } - class Rule { + class Document { } - class Parser { + class Import { } - class OutputFormatter { + class KeyFrame { } - class OutputFormat { + class KeyframeSelector { + } + class LineName { } class OutputException { } - class UnexpectedEOFException { + class OutputFormat { } - class SourceException { + class OutputFormatter { } - class UnexpectedTokenException { + class Parser { } class ParserState { } - class Anchor { - } - class CSSBlockList { - <> - } - class Document { + class Positionable { + <> } - class CSSList { + class PrimitiveValue { <> } - class KeyFrame { - } - class AtRuleBlockList { - } - class Color { + class Renderable { + <> } - class URL { + class Rule { } - class CalcRuleValueList { + class RuleContainer { + <> } - class ValueList { + class RuleSet { <> } - class CalcFunction { + class RuleValueList { } - class LineName { + class Selector { } - class Value { - <> + class Settings { } class Size { } - class CSSString { + class SourceException { } - class PrimitiveValue { - <> + class UnexpectedEOFException { } - class CSSFunction { + class UnexpectedTokenException { } - class RuleValueList { + class URL { } - class Commentable { - <> + class Value { + <> } - class Comment { + class ValueList { + <> } RuleSet <|-- DeclarationBlock: inheritance From 67d3d39326726e8d994af3d3875ae63af3c903ce Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 4 Jul 2025 14:52:34 +0200 Subject: [PATCH 480/555] [DOCS] Fix class list in class diagram (#1301) - adapt the sorting to how the diagram generator sorts - add `SpecificityCalculator` which had been missing --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c6373463..c7319601 100644 --- a/README.md +++ b/README.md @@ -713,12 +713,14 @@ classDiagram } class SourceException { } + class SpecificityCalculator { + } + class URL { + } class UnexpectedEOFException { } class UnexpectedTokenException { } - class URL { - } class Value { <> } From 8260bba5f5283623ca73493752995631a58b963d Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 4 Jul 2025 15:40:04 +0200 Subject: [PATCH 481/555] [DOCS] Remove cardinalities from the class diagram (#1303) This brings our class diagram closer to what the latest version of the diagram generator creates. --- README.md | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index c7319601..e9521723 100644 --- a/README.md +++ b/README.md @@ -783,28 +783,28 @@ classDiagram %% end of the generated part - Anchor --> "1" ParserState: parserState - CSSList --> "*" CSSList: contents - CSSList --> "*" Charset: contents - CSSList --> "*" Comment: comments - CSSList --> "*" Import: contents - CSSList --> "*" RuleSet: contents - CSSNamespace --> "*" Comment: comments - Charset --> "*" Comment: comments - Charset --> "1" CSSString: charset - DeclarationBlock --> "*" Selector: selectors - Import --> "*" Comment: comments - OutputFormat --> "1" OutputFormat: nextLevelFormat - OutputFormat --> "1" OutputFormatter: outputFormatter - OutputFormatter --> "1" OutputFormat: outputFormat - Parser --> "1" ParserState: parserState - ParserState --> "1" Settings: parserSettings - Rule --> "*" Comment: comments - Rule --> "1" RuleValueList: value - RuleSet --> "*" Comment: comments - RuleSet --> "*" Rule: rules - URL --> "1" CSSString: url - ValueList --> "*" Value: components + Anchor --> ParserState: parserState + CSSList --> CSSList: contents + CSSList --> Charset: contents + CSSList --> Comment: comments + CSSList --> Import: contents + CSSList --> RuleSet: contents + CSSNamespace --> Comment: comments + Charset --> Comment: comments + Charset --> CSSString: charset + DeclarationBlock --> Selector: selectors + Import --> Comment: comments + OutputFormat --> OutputFormat: nextLevelFormat + OutputFormat --> OutputFormatter: outputFormatter + OutputFormatter --> OutputFormat: outputFormat + Parser --> ParserState: parserState + ParserState --> Settings: parserSettings + Rule --> Comment: comments + Rule --> RuleValueList: value + RuleSet --> Comment: comments + RuleSet --> Rule: rules + URL --> CSSString: url + ValueList --> Value: components ``` ## API and deprecation policy From b95811ff7edaf4db5df7e17a88c6930d4e575642 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 4 Jul 2025 15:57:55 +0200 Subject: [PATCH 482/555] [DOCS] Make the class diagram markers generator-friendly (#1302) This is in preparation for adding a script for generating the class diagram in #1297. --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index e9521723..999bd5cd 100644 --- a/README.md +++ b/README.md @@ -619,11 +619,10 @@ class Sabberworm\CSS\CSSList\Document#4 (2) { ## Class diagram ```mermaid + %% start of the generated part classDiagram direction LR - %% Start of the part originally generated from the PHP code using tasuku43/mermaid-class-diagram - class Anchor { } class AtRule { @@ -779,7 +778,6 @@ classDiagram ValueList <|-- RuleValueList: inheritance Renderable <|.. Comment: realization Positionable <|.. Comment: realization - %% end of the generated part From 63b22be4c03e2ff6cf32f7e460236fc26513dd89 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 4 Jul 2025 15:59:48 +0200 Subject: [PATCH 483/555] [DOCS] Change some arrow types in the class diagram (#1304) --- README.md | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 999bd5cd..dc4ceb5f 100644 --- a/README.md +++ b/README.md @@ -781,28 +781,28 @@ classDiagram %% end of the generated part - Anchor --> ParserState: parserState - CSSList --> CSSList: contents - CSSList --> Charset: contents - CSSList --> Comment: comments - CSSList --> Import: contents - CSSList --> RuleSet: contents - CSSNamespace --> Comment: comments - Charset --> Comment: comments - Charset --> CSSString: charset - DeclarationBlock --> Selector: selectors - Import --> Comment: comments - OutputFormat --> OutputFormat: nextLevelFormat - OutputFormat --> OutputFormatter: outputFormatter - OutputFormatter --> OutputFormat: outputFormat - Parser --> ParserState: parserState - ParserState --> Settings: parserSettings - Rule --> Comment: comments - Rule --> RuleValueList: value - RuleSet --> Comment: comments - RuleSet --> Rule: rules - URL --> CSSString: url - ValueList --> Value: components + Anchor ..> ParserState: parserState + CSSList ..> CSSList: contents + CSSList ..> Charset: contents + CSSList ..> Comment: comments + CSSList ..> Import: contents + CSSList ..> RuleSet: contents + CSSNamespace ..> Comment: comments + Charset ..> Comment: comments + Charset ..> CSSString: charset + DeclarationBlock ..> Selector: selectors + Import ..> Comment: comments + OutputFormat ..> OutputFormat: nextLevelFormat + OutputFormat ..> OutputFormatter: outputFormatter + OutputFormatter ..> OutputFormat: outputFormat + Parser ..> ParserState: parserState + ParserState ..> Settings: parserSettings + Rule ..> Comment: comments + Rule ..> RuleValueList: value + RuleSet ..> Comment: comments + RuleSet ..> Rule: rules + URL ..> CSSString: url + ValueList ..> Value: components ``` ## API and deprecation policy From 674cfa00cb1d0c060acba4722651adcf98cfc838 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 4 Jul 2025 16:32:21 +0200 Subject: [PATCH 484/555] [DOCS] Label dependencies in the class diagram as such (#1305) This corresponds to what the latest version of our diagram generator does. --- README.md | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index dc4ceb5f..69f0acff 100644 --- a/README.md +++ b/README.md @@ -781,28 +781,28 @@ classDiagram %% end of the generated part - Anchor ..> ParserState: parserState - CSSList ..> CSSList: contents - CSSList ..> Charset: contents - CSSList ..> Comment: comments - CSSList ..> Import: contents - CSSList ..> RuleSet: contents - CSSNamespace ..> Comment: comments - Charset ..> Comment: comments - Charset ..> CSSString: charset - DeclarationBlock ..> Selector: selectors - Import ..> Comment: comments - OutputFormat ..> OutputFormat: nextLevelFormat - OutputFormat ..> OutputFormatter: outputFormatter - OutputFormatter ..> OutputFormat: outputFormat - Parser ..> ParserState: parserState - ParserState ..> Settings: parserSettings - Rule ..> Comment: comments - Rule ..> RuleValueList: value - RuleSet ..> Comment: comments - RuleSet ..> Rule: rules - URL ..> CSSString: url - ValueList ..> Value: components + Anchor ..> ParserState: dependency + CSSList ..> CSSList: dependency + CSSList ..> Charset: dependency + CSSList ..> Comment: dependency + CSSList ..> Import: dependency + CSSList ..> RuleSet: dependency + CSSNamespace ..> Comment: dependency + Charset ..> Comment: dependency + Charset ..> CSSString: dependency + DeclarationBlock ..> Selector: dependency + Import ..> Comment: dependency + OutputFormat ..> OutputFormat: dependency + OutputFormat ..> OutputFormatter: dependency + OutputFormatter ..> OutputFormat: dependency + Parser ..> ParserState: dependency + ParserState ..> Settings: dependency + Rule ..> Comment: dependency + Rule ..> RuleValueList: dependency + RuleSet ..> Comment: dependency + RuleSet ..> Rule: dependency + URL ..> CSSString: dependency + ValueList ..> Value: dependency ``` ## API and deprecation policy From e69e8ca31f9c996da29df1b6e166cb9c0f73428c Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 7 Jul 2025 14:29:50 +0200 Subject: [PATCH 485/555] [DOCS] Temporarily drop some markers from the class diagram (#1307) As long as we manually edit the source of the class diagram, having markers for a part being autogenerated does not make sense. --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 69f0acff..727a6e1b 100644 --- a/README.md +++ b/README.md @@ -619,7 +619,6 @@ class Sabberworm\CSS\CSSList\Document#4 (2) { ## Class diagram ```mermaid - %% start of the generated part classDiagram direction LR @@ -778,9 +777,6 @@ classDiagram ValueList <|-- RuleValueList: inheritance Renderable <|.. Comment: realization Positionable <|.. Comment: realization - %% end of the generated part - - Anchor ..> ParserState: dependency CSSList ..> CSSList: dependency CSSList ..> Charset: dependency From f035185c4d4aa3a3652c19694814fdd004f9af64 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 7 Jul 2025 15:48:04 +0200 Subject: [PATCH 486/555] [DOCS] Reorder some lines in the class diagram (part 1) (#1306) This is in preparation for #1298. --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 727a6e1b..0c2dd8de 100644 --- a/README.md +++ b/README.md @@ -726,6 +726,12 @@ classDiagram <> } + Anchor ..> ParserState: dependency + CSSListItem <|-- AtRule: inheritance + AtRule <|.. AtRuleSet: realization + CSSBlockList <|-- AtRuleBlockList: inheritance + AtRule <|.. AtRuleBlockList: realization + RuleSet <|-- DeclarationBlock: inheritance Renderable <|-- CSSElement: inheritance Renderable <|-- CSSListItem: inheritance @@ -735,10 +741,8 @@ classDiagram CSSListItem <|.. RuleSet: realization RuleContainer <|.. RuleSet: realization RuleSet <|-- AtRuleSet: inheritance - AtRule <|.. AtRuleSet: realization Renderable <|.. Selector: realization Selector <|-- KeyframeSelector: inheritance - CSSListItem <|-- AtRule: inheritance Positionable <|.. Charset: realization AtRule <|.. Charset: realization Positionable <|.. Import: realization @@ -760,8 +764,6 @@ classDiagram CSSListItem <|.. CSSList: realization CSSList <|-- KeyFrame: inheritance AtRule <|.. KeyFrame: realization - CSSBlockList <|-- AtRuleBlockList: inheritance - AtRule <|.. AtRuleBlockList: realization CSSFunction <|-- Color: inheritance PrimitiveValue <|-- URL: inheritance RuleValueList <|-- CalcRuleValueList: inheritance @@ -777,7 +779,6 @@ classDiagram ValueList <|-- RuleValueList: inheritance Renderable <|.. Comment: realization Positionable <|.. Comment: realization - Anchor ..> ParserState: dependency CSSList ..> CSSList: dependency CSSList ..> Charset: dependency CSSList ..> Comment: dependency From ab9fa7ffa250f80a45941f0f5d52212113ca74db Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 7 Jul 2025 16:25:55 +0200 Subject: [PATCH 487/555] [DOCS] Reorder some lines in the class diagram (part 2) (#1308) This is in preparation for #1298. --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0c2dd8de..4e193003 100644 --- a/README.md +++ b/README.md @@ -728,19 +728,20 @@ classDiagram Anchor ..> ParserState: dependency CSSListItem <|-- AtRule: inheritance - AtRule <|.. AtRuleSet: realization - CSSBlockList <|-- AtRuleBlockList: inheritance AtRule <|.. AtRuleBlockList: realization + CSSBlockList <|-- AtRuleBlockList: inheritance + AtRule <|.. AtRuleSet: realization + RuleSet <|-- AtRuleSet: inheritance + CSSList <|-- CSSBlockList: inheritance + Renderable <|-- CSSElement: inheritance RuleSet <|-- DeclarationBlock: inheritance - Renderable <|-- CSSElement: inheritance Renderable <|-- CSSListItem: inheritance Commentable <|-- CSSListItem: inheritance Positionable <|.. RuleSet: realization CSSElement <|.. RuleSet: realization CSSListItem <|.. RuleSet: realization RuleContainer <|.. RuleSet: realization - RuleSet <|-- AtRuleSet: inheritance Renderable <|.. Selector: realization Selector <|-- KeyframeSelector: inheritance Positionable <|.. Charset: realization @@ -757,7 +758,6 @@ classDiagram Exception <|-- SourceException: inheritance Positionable <|.. SourceException: realization SourceException <|-- UnexpectedTokenException: inheritance - CSSList <|-- CSSBlockList: inheritance CSSBlockList <|-- Document: inheritance CSSElement <|.. CSSList: realization Positionable <|.. CSSList: realization From d8f7d4a1c8c7d23a92825f990aae1e0325f5f0a9 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 7 Jul 2025 16:37:01 +0200 Subject: [PATCH 488/555] [DOCS] Reorder some lines in the class diagram (part 3) (#1309) This is in preparation for #1298. --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4e193003..a16e57c6 100644 --- a/README.md +++ b/README.md @@ -734,6 +734,11 @@ classDiagram RuleSet <|-- AtRuleSet: inheritance CSSList <|-- CSSBlockList: inheritance Renderable <|-- CSSElement: inheritance + ValueList <|-- CSSFunction: inheritance + CSSElement <|.. CSSList: realization + CSSListItem <|.. CSSList: realization + CSSList ..> Charset: dependency + CSSList ..> Import: dependency RuleSet <|-- DeclarationBlock: inheritance Renderable <|-- CSSListItem: inheritance @@ -759,9 +764,7 @@ classDiagram Positionable <|.. SourceException: realization SourceException <|-- UnexpectedTokenException: inheritance CSSBlockList <|-- Document: inheritance - CSSElement <|.. CSSList: realization Positionable <|.. CSSList: realization - CSSListItem <|.. CSSList: realization CSSList <|-- KeyFrame: inheritance AtRule <|.. KeyFrame: realization CSSFunction <|-- Color: inheritance @@ -775,14 +778,11 @@ classDiagram PrimitiveValue <|-- Size: inheritance PrimitiveValue <|-- CSSString: inheritance Value <|-- PrimitiveValue: inheritance - ValueList <|-- CSSFunction: inheritance ValueList <|-- RuleValueList: inheritance Renderable <|.. Comment: realization Positionable <|.. Comment: realization CSSList ..> CSSList: dependency - CSSList ..> Charset: dependency CSSList ..> Comment: dependency - CSSList ..> Import: dependency CSSList ..> RuleSet: dependency CSSNamespace ..> Comment: dependency Charset ..> Comment: dependency From 12567dc89c630e51ea0a5a341c58366d3a561012 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 7 Jul 2025 16:48:56 +0200 Subject: [PATCH 489/555] [DOCS] Reorder some lines in the class diagram (part 4) (#1310) This is in preparation for #1298. --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a16e57c6..095a0def 100644 --- a/README.md +++ b/README.md @@ -739,10 +739,13 @@ classDiagram CSSListItem <|.. CSSList: realization CSSList ..> Charset: dependency CSSList ..> Import: dependency + Positionable <|.. CSSList: realization + Commentable <|-- CSSListItem: inheritance + Renderable <|-- CSSListItem: inheritance + AtRule <|.. CSSNamespace: realization + Positionable <|.. CSSNamespace: realization RuleSet <|-- DeclarationBlock: inheritance - Renderable <|-- CSSListItem: inheritance - Commentable <|-- CSSListItem: inheritance Positionable <|.. RuleSet: realization CSSElement <|.. RuleSet: realization CSSListItem <|.. RuleSet: realization @@ -753,8 +756,6 @@ classDiagram AtRule <|.. Charset: realization Positionable <|.. Import: realization AtRule <|.. Import: realization - Positionable <|.. CSSNamespace: realization - AtRule <|.. CSSNamespace: realization CSSElement <|.. Rule: realization Positionable <|.. Rule: realization Commentable <|.. Rule: realization @@ -764,7 +765,6 @@ classDiagram Positionable <|.. SourceException: realization SourceException <|-- UnexpectedTokenException: inheritance CSSBlockList <|-- Document: inheritance - Positionable <|.. CSSList: realization CSSList <|-- KeyFrame: inheritance AtRule <|.. KeyFrame: realization CSSFunction <|-- Color: inheritance From 6a784a41fc2bd8b945fde8fb4998bb1ae978a30c Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 7 Jul 2025 17:12:10 +0200 Subject: [PATCH 490/555] [DOCS] Reorder some lines in the class diagram (part 5) (#1311) This is in preparation for #1298. --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 095a0def..ede47562 100644 --- a/README.md +++ b/README.md @@ -744,6 +744,11 @@ classDiagram Renderable <|-- CSSListItem: inheritance AtRule <|.. CSSNamespace: realization Positionable <|.. CSSNamespace: realization + PrimitiveValue <|-- CSSString: inheritance + CSSFunction <|-- CalcFunction: inheritance + RuleValueList <|-- CalcRuleValueList: inheritance + AtRule <|.. Charset: realization + Charset ..> CSSString: dependency RuleSet <|-- DeclarationBlock: inheritance Positionable <|.. RuleSet: realization @@ -753,7 +758,6 @@ classDiagram Renderable <|.. Selector: realization Selector <|-- KeyframeSelector: inheritance Positionable <|.. Charset: realization - AtRule <|.. Charset: realization Positionable <|.. Import: realization AtRule <|.. Import: realization CSSElement <|.. Rule: realization @@ -769,14 +773,11 @@ classDiagram AtRule <|.. KeyFrame: realization CSSFunction <|-- Color: inheritance PrimitiveValue <|-- URL: inheritance - RuleValueList <|-- CalcRuleValueList: inheritance Value <|-- ValueList: inheritance - CSSFunction <|-- CalcFunction: inheritance ValueList <|-- LineName: inheritance CSSElement <|.. Value: realization Positionable <|.. Value: realization PrimitiveValue <|-- Size: inheritance - PrimitiveValue <|-- CSSString: inheritance Value <|-- PrimitiveValue: inheritance ValueList <|-- RuleValueList: inheritance Renderable <|.. Comment: realization @@ -786,7 +787,6 @@ classDiagram CSSList ..> RuleSet: dependency CSSNamespace ..> Comment: dependency Charset ..> Comment: dependency - Charset ..> CSSString: dependency DeclarationBlock ..> Selector: dependency Import ..> Comment: dependency OutputFormat ..> OutputFormat: dependency From d270c2c1eee714c6a67a41eaa3e9778a86fd23c5 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 7 Jul 2025 17:19:32 +0200 Subject: [PATCH 491/555] [DOCS] Reorder some lines in the class diagram (part 6) (#1312) This is in preparation for #1298. --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ede47562..35f06a50 100644 --- a/README.md +++ b/README.md @@ -749,15 +749,19 @@ classDiagram RuleValueList <|-- CalcRuleValueList: inheritance AtRule <|.. Charset: realization Charset ..> CSSString: dependency - + Positionable <|.. Charset: realization + CSSFunction <|-- Color: inheritance + Positionable <|.. Comment: realization + Renderable <|.. Comment: realization RuleSet <|-- DeclarationBlock: inheritance + DeclarationBlock ..> Selector: dependency + Positionable <|.. RuleSet: realization CSSElement <|.. RuleSet: realization CSSListItem <|.. RuleSet: realization RuleContainer <|.. RuleSet: realization Renderable <|.. Selector: realization Selector <|-- KeyframeSelector: inheritance - Positionable <|.. Charset: realization Positionable <|.. Import: realization AtRule <|.. Import: realization CSSElement <|.. Rule: realization @@ -771,7 +775,6 @@ classDiagram CSSBlockList <|-- Document: inheritance CSSList <|-- KeyFrame: inheritance AtRule <|.. KeyFrame: realization - CSSFunction <|-- Color: inheritance PrimitiveValue <|-- URL: inheritance Value <|-- ValueList: inheritance ValueList <|-- LineName: inheritance @@ -780,14 +783,11 @@ classDiagram PrimitiveValue <|-- Size: inheritance Value <|-- PrimitiveValue: inheritance ValueList <|-- RuleValueList: inheritance - Renderable <|.. Comment: realization - Positionable <|.. Comment: realization CSSList ..> CSSList: dependency CSSList ..> Comment: dependency CSSList ..> RuleSet: dependency CSSNamespace ..> Comment: dependency Charset ..> Comment: dependency - DeclarationBlock ..> Selector: dependency Import ..> Comment: dependency OutputFormat ..> OutputFormat: dependency OutputFormat ..> OutputFormatter: dependency From d250c86c38b1d320dbfa69f9344f2fd82c2cd8be Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 7 Jul 2025 17:53:42 +0200 Subject: [PATCH 492/555] [DOCS] Reorder some lines in the class diagram (part 7) (#1313) This is in preparation for #1298. --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 35f06a50..397e3211 100644 --- a/README.md +++ b/README.md @@ -755,6 +755,11 @@ classDiagram Renderable <|.. Comment: realization RuleSet <|-- DeclarationBlock: inheritance DeclarationBlock ..> Selector: dependency + CSSBlockList <|-- Document: inheritance + AtRule <|.. Import: realization + Positionable <|.. Import: realization + AtRule <|.. KeyFrame: realization + CSSList <|-- KeyFrame: inheritance Positionable <|.. RuleSet: realization CSSElement <|.. RuleSet: realization @@ -762,8 +767,6 @@ classDiagram RuleContainer <|.. RuleSet: realization Renderable <|.. Selector: realization Selector <|-- KeyframeSelector: inheritance - Positionable <|.. Import: realization - AtRule <|.. Import: realization CSSElement <|.. Rule: realization Positionable <|.. Rule: realization Commentable <|.. Rule: realization @@ -772,9 +775,6 @@ classDiagram Exception <|-- SourceException: inheritance Positionable <|.. SourceException: realization SourceException <|-- UnexpectedTokenException: inheritance - CSSBlockList <|-- Document: inheritance - CSSList <|-- KeyFrame: inheritance - AtRule <|.. KeyFrame: realization PrimitiveValue <|-- URL: inheritance Value <|-- ValueList: inheritance ValueList <|-- LineName: inheritance From f6f529d82a66daf4a7b5c74335a24169c1782a1d Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 7 Jul 2025 19:19:08 +0200 Subject: [PATCH 493/555] [DOCS] Reorder some lines in the class diagram (part 8) (#1314) This is in preparation for #1298. --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 397e3211..bcf62319 100644 --- a/README.md +++ b/README.md @@ -760,24 +760,26 @@ classDiagram Positionable <|.. Import: realization AtRule <|.. KeyFrame: realization CSSList <|-- KeyFrame: inheritance + Selector <|-- KeyframeSelector: inheritance + ValueList <|-- LineName: inheritance + SourceException <|-- OutputException: inheritance + OutputFormat ..> OutputFormatter: dependency + OutputFormatter ..> OutputFormat: dependency Positionable <|.. RuleSet: realization CSSElement <|.. RuleSet: realization CSSListItem <|.. RuleSet: realization RuleContainer <|.. RuleSet: realization Renderable <|.. Selector: realization - Selector <|-- KeyframeSelector: inheritance CSSElement <|.. Rule: realization Positionable <|.. Rule: realization Commentable <|.. Rule: realization - SourceException <|-- OutputException: inheritance UnexpectedTokenException <|-- UnexpectedEOFException: inheritance Exception <|-- SourceException: inheritance Positionable <|.. SourceException: realization SourceException <|-- UnexpectedTokenException: inheritance PrimitiveValue <|-- URL: inheritance Value <|-- ValueList: inheritance - ValueList <|-- LineName: inheritance CSSElement <|.. Value: realization Positionable <|.. Value: realization PrimitiveValue <|-- Size: inheritance @@ -790,8 +792,6 @@ classDiagram Charset ..> Comment: dependency Import ..> Comment: dependency OutputFormat ..> OutputFormat: dependency - OutputFormat ..> OutputFormatter: dependency - OutputFormatter ..> OutputFormat: dependency Parser ..> ParserState: dependency ParserState ..> Settings: dependency Rule ..> Comment: dependency From 348b395afacf068edf7111dac2120969ed54419d Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 7 Jul 2025 20:01:12 +0200 Subject: [PATCH 494/555] [DOCS] Reorder some lines in the class diagram (part 9) (#1315) This is in preparation for #1298. --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index bcf62319..880c68de 100644 --- a/README.md +++ b/README.md @@ -765,15 +765,18 @@ classDiagram SourceException <|-- OutputException: inheritance OutputFormat ..> OutputFormatter: dependency OutputFormatter ..> OutputFormat: dependency + Parser ..> ParserState: dependency + ParserState ..> Settings: dependency + Value <|-- PrimitiveValue: inheritance + CSSElement <|.. Rule: realization + Commentable <|.. Rule: realization Positionable <|.. RuleSet: realization CSSElement <|.. RuleSet: realization CSSListItem <|.. RuleSet: realization RuleContainer <|.. RuleSet: realization Renderable <|.. Selector: realization - CSSElement <|.. Rule: realization Positionable <|.. Rule: realization - Commentable <|.. Rule: realization UnexpectedTokenException <|-- UnexpectedEOFException: inheritance Exception <|-- SourceException: inheritance Positionable <|.. SourceException: realization @@ -783,7 +786,6 @@ classDiagram CSSElement <|.. Value: realization Positionable <|.. Value: realization PrimitiveValue <|-- Size: inheritance - Value <|-- PrimitiveValue: inheritance ValueList <|-- RuleValueList: inheritance CSSList ..> CSSList: dependency CSSList ..> Comment: dependency @@ -792,8 +794,6 @@ classDiagram Charset ..> Comment: dependency Import ..> Comment: dependency OutputFormat ..> OutputFormat: dependency - Parser ..> ParserState: dependency - ParserState ..> Settings: dependency Rule ..> Comment: dependency Rule ..> RuleValueList: dependency RuleSet ..> Comment: dependency From a155d0446c967b887d6224b25d779e0ffed8238f Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 7 Jul 2025 20:15:51 +0200 Subject: [PATCH 495/555] [DOCS] Reorder some lines in the class diagram (part 10) (#1316) This is in preparation for #1298. --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 880c68de..dda957b8 100644 --- a/README.md +++ b/README.md @@ -770,13 +770,15 @@ classDiagram Value <|-- PrimitiveValue: inheritance CSSElement <|.. Rule: realization Commentable <|.. Rule: realization - - Positionable <|.. RuleSet: realization + Positionable <|.. Rule: realization + Rule ..> RuleValueList: dependency CSSElement <|.. RuleSet: realization CSSListItem <|.. RuleSet: realization + Positionable <|.. RuleSet: realization + RuleSet ..> Rule: dependency RuleContainer <|.. RuleSet: realization + Renderable <|.. Selector: realization - Positionable <|.. Rule: realization UnexpectedTokenException <|-- UnexpectedEOFException: inheritance Exception <|-- SourceException: inheritance Positionable <|.. SourceException: realization @@ -795,9 +797,7 @@ classDiagram Import ..> Comment: dependency OutputFormat ..> OutputFormat: dependency Rule ..> Comment: dependency - Rule ..> RuleValueList: dependency RuleSet ..> Comment: dependency - RuleSet ..> Rule: dependency URL ..> CSSString: dependency ValueList ..> Value: dependency ``` From 45ae185b96259978c3a68c60bcc0a3d055c26eaa Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 7 Jul 2025 22:45:10 +0200 Subject: [PATCH 496/555] [DOCS] Reorder some lines in the class diagram (part 11) (#1317) This is in preparation for #1298. --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index dda957b8..9161dc3c 100644 --- a/README.md +++ b/README.md @@ -777,18 +777,19 @@ classDiagram Positionable <|.. RuleSet: realization RuleSet ..> Rule: dependency RuleContainer <|.. RuleSet: realization - + ValueList <|-- RuleValueList: inheritance Renderable <|.. Selector: realization - UnexpectedTokenException <|-- UnexpectedEOFException: inheritance + PrimitiveValue <|-- Size: inheritance Exception <|-- SourceException: inheritance Positionable <|.. SourceException: realization + URL ..> CSSString: dependency + + UnexpectedTokenException <|-- UnexpectedEOFException: inheritance SourceException <|-- UnexpectedTokenException: inheritance PrimitiveValue <|-- URL: inheritance Value <|-- ValueList: inheritance CSSElement <|.. Value: realization Positionable <|.. Value: realization - PrimitiveValue <|-- Size: inheritance - ValueList <|-- RuleValueList: inheritance CSSList ..> CSSList: dependency CSSList ..> Comment: dependency CSSList ..> RuleSet: dependency @@ -798,7 +799,6 @@ classDiagram OutputFormat ..> OutputFormat: dependency Rule ..> Comment: dependency RuleSet ..> Comment: dependency - URL ..> CSSString: dependency ValueList ..> Value: dependency ``` From 18152fb32859aa50a297e1b77f128c9d7c785ce5 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 7 Jul 2025 23:05:35 +0200 Subject: [PATCH 497/555] [DOCS] Reorder some lines in the class diagram (part 12) (#1318) This is in preparation for #1298. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9161dc3c..1ffdabf6 100644 --- a/README.md +++ b/README.md @@ -783,13 +783,13 @@ classDiagram Exception <|-- SourceException: inheritance Positionable <|.. SourceException: realization URL ..> CSSString: dependency - + PrimitiveValue <|-- URL: inheritance UnexpectedTokenException <|-- UnexpectedEOFException: inheritance SourceException <|-- UnexpectedTokenException: inheritance - PrimitiveValue <|-- URL: inheritance - Value <|-- ValueList: inheritance CSSElement <|.. Value: realization Positionable <|.. Value: realization + + Value <|-- ValueList: inheritance CSSList ..> CSSList: dependency CSSList ..> Comment: dependency CSSList ..> RuleSet: dependency From 6dec68e38d1978942093fe6840eee4548cc52693 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 7 Jul 2025 23:13:44 +0200 Subject: [PATCH 498/555] [DOCS] Reorder some lines in the class diagram (part 13) (#1319) This is in preparation for #1298. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1ffdabf6..29253a34 100644 --- a/README.md +++ b/README.md @@ -788,8 +788,8 @@ classDiagram SourceException <|-- UnexpectedTokenException: inheritance CSSElement <|.. Value: realization Positionable <|.. Value: realization - Value <|-- ValueList: inheritance + CSSList ..> CSSList: dependency CSSList ..> Comment: dependency CSSList ..> RuleSet: dependency From 7bfae2b0c0d7ea236d7f15409409602dcd5f11be Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Wed, 9 Jul 2025 19:55:30 +0100 Subject: [PATCH 499/555] [CLEANUP] Use static variable for stop characters (#1321) ... in `DeclarationBlock::parse()`. This improves readability in preparation for #1292 which will extend the list. (It may also slightly improve performance.) --- src/RuleSet/DeclarationBlock.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 314bbae3..bd295dd2 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -42,9 +42,10 @@ public static function parse(ParserState $parserState, ?CSSList $list = null): ? try { $selectorParts = []; $stringWrapperCharacter = null; + static $stopCharacters = ['{', '}', '\'', '"']; do { $selectorParts[] = $parserState->consume(1) - . $parserState->consumeUntil(['{', '}', '\'', '"'], false, false, $comments); + . $parserState->consumeUntil($stopCharacters, false, false, $comments); $nextCharacter = $parserState->peek(); switch ($nextCharacter) { case '\'': From 595a47b75d591ab28592722334b0d06625dedd48 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Wed, 9 Jul 2025 23:26:22 +0100 Subject: [PATCH 500/555] [TASK] Add unit tests for selector parsing (#1322) The tests broadly cover what currently works, and will be extended to cover the fixes in #1292. --- tests/Unit/RuleSet/DeclarationBlockTest.php | 78 +++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/tests/Unit/RuleSet/DeclarationBlockTest.php b/tests/Unit/RuleSet/DeclarationBlockTest.php index c84d1f9a..697b15aa 100644 --- a/tests/Unit/RuleSet/DeclarationBlockTest.php +++ b/tests/Unit/RuleSet/DeclarationBlockTest.php @@ -8,7 +8,11 @@ use Sabberworm\CSS\CSSElement; use Sabberworm\CSS\CSSList\CSSListItem; use Sabberworm\CSS\Position\Positionable; +use Sabberworm\CSS\Parsing\ParserState; +use Sabberworm\CSS\Property\Selector; use Sabberworm\CSS\RuleSet\DeclarationBlock; +use Sabberworm\CSS\Settings; +use TRegx\PhpUnit\DataProviders\DataProvider; /** * @covers \Sabberworm\CSS\RuleSet\DeclarationBlock @@ -48,4 +52,78 @@ public function implementsPositionable(): void { self::assertInstanceOf(Positionable::class, $this->subject); } + + /** + * @return array + */ + public static function provideSelector(): array + { + return [ + 'type' => ['body'], + 'class' => ['.teapot'], + 'type & class' => ['img.teapot'], + 'id' => ['#my-mug'], + 'type & id' => ['h2#my-mug'], + 'pseudo-class' => [':hover'], + 'type & pseudo-class' => ['a:hover'], + '`not`' => [':not(#your-mug)'], + 'pseudo-element' => ['::before'], + 'attribute with `"`' => ['[alt="{}()[]\\"\'"]'], + 'attribute with `\'`' => ['[alt=\'{}()[]"\\\'\']'], + ]; + } + + /** + * @test + * + * @param non-empty-string $selector + * + * @dataProvider provideSelector + */ + public function parsesSingleSelector(string $selector): void + { + $subject = DeclarationBlock::parse(new ParserState($selector . ' {}', Settings::create())); + + self::assertInstanceOf(DeclarationBlock::class, $subject); + self::assertSame([$selector], self::getSelectorsAsStrings($subject)); + } + + /** + * @return DataProvider + */ + public static function provideTwoSelectors(): DataProvider + { + return DataProvider::cross(self::provideSelector(), self::provideSelector()); + } + + /** + * @test + * + * @param non-empty-string $firstSelector + * @param non-empty-string $secondSelector + * + * @dataProvider provideTwoSelectors + */ + public function parsesTwoCommaSeparatedSelectors(string $firstSelector, string $secondSelector): void + { + $joinedSelectors = $firstSelector . ', ' . $secondSelector; + + $subject = DeclarationBlock::parse(new ParserState($joinedSelectors . ' {}', Settings::create())); + + self::assertInstanceOf(DeclarationBlock::class, $subject); + self::assertSame([$firstSelector, $secondSelector], self::getSelectorsAsStrings($subject)); + } + + /** + * @return array + */ + private static function getSelectorsAsStrings(DeclarationBlock $declarationBlock): array + { + return \array_map( + static function (Selector $selectorObject): string { + return $selectorObject->getSelector(); + }, + $declarationBlock->getSelectors() + ); + } } From 931c4061376059bc63d86cf5f76b83281866e2d3 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Thu, 10 Jul 2025 08:35:48 +0100 Subject: [PATCH 501/555] [BUGFIX] Allow comma in quoted string in selector (#1323) Split by commas during parsing, not after. --- CHANGELOG.md | 1 + src/RuleSet/DeclarationBlock.php | 22 +++++++++++++++++---- tests/Unit/RuleSet/DeclarationBlockTest.php | 4 ++-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 912d5dba..08dfef29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -110,6 +110,7 @@ Please also have a look at our ### Fixed +- Parse quoted attribute selector value containing comma (#1323) - Allow comma in selectors (e.g. `:not(html, body)`) (#1293) - Insert `Rule` before sibling even with different property name (in `RuleSet::addRule()`) (#1270) diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index bd295dd2..d0b9654c 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -40,13 +40,18 @@ public static function parse(ParserState $parserState, ?CSSList $list = null): ? $comments = []; $result = new DeclarationBlock($parserState->currentLine()); try { + $selectors = []; $selectorParts = []; $stringWrapperCharacter = null; - static $stopCharacters = ['{', '}', '\'', '"']; + $consumedNextCharacter = false; + static $stopCharacters = ['{', '}', '\'', '"', ',']; do { - $selectorParts[] = $parserState->consume(1) - . $parserState->consumeUntil($stopCharacters, false, false, $comments); + if (!$consumedNextCharacter) { + $selectorParts[] = $parserState->consume(1); + } + $selectorParts[] = $parserState->consumeUntil($stopCharacters, false, false, $comments); $nextCharacter = $parserState->peek(); + $consumedNextCharacter = false; switch ($nextCharacter) { case '\'': // The fallthrough is intentional. @@ -59,9 +64,18 @@ public static function parse(ParserState $parserState, ?CSSList $list = null): ? } } break; + case ',': + if (!\is_string($stringWrapperCharacter)) { + $selectors[] = \implode('', $selectorParts); + $selectorParts = []; + $parserState->consume(1); + $consumedNextCharacter = true; + } + break; } } while (!\in_array($nextCharacter, ['{', '}'], true) || \is_string($stringWrapperCharacter)); - $result->setSelectors(\implode('', $selectorParts), $list); + $selectors[] = \implode('', $selectorParts); // add final or only selector + $result->setSelectors($selectors, $list); if ($parserState->comes('{')) { $parserState->consume(1); } diff --git a/tests/Unit/RuleSet/DeclarationBlockTest.php b/tests/Unit/RuleSet/DeclarationBlockTest.php index 697b15aa..468a8515 100644 --- a/tests/Unit/RuleSet/DeclarationBlockTest.php +++ b/tests/Unit/RuleSet/DeclarationBlockTest.php @@ -68,8 +68,8 @@ public static function provideSelector(): array 'type & pseudo-class' => ['a:hover'], '`not`' => [':not(#your-mug)'], 'pseudo-element' => ['::before'], - 'attribute with `"`' => ['[alt="{}()[]\\"\'"]'], - 'attribute with `\'`' => ['[alt=\'{}()[]"\\\'\']'], + 'attribute with `"`' => ['[alt="{}()[]\\"\',"]'], + 'attribute with `\'`' => ['[alt=\'{}()[]"\\\',\']'], ]; } From d856c3a816ed4b4afc4223f7588eb92c57b96616 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Thu, 10 Jul 2025 12:24:54 +0100 Subject: [PATCH 502/555] [BUGFIX] Allow comma-separated arguments in selectors (#1292) Fixes #138. Fixes #360. Fixes #1289. --- CHANGELOG.md | 1 + src/RuleSet/DeclarationBlock.php | 21 +++++++++++-- tests/Unit/RuleSet/DeclarationBlockTest.php | 35 +++++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08dfef29..a40e5ff2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -110,6 +110,7 @@ Please also have a look at our ### Fixed +- Parse selector functions (like `:not`) with comma-separated arguments (#1292) - Parse quoted attribute selector value containing comma (#1323) - Allow comma in selectors (e.g. `:not(html, body)`) (#1293) - Insert `Rule` before sibling even with different property name diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index d0b9654c..68926deb 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -43,8 +43,9 @@ public static function parse(ParserState $parserState, ?CSSList $list = null): ? $selectors = []; $selectorParts = []; $stringWrapperCharacter = null; + $functionNestingLevel = 0; $consumedNextCharacter = false; - static $stopCharacters = ['{', '}', '\'', '"', ',']; + static $stopCharacters = ['{', '}', '\'', '"', '(', ')', ',']; do { if (!$consumedNextCharacter) { $selectorParts[] = $parserState->consume(1); @@ -64,8 +65,21 @@ public static function parse(ParserState $parserState, ?CSSList $list = null): ? } } break; - case ',': + case '(': if (!\is_string($stringWrapperCharacter)) { + ++$functionNestingLevel; + } + break; + case ')': + if (!\is_string($stringWrapperCharacter)) { + if ($functionNestingLevel <= 0) { + throw new UnexpectedTokenException('anything but', ')'); + } + --$functionNestingLevel; + } + break; + case ',': + if (!\is_string($stringWrapperCharacter) && $functionNestingLevel === 0) { $selectors[] = \implode('', $selectorParts); $selectorParts = []; $parserState->consume(1); @@ -74,6 +88,9 @@ public static function parse(ParserState $parserState, ?CSSList $list = null): ? break; } } while (!\in_array($nextCharacter, ['{', '}'], true) || \is_string($stringWrapperCharacter)); + if ($functionNestingLevel !== 0) { + throw new UnexpectedTokenException(')', $nextCharacter); + } $selectors[] = \implode('', $selectorParts); // add final or only selector $result->setSelectors($selectors, $list); if ($parserState->comes('{')) { diff --git a/tests/Unit/RuleSet/DeclarationBlockTest.php b/tests/Unit/RuleSet/DeclarationBlockTest.php index 468a8515..4419ba0f 100644 --- a/tests/Unit/RuleSet/DeclarationBlockTest.php +++ b/tests/Unit/RuleSet/DeclarationBlockTest.php @@ -67,6 +67,7 @@ public static function provideSelector(): array 'pseudo-class' => [':hover'], 'type & pseudo-class' => ['a:hover'], '`not`' => [':not(#your-mug)'], + '`not` with multiple arguments' => [':not(#your-mug, .their-mug)'], 'pseudo-element' => ['::before'], 'attribute with `"`' => ['[alt="{}()[]\\"\',"]'], 'attribute with `\'`' => ['[alt=\'{}()[]"\\\',\']'], @@ -114,6 +115,40 @@ public function parsesTwoCommaSeparatedSelectors(string $firstSelector, string $ self::assertSame([$firstSelector, $secondSelector], self::getSelectorsAsStrings($subject)); } + /** + * @return array + */ + public static function provideInvalidSelector(): array + { + // TODO: the `parse` method consumes the first character without inspection, + // so the 'lone' test strings are prefixed with a space. + return [ + 'lone `(`' => [' ('], + 'lone `)`' => [' )'], + 'unclosed `(`' => [':not(#your-mug'], + 'extra `)`' => [':not(#your-mug))'], + ]; + } + + /** + * @test + * + * @param non-empty-string $selector + * + * @dataProvider provideInvalidSelector + */ + public function parseSkipsBlockWithInvalidSelector(string $selector): void + { + static $nextCss = ' .next {}'; + $css = $selector . ' {}' . $nextCss; + $parserState = new ParserState($css, Settings::create()); + + $subject = DeclarationBlock::parse($parserState); + + self::assertNull($subject); + self::assertTrue($parserState->comes($nextCss)); + } + /** * @return array */ From 6a7d412a52849bdd7cf0ebffa5ed3336afa79e4e Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 11 Jul 2025 18:44:38 +0200 Subject: [PATCH 503/555] [TASK] Update the development tools (#786) --- .phive/phars.xml | 4 ++-- composer.json | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.phive/phars.xml b/.phive/phars.xml index 6af30ed5..711a07e2 100644 --- a/.phive/phars.xml +++ b/.phive/phars.xml @@ -1,5 +1,5 @@ - - + + diff --git a/composer.json b/composer.json index e4ea9c43..3cfe10b3 100644 --- a/composer.json +++ b/composer.json @@ -29,13 +29,13 @@ "require-dev": { "php-parallel-lint/php-parallel-lint": "1.4.0", "phpstan/extension-installer": "1.4.3", - "phpstan/phpstan": "1.12.16 || 2.1.2", - "phpstan/phpstan-phpunit": "1.4.2 || 2.0.4", - "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.3", + "phpstan/phpstan": "1.12.27 || 2.1.17", + "phpstan/phpstan-phpunit": "1.4.2 || 2.0.6", + "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.4", "phpunit/phpunit": "8.5.42", "rawr/phpunit-data-provider": "3.3.1", - "rector/rector": "1.2.10 || 2.0.7", - "rector/type-perfect": "1.0.0 || 2.0.2" + "rector/rector": "1.2.10 || 2.1.1", + "rector/type-perfect": "1.0.0 || 2.1.0" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" From 9aef254263698a0dbd89d7f3edd1aac21d18779d Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 11 Jul 2025 18:49:20 +0200 Subject: [PATCH 504/555] [DOCS] Integrate the 8.9.0 changelog into main changelog (#1329) This way, the upcoming 9.0.0 release won't have changes in the changelog that already are part of the 8.9.0 changelog. --- CHANGELOG.md | 93 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 54 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a40e5ff2..f201262c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,17 +11,6 @@ Please also have a look at our ### Added - Interface `RuleContainer` for `RuleSet` `Rule` manipulation methods (#1256) -- `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: - `Comment`, `CSSList`, `SourceException`, `Charset`, `CSSNamespace`, `Import`, - `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) - Partial support for CSS Color Module Level 4: - `rgb` and `rgba`, and `hsl` and `hsla` are now aliases (#797} - Parse color functions that use the "modern" syntax (#800) @@ -41,12 +30,6 @@ Please also have a look at our - `RuleSet::getRules()` and `getRulesAssoc()` now only allow `string` or `null` as the parameter (implementing classes are `AtRuleSet` and `DeclarationBlock`) (#1253) -- 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) - Initialize `KeyFrame` properties to sensible defaults (#1146) - Make `OutputFormat` `final` (#1128) - Make `Selector` a `Renderable` (#1017) @@ -60,25 +43,6 @@ Please also have a look at our ### 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 `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; - 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) -- `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 - Remove `getLineNo()` from these classes (use `getLineNumber()` instead): @@ -117,9 +81,6 @@ Please also have a look at our (in `RuleSet::addRule()`) (#1270) - Ensure `RuleSet::addRule()` sets non-negative column number when sibling provided (#1268) -- Set line number when `RuleSet::addRule()` called with only column number set - (#1265) -- Ensure first rule added with `RuleSet::addRule()` has valid position (#1262) - Don't render `rgb` colors with percentage values using hex notation (#803) ### Documentation @@ -129,6 +90,60 @@ Please also have a look at our @ziegenberg is a new contributor to this release and did a lot of the heavy lifting. Thanks! :heart: +## 8.9.0: New features, bug fixes and deprecations + +### 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: + `Comment`, `CSSList`, `SourceException`, `Charset`, `CSSNamespace`, `Import`, + `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) + +### 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 + +- 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) +- 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; + 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) +- `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) + +### 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 From b6b8ecf366d5813e06af772bb8eb3d1ecc757c8f Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 11 Jul 2025 19:53:19 +0200 Subject: [PATCH 505/555] [TASK] Raise PHPStan to level 4 (#1201) Also allow `assertInstanceOf` checks in the tests (as we find those useful). --- config/phpstan-baseline.neon | 30 ++++++++++++++++++++++++++++++ config/phpstan.neon | 7 ++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 8d882804..853c246c 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -6,6 +6,12 @@ parameters: count: 1 path: ../src/CSSList/AtRuleBlockList.php + - + message: '#^Call to function is_string\(\) with Sabberworm\\CSS\\Parsing\\ParserState will always evaluate to false\.$#' + identifier: function.impossibleType + count: 1 + path: ../src/CSSList/CSSList.php + - message: '#^Loose comparison via "\!\=" is not allowed\.$#' identifier: notEqual.notAllowed @@ -36,6 +42,18 @@ parameters: count: 1 path: ../src/CSSList/Document.php + - + message: '#^Call to function in_array\(\) with arguments null, list\ and true will always evaluate to false\.$#' + identifier: function.impossibleType + count: 1 + path: ../src/Parsing/ParserState.php + + - + message: '#^Negated boolean expression is always true\.$#' + identifier: booleanNot.alwaysTrue + count: 1 + path: ../src/Parsing/ParserState.php + - message: '#^Only booleans are allowed in an if condition, Sabberworm\\CSS\\Value\\RuleValueList\|string\|null given\.$#' identifier: if.condNotBoolean @@ -77,3 +95,15 @@ parameters: identifier: typePerfect.narrowPublicClassMethodParamType count: 1 path: ../src/Value/Size.php + + - + message: '#^Strict comparison using \!\=\= between non\-empty\-string and null will always evaluate to true\.$#' + identifier: notIdentical.alwaysTrue + count: 1 + path: ../src/Value/Size.php + + - + message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertSame\(\) with ''red'' and Sabberworm\\CSS\\Value\\Value will always evaluate to false\.$#' + identifier: staticMethod.impossibleType + count: 1 + path: ../tests/ParserTest.php diff --git a/config/phpstan.neon b/config/phpstan.neon index 6a410ac2..9eee8659 100644 --- a/config/phpstan.neon +++ b/config/phpstan.neon @@ -8,7 +8,7 @@ parameters: phpVersion: 70200 - level: 3 + level: 4 paths: - %currentWorkingDirectory%/bin/ @@ -21,3 +21,8 @@ parameters: null_over_false: true narrow_param: true narrow_return: true + + ignoreErrors: + - + message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertInstanceOf\(\) .* will always evaluate to#' + path: '../tests/' From d0b3e9cdb694abb691f6d45214901fd26d3ca96c Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Mon, 14 Jul 2025 08:39:53 +0100 Subject: [PATCH 506/555] [TASK] Use strict equality (#1331) One instance is left out, but is covered by #1330. --- config/phpstan-baseline.neon | 24 ------------------------ src/CSSList/CSSList.php | 2 +- src/Value/CalcFunction.php | 6 +++--- src/Value/Color.php | 2 +- src/Value/Size.php | 2 +- 5 files changed, 6 insertions(+), 30 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 853c246c..3ddb9d50 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -12,12 +12,6 @@ parameters: count: 1 path: ../src/CSSList/CSSList.php - - - message: '#^Loose comparison via "\!\=" is not allowed\.$#' - identifier: notEqual.notAllowed - count: 1 - path: ../src/CSSList/CSSList.php - - message: '#^Loose comparison via "\=\=" is not allowed\.$#' identifier: equal.notAllowed @@ -66,30 +60,12 @@ parameters: count: 1 path: ../src/RuleSet/DeclarationBlock.php - - - message: '#^Loose comparison via "\!\=" is not allowed\.$#' - identifier: notEqual.notAllowed - count: 3 - path: ../src/Value/CalcFunction.php - - message: '#^Cannot call method getSize\(\) on Sabberworm\\CSS\\Value\\Value\|string\.$#' identifier: method.nonObject count: 3 path: ../src/Value/Color.php - - - message: '#^Loose comparison via "\=\=" is not allowed\.$#' - identifier: equal.notAllowed - count: 3 - path: ../src/Value/Color.php - - - - message: '#^Loose comparison via "\!\=" is not allowed\.$#' - identifier: notEqual.notAllowed - count: 1 - path: ../src/Value/Size.php - - message: '#^Parameters should have "float" types as the only types passed to this method$#' identifier: typePerfect.narrowPublicClassMethodParamType diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 34246813..826581a2 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -208,7 +208,7 @@ private static function parseAtRule(ParserState $parserState): ?CSSListItem } else { // Unknown other at rule (font-face or such) $arguments = \trim($parserState->consumeUntil('{', false, true)); - if (\substr_count($arguments, '(') != \substr_count($arguments, ')')) { + if (\substr_count($arguments, '(') !== \substr_count($arguments, ')')) { if ($parserState->getSettings()->usesLenientParsing()) { return null; } else { diff --git a/src/Value/CalcFunction.php b/src/Value/CalcFunction.php index 12e2638c..dba6e1dd 100644 --- a/src/Value/CalcFunction.php +++ b/src/Value/CalcFunction.php @@ -30,7 +30,7 @@ public static function parse(ParserState $parserState, bool $ignoreCase = false) { $operators = ['+', '-', '*', '/']; $function = $parserState->parseIdentifier(); - if ($parserState->peek() != '(') { + if ($parserState->peek() !== '(') { // Found ; or end of line before an opening bracket throw new UnexpectedTokenException('(', $parserState->peek(), 'literal', $parserState->currentLine()); } elseif ($function !== 'calc') { @@ -59,7 +59,7 @@ public static function parse(ParserState $parserState, bool $ignoreCase = false) $parserState->consumeWhiteSpace(); continue; } - if ($lastComponentType != CalcFunction::T_OPERAND) { + if ($lastComponentType !== CalcFunction::T_OPERAND) { $value = Value::parsePrimitiveValue($parserState); $calcRuleValueList->addListComponent($value); $lastComponentType = CalcFunction::T_OPERAND; @@ -67,7 +67,7 @@ public static function parse(ParserState $parserState, bool $ignoreCase = false) if (\in_array($parserState->peek(), $operators, true)) { if (($parserState->comes('-') || $parserState->comes('+'))) { if ( - $parserState->peek(1, -1) != ' ' + $parserState->peek(1, -1) !== ' ' || !($parserState->comes('- ') || $parserState->comes('+ ')) ) { diff --git a/src/Value/Color.php b/src/Value/Color.php index 74e7d1bf..63bccab8 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -289,7 +289,7 @@ private function renderAsHex(): string $this->components['g']->getSize(), $this->components['b']->getSize() ); - $canUseShortVariant = ($result[0] == $result[1]) && ($result[2] == $result[3]) && ($result[4] == $result[5]); + $canUseShortVariant = ($result[0] === $result[1]) && ($result[2] === $result[3]) && ($result[4] === $result[5]); return '#' . ($canUseShortVariant ? $result[0] . $result[2] . $result[4] : $result); } diff --git a/src/Value/Size.php b/src/Value/Size.php index 3882cea1..74e8baf7 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -189,7 +189,7 @@ public function isRelative(): bool if (\in_array($this->unit, self::RELATIVE_SIZE_UNITS, true)) { return true; } - if ($this->unit === null && $this->size != 0) { + if ($this->unit === null && $this->size !== 0.0) { return true; } return false; From 7f80a2c09fede696e5c9d4323c3a8730973ab177 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Mon, 14 Jul 2025 08:42:31 +0100 Subject: [PATCH 507/555] [TASK] Make Boolean tests explicit (#1332) --- config/phpstan-baseline.neon | 12 ------------ src/CSSList/AtRuleBlockList.php | 2 +- src/Rule/Rule.php | 2 +- 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 3ddb9d50..e96a6ae2 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -1,11 +1,5 @@ parameters: ignoreErrors: - - - message: '#^Only booleans are allowed in an if condition, string given\.$#' - identifier: if.condNotBoolean - count: 1 - path: ../src/CSSList/AtRuleBlockList.php - - message: '#^Call to function is_string\(\) with Sabberworm\\CSS\\Parsing\\ParserState will always evaluate to false\.$#' identifier: function.impossibleType @@ -48,12 +42,6 @@ parameters: count: 1 path: ../src/Parsing/ParserState.php - - - message: '#^Only booleans are allowed in an if condition, Sabberworm\\CSS\\Value\\RuleValueList\|string\|null given\.$#' - identifier: if.condNotBoolean - count: 1 - path: ../src/Rule/Rule.php - - message: '#^Parameters should have "string" types as the only types passed to this method$#' identifier: typePerfect.narrowPublicClassMethodParamType diff --git a/src/CSSList/AtRuleBlockList.php b/src/CSSList/AtRuleBlockList.php index e9487cf2..b711a04e 100644 --- a/src/CSSList/AtRuleBlockList.php +++ b/src/CSSList/AtRuleBlockList.php @@ -55,7 +55,7 @@ public function render(OutputFormat $outputFormat): string $result = $formatter->comments($this); $result .= $outputFormat->getContentBeforeAtRuleBlock(); $arguments = $this->arguments; - if ($arguments) { + if ($arguments !== '') { $arguments = ' ' . $arguments; } $result .= "@{$this->type}$arguments{$formatter->spaceBeforeOpeningBrace()}{"; diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index 2ccfa7c1..8e2e81d0 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -158,7 +158,7 @@ public function addValue($value, string $type = ' '): void if (!($this->value instanceof RuleValueList) || $this->value->getListSeparator() !== $type) { $currentValue = $this->value; $this->value = new RuleValueList($type, $this->getLineNumber()); - if ($currentValue) { + if ($currentValue !== null && $currentValue !== '') { $this->value->addListComponent($currentValue); } } From e5e24f7a156ea542c17c588e0bcebc650cfd898f Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Mon, 14 Jul 2025 19:16:10 +0100 Subject: [PATCH 508/555] [CLEANUP] Remove superfluous Rector rule (#1333) Since #1201, this becomes a duplicate. --- config/rector.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/config/rector.php b/config/rector.php index 57c98235..61df8418 100644 --- a/config/rector.php +++ b/config/rector.php @@ -6,7 +6,6 @@ use Rector\PHPUnit\Set\PHPUnitSetList; use Rector\Set\ValueObject\LevelSetList; use Rector\Set\ValueObject\SetList; -use Rector\TypeDeclaration\Rector\ClassMethod\AddVoidReturnTypeWhereNoReturnRector; return RectorConfig::configure() ->withPaths( @@ -35,7 +34,4 @@ PHPUnitSetList::PHPUNIT_80, // PHPUnitSetList::PHPUNIT_CODE_QUALITY, ]) - ->withRules([ - AddVoidReturnTypeWhereNoReturnRector::class, - ]) ->withImportNames(true, true, false); From e57a7ba2df45c86cefb8fdbca9b68d74b23d464d Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Tue, 15 Jul 2025 08:39:50 +0100 Subject: [PATCH 509/555] [TASK] Add tests for `removeDeclarationBlockBySelector()` (#1335) --- tests/Unit/CSSList/CSSListTest.php | 137 +++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/tests/Unit/CSSList/CSSListTest.php b/tests/Unit/CSSList/CSSListTest.php index ce20caaf..ada176e9 100644 --- a/tests/Unit/CSSList/CSSListTest.php +++ b/tests/Unit/CSSList/CSSListTest.php @@ -8,6 +8,7 @@ use Sabberworm\CSS\Comment\Commentable; use Sabberworm\CSS\CSSElement; use Sabberworm\CSS\CSSList\CSSListItem; +use Sabberworm\CSS\Property\Selector; use Sabberworm\CSS\Renderable; use Sabberworm\CSS\RuleSet\DeclarationBlock; use Sabberworm\CSS\Tests\Unit\CSSList\Fixtures\ConcreteCSSList; @@ -189,4 +190,140 @@ public function insertBeforeAppendsIfSiblingNotFound(): void self::assertCount(4, $subject->getContents()); self::assertSame([$bogusOne, $sibling, $bogusTwo, $item], $subject->getContents()); } + + /** + * @test + */ + public function removeDeclarationBlockBySelectorRemovesDeclarationBlockProvided(): void + { + $subject = new ConcreteCSSList(); + $declarationBlock = new DeclarationBlock(); + $declarationBlock->setSelectors(['html', 'body']); + $subject->setContents([$declarationBlock]); + self::assertNotSame([], $subject->getContents()); // make sure contents are set + + $subject->removeDeclarationBlockBySelector($declarationBlock); + + self::assertSame([], $subject->getContents()); + } + + /** + * @test + */ + public function removeDeclarationBlockBySelectorRemovesDeclarationBlockWithSelectorsProvidedFromItself(): void + { + $subject = new ConcreteCSSList(); + $declarationBlock = new DeclarationBlock(); + $declarationBlock->setSelectors(['html', 'body']); + $subject->setContents([$declarationBlock]); + self::assertNotSame([], $subject->getContents()); // make sure contents are set + + $subject->removeDeclarationBlockBySelector($declarationBlock->getSelectors()); + + self::assertSame([], $subject->getContents()); + } + + /** + * @test + */ + public function removeDeclarationBlockBySelectorRemovesDeclarationBlockWithOutsourcedSelectorsProvided(): void + { + $subject = new ConcreteCSSList(); + $declarationBlock = new DeclarationBlock(); + $declarationBlock->setSelectors(['html', 'body']); + $subject->setContents([$declarationBlock]); + self::assertNotSame([], $subject->getContents()); // make sure contents are set + + $subject->removeDeclarationBlockBySelector([new Selector('html'), new Selector('body')]); + + self::assertSame([], $subject->getContents()); + } + + /** + * @test + */ + public function removeDeclarationBlockBySelectorRemovesDeclarationBlockWithStringSelectorsProvided(): void + { + $subject = new ConcreteCSSList(); + $declarationBlock = new DeclarationBlock(); + $declarationBlock->setSelectors(['html', 'body']); + $subject->setContents([$declarationBlock]); + self::assertNotSame([], $subject->getContents()); // make sure contents are set + + $subject->removeDeclarationBlockBySelector(['html', 'body']); + + self::assertSame([], $subject->getContents()); + } + + /** + * @test + */ + public function removeDeclarationBlockBySelectorRemovesDeclarationBlockProvidedAndAnotherWithSameSelectors(): void + { + $subject = new ConcreteCSSList(); + $declarationBlock1 = new DeclarationBlock(); + $declarationBlock1->setSelectors(['html', 'body']); + $declarationBlock2 = new DeclarationBlock(); + $declarationBlock2->setSelectors(['html', 'body']); + $subject->setContents([$declarationBlock1, $declarationBlock2]); + self::assertNotSame([], $subject->getContents()); // make sure contents are set + + $subject->removeDeclarationBlockBySelector($declarationBlock1, true); + + self::assertSame([], $subject->getContents()); + } + + /** + * @test + */ + public function removeDeclarationBlockBySelectorRemovesBlockWithSelectorsFromItselfAndAnotherMatching(): void + { + $subject = new ConcreteCSSList(); + $declarationBlock1 = new DeclarationBlock(); + $declarationBlock1->setSelectors(['html', 'body']); + $declarationBlock2 = new DeclarationBlock(); + $declarationBlock2->setSelectors(['html', 'body']); + $subject->setContents([$declarationBlock1, $declarationBlock2]); + self::assertNotSame([], $subject->getContents()); // make sure contents are set + + $subject->removeDeclarationBlockBySelector($declarationBlock1->getSelectors(), true); + + self::assertSame([], $subject->getContents()); + } + + /** + * @test + */ + public function removeDeclarationBlockBySelectorRemovesMultipleBlocksWithOutsourcedSelectors(): void + { + $subject = new ConcreteCSSList(); + $declarationBlock1 = new DeclarationBlock(); + $declarationBlock1->setSelectors(['html', 'body']); + $declarationBlock2 = new DeclarationBlock(); + $declarationBlock2->setSelectors(['html', 'body']); + $subject->setContents([$declarationBlock1, $declarationBlock2]); + self::assertNotSame([], $subject->getContents()); // make sure contents are set + + $subject->removeDeclarationBlockBySelector([new Selector('html'), new Selector('body')], true); + + self::assertSame([], $subject->getContents()); + } + + /** + * @test + */ + public function removeDeclarationBlockBySelectorRemovesMultipleBlocksWithStringSelectorsProvided(): void + { + $subject = new ConcreteCSSList(); + $declarationBlock1 = new DeclarationBlock(); + $declarationBlock1->setSelectors(['html', 'body']); + $declarationBlock2 = new DeclarationBlock(); + $declarationBlock2->setSelectors(['html', 'body']); + $subject->setContents([$declarationBlock1, $declarationBlock2]); + self::assertNotSame([], $subject->getContents()); // make sure contents are set + + $subject->removeDeclarationBlockBySelector(['html', 'body'], true); + + self::assertSame([], $subject->getContents()); + } } From 6039270c5764a1ff9fc5d1a9340cec08403346a1 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Wed, 16 Jul 2025 11:57:18 +0100 Subject: [PATCH 510/555] [CLEANUP] Avoid use of short-ternary operator (#1336) (I share a birthday with Elvis, but needs must.) --- config/phpstan-baseline.neon | 6 ------ src/CSSList/CSSList.php | 7 +++++-- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index e96a6ae2..587fcb0e 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -18,12 +18,6 @@ parameters: count: 1 path: ../src/CSSList/CSSList.php - - - message: '#^Short ternary operator is not allowed\. Use null coalesce operator if applicable or consider using long ternary\.$#' - identifier: ternary.shortNotAllowed - count: 1 - path: ../src/CSSList/CSSList.php - - message: '#^Parameters should have "string\|null" types as the only types passed to this method$#' identifier: typePerfect.narrowPublicClassMethodParamType diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 826581a2..8cbf74e0 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -242,8 +242,11 @@ private static function parseAtRule(ParserState $parserState): ?CSSListItem */ private static function identifierIs(string $identifier, string $match): bool { - return (\strcasecmp($identifier, $match) === 0) - ?: \preg_match("/^(-\\w+-)?$match$/i", $identifier) === 1; + if (\strcasecmp($identifier, $match) === 0) { + return true; + } + + return \preg_match("/^(-\\w+-)?$match$/i", $identifier) === 1; } /** From 9cc6f73e67e5a0c3e4088fc648082b66002c2aab Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Wed, 16 Jul 2025 11:57:53 +0100 Subject: [PATCH 511/555] [CLEANUP] Remove impossible conditional (#1337) The parameter `$parserState` is specified to be a `ParserState`, so it can never be a string. --- config/phpstan-baseline.neon | 6 ------ src/CSSList/CSSList.php | 4 ---- 2 files changed, 10 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 587fcb0e..982bbbfe 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -1,11 +1,5 @@ parameters: ignoreErrors: - - - message: '#^Call to function is_string\(\) with Sabberworm\\CSS\\Parsing\\ParserState will always evaluate to false\.$#' - identifier: function.impossibleType - count: 1 - path: ../src/CSSList/CSSList.php - - message: '#^Loose comparison via "\=\=" is not allowed\.$#' identifier: equal.notAllowed diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 8cbf74e0..ee09b79e 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -21,7 +21,6 @@ use Sabberworm\CSS\RuleSet\AtRuleSet; use Sabberworm\CSS\RuleSet\DeclarationBlock; use Sabberworm\CSS\RuleSet\RuleSet; -use Sabberworm\CSS\Settings; use Sabberworm\CSS\Value\CSSString; use Sabberworm\CSS\Value\URL; use Sabberworm\CSS\Value\Value; @@ -64,9 +63,6 @@ public function __construct(?int $lineNumber = null) public static function parseList(ParserState $parserState, CSSList $list): void { $isRoot = $list instanceof Document; - if (\is_string($parserState)) { - $parserState = new ParserState($parserState, Settings::create()); - } $usesLenientParsing = $parserState->getSettings()->usesLenientParsing(); $comments = []; while (!$parserState->isEnd()) { From cad75d74e5ce9756de05115e4a92b70f958a4f70 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Thu, 17 Jul 2025 21:32:57 +0100 Subject: [PATCH 512/555] [BUGFIX] Correct DocBlock for `ParserState::consumeUntil()` (#1338) The special `EOF` constant is actually defined as `null`, so the stop characters may be strings or `null`. --- config/phpstan-baseline.neon | 6 ------ src/Parsing/ParserState.php | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 982bbbfe..486ed7da 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -18,12 +18,6 @@ parameters: count: 1 path: ../src/CSSList/Document.php - - - message: '#^Call to function in_array\(\) with arguments null, list\ and true will always evaluate to false\.$#' - identifier: function.impossibleType - count: 1 - path: ../src/Parsing/ParserState.php - - message: '#^Negated boolean expression is always true\.$#' identifier: booleanNot.alwaysTrue diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index e90650d5..74ad0efe 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -328,7 +328,7 @@ public function isEnd(): bool } /** - * @param list|string $stopCharacters + * @param list|string|self::EOF $stopCharacters * @param array $comments * * @throws UnexpectedEOFException From 8c4a77ee56cad126e33d0c9ee69e646b8c039fa2 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 18 Jul 2025 15:19:15 +0200 Subject: [PATCH 513/555] [BUGFIX] Provide the authentication token for PHIVE on CI (#1340) This will hopefully avoid the 403 errors when installing the PHIVE packages. Fixes #1339 --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14624a48..73e68ff8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -139,6 +139,8 @@ jobs: composer show; - name: Install development tools + env: + GITHUB_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | phive --no-progress install --trust-gpg-keys 0FDE18AE1D09E19F60F6B1CBC00543248C87FB13,BBAB5DF0A0D6672989CF1869E82B2FB314E9906E From af6d8c0232ba7da9b332b3fefe12750dd2e2d64e Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Mon, 21 Jul 2025 09:10:30 +0100 Subject: [PATCH 514/555] [TASK] Make `RuleSet` concrete (#1341) ... adding internal `render` method. Precursor to #1194. --- src/RuleSet/RuleSet.php | 10 +- tests/Functional/RuleSet/RuleSetTest.php | 119 ++++++++++++++++++ .../Unit/RuleSet/Fixtures/ConcreteRuleSet.php | 19 --- tests/Unit/RuleSet/RuleSetTest.php | 6 +- 4 files changed, 131 insertions(+), 23 deletions(-) create mode 100644 tests/Functional/RuleSet/RuleSetTest.php delete mode 100644 tests/Unit/RuleSet/Fixtures/ConcreteRuleSet.php diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 9ab5e203..e634b879 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -27,7 +27,7 @@ * Note that `CSSListItem` extends both `Commentable` and `Renderable`, * so those interfaces must also be implemented by concrete subclasses. */ -abstract class RuleSet implements CSSElement, CSSListItem, Positionable, RuleContainer +class RuleSet implements CSSElement, CSSListItem, Positionable, RuleContainer { use CommentContainer; use Position; @@ -293,6 +293,14 @@ public function removeAllRules(): void $this->rules = []; } + /** + * @internal + */ + public function render(OutputFormat $outputFormat): string + { + return $this->renderRules($outputFormat); + } + protected function renderRules(OutputFormat $outputFormat): string { $result = ''; diff --git a/tests/Functional/RuleSet/RuleSetTest.php b/tests/Functional/RuleSet/RuleSetTest.php new file mode 100644 index 00000000..152f0c6b --- /dev/null +++ b/tests/Functional/RuleSet/RuleSetTest.php @@ -0,0 +1,119 @@ +subject = new RuleSet(); + } + + /** + * @return array, 1: string}> + */ + public static function providePropertyNamesAndValuesAndExpectedCss(): array + { + return [ + 'no properties' => [[], ''], + 'one property' => [ + [['name' => 'color', 'value' => 'green']], + 'color: green;', + ], + 'two different properties' => [ + [ + ['name' => 'color', 'value' => 'green'], + ['name' => 'display', 'value' => 'block'], + ], + 'color: green;display: block;', + ], + 'two of the same property' => [ + [ + ['name' => 'color', 'value' => '#40A040'], + ['name' => 'color', 'value' => 'rgba(0, 128, 0, 0.25)'], + ], + 'color: #40A040;color: rgba(0, 128, 0, 0.25);', + ], + ]; + } + + /** + * @test + * + * @param list $propertyNamesAndValuesToSet + * + * @dataProvider providePropertyNamesAndValuesAndExpectedCss + */ + public function renderReturnsCssForRulesSet(array $propertyNamesAndValuesToSet, string $expectedCss): void + { + $this->setRulesFromPropertyNamesAndValues($propertyNamesAndValuesToSet); + + $result = $this->subject->render(OutputFormat::create()); + + self::assertSame($expectedCss, $result); + } + + /** + * @test + */ + public function renderWithCompactOutputFormatReturnsCssWithoutWhitespace(): void + { + $this->setRulesFromPropertyNamesAndValues([ + ['name' => 'color', 'value' => 'green'], + ['name' => 'display', 'value' => 'block'], + ]); + + $result = $this->subject->render(OutputFormat::createCompact()); + + self::assertSame('color:green;display:block;', $result); + } + + /** + * @test + */ + public function renderWithPrettyOutputFormatReturnsCssWithNewlinesAroundIndentedDeclarations(): void + { + $this->setRulesFromPropertyNamesAndValues([ + ['name' => 'color', 'value' => 'green'], + ['name' => 'display', 'value' => 'block'], + ]); + + $result = $this->subject->render(OutputFormat::createPretty()); + + self::assertSame("\n\tcolor: green;\n\tdisplay: block;\n", $result); + } + + /** + * @param list $propertyNamesAndValues + */ + private function setRulesFromPropertyNamesAndValues(array $propertyNamesAndValues): void + { + $rulesToSet = \array_map( + /** + * @param array{name: string, value: string} $nameAndValue + */ + static function (array $nameAndValue): Rule { + $rule = new Rule($nameAndValue['name']); + $rule->setValue($nameAndValue['value']); + return $rule; + }, + $propertyNamesAndValues + ); + $this->subject->setRules($rulesToSet); + } +} diff --git a/tests/Unit/RuleSet/Fixtures/ConcreteRuleSet.php b/tests/Unit/RuleSet/Fixtures/ConcreteRuleSet.php deleted file mode 100644 index 9c79c09d..00000000 --- a/tests/Unit/RuleSet/Fixtures/ConcreteRuleSet.php +++ /dev/null @@ -1,19 +0,0 @@ -subject = new ConcreteRuleSet(); + $this->subject = new RuleSet(); } /** From 3d72bbc94912d1bb77a9a6fe13328ebb324bc21e Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Mon, 21 Jul 2025 15:02:19 +0100 Subject: [PATCH 515/555] [CLEANUP] Update `RuleSet` DocBlock (#1343) ... to be consistent with the class now being concrete. Missed in #1341. --- src/RuleSet/RuleSet.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index e634b879..521a6ae9 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -24,8 +24,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). * - * Note that `CSSListItem` extends both `Commentable` and `Renderable`, - * so those interfaces must also be implemented by concrete subclasses. + * Note that `CSSListItem` extends both `Commentable` and `Renderable`, so those interfaces must also be implemented. */ class RuleSet implements CSSElement, CSSListItem, Positionable, RuleContainer { From aed922c9ae2751885b1af0b0cb8cf21cfe0d42a6 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Mon, 21 Jul 2025 15:02:49 +0100 Subject: [PATCH 516/555] [CLEANUP] Reorder `use`s in `DeclarationBlockTest` (#1344) Order alphabetically. --- tests/Unit/RuleSet/DeclarationBlockTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/RuleSet/DeclarationBlockTest.php b/tests/Unit/RuleSet/DeclarationBlockTest.php index 4419ba0f..5559c044 100644 --- a/tests/Unit/RuleSet/DeclarationBlockTest.php +++ b/tests/Unit/RuleSet/DeclarationBlockTest.php @@ -7,8 +7,8 @@ use PHPUnit\Framework\TestCase; use Sabberworm\CSS\CSSElement; use Sabberworm\CSS\CSSList\CSSListItem; -use Sabberworm\CSS\Position\Positionable; use Sabberworm\CSS\Parsing\ParserState; +use Sabberworm\CSS\Position\Positionable; use Sabberworm\CSS\Property\Selector; use Sabberworm\CSS\RuleSet\DeclarationBlock; use Sabberworm\CSS\Settings; From 459dd28172b5efddeae7bce8379897e3c17e35fe Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Mon, 21 Jul 2025 15:41:23 +0100 Subject: [PATCH 517/555] [BUGFIX] Remove trailing semicolon with compact format (#1345) Fixes #1342. --- CHANGELOG.md | 2 ++ src/OutputFormat.php | 1 + tests/Comment/CommentTest.php | 8 ++++---- tests/Functional/RuleSet/RuleSetTest.php | 4 ++-- tests/OutputFormatTest.php | 4 ++-- tests/Unit/OutputFormatTest.php | 10 ++++++++++ 6 files changed, 21 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f201262c..2064f81f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,8 @@ Please also have a look at our ### Fixed +- Remove trailing semicolon from declaration blocks with 'compact' + `OutputFormat` (#1345) - Parse selector functions (like `:not`) with comma-separated arguments (#1292) - Parse quoted attribute selector value containing comma (#1323) - Allow comma in selectors (e.g. `:not(html, body)`) (#1293) diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 6ad45aa4..5c493865 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -726,6 +726,7 @@ public static function createCompact(): self ->setSpaceAfterRuleName('') ->setSpaceBeforeOpeningBrace('') ->setSpaceAfterSelectorSeparator('') + ->setSemicolonAfterLastRule(false) ->setRenderComments(false); return $format; diff --git a/tests/Comment/CommentTest.php b/tests/Comment/CommentTest.php index 15d5983e..52c3de55 100644 --- a/tests/Comment/CommentTest.php +++ b/tests/Comment/CommentTest.php @@ -49,8 +49,8 @@ public function keepCommentsInOutput(): void . ' * 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;}}', + . '/* Number 6 */background-color:#000}@media screen{' + . '/** Number 10 **/#foo.bar{/** Number 10b **/position:absolute}}', $cssDocument->render(OutputFormat::createCompact()->setRenderComments(true)) ); } @@ -76,8 +76,8 @@ public function stripCommentsFromOutput(): void ', $css->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}}', $css->render(OutputFormat::createCompact()) ); } diff --git a/tests/Functional/RuleSet/RuleSetTest.php b/tests/Functional/RuleSet/RuleSetTest.php index 152f0c6b..b0d97605 100644 --- a/tests/Functional/RuleSet/RuleSetTest.php +++ b/tests/Functional/RuleSet/RuleSetTest.php @@ -71,7 +71,7 @@ public function renderReturnsCssForRulesSet(array $propertyNamesAndValuesToSet, /** * @test */ - public function renderWithCompactOutputFormatReturnsCssWithoutWhitespace(): void + public function renderWithCompactOutputFormatReturnsCssWithoutWhitespaceOrTrailingSemicolon(): void { $this->setRulesFromPropertyNamesAndValues([ ['name' => 'color', 'value' => 'green'], @@ -80,7 +80,7 @@ public function renderWithCompactOutputFormatReturnsCssWithoutWhitespace(): void $result = $this->subject->render(OutputFormat::createCompact()); - self::assertSame('color:green;display:block;', $result); + self::assertSame('color:green;display:block', $result); } /** diff --git a/tests/OutputFormatTest.php b/tests/OutputFormatTest.php index 9852ae58..3a8deb30 100644 --- a/tests/OutputFormatTest.php +++ b/tests/OutputFormatTest.php @@ -69,8 +69,8 @@ public function plain(): void public function compact(): void { 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;}}', + '.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}}', $this->document->render(OutputFormat::createCompact()) ); } diff --git a/tests/Unit/OutputFormatTest.php b/tests/Unit/OutputFormatTest.php index 781ad2f4..1a043e1d 100644 --- a/tests/Unit/OutputFormatTest.php +++ b/tests/Unit/OutputFormatTest.php @@ -1034,6 +1034,16 @@ public function createCompactReturnsInstanceWithSpaceAfterListArgumentSeparators self::assertSame([], $newInstance->getSpaceAfterListArgumentSeparators()); } + /** + * @test + */ + public function createCompactReturnsInstanceWithRenderSemicolonAfterLastRuleDisabled(): void + { + $newInstance = OutputFormat::createCompact(); + + self::assertFalse($newInstance->shouldRenderSemicolonAfterLastRule()); + } + /** * @test */ From bb3d3425a8522a5bbd5fe6fb5ebf681bd68c5e45 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Mon, 21 Jul 2025 17:49:27 +0100 Subject: [PATCH 518/555] [CLEANUP] Use `getAllDeclarationBlocks` in `colorParsing` test (#1346) ... instead of `getAllRuleSets`. This avoids testing if `RuleSet`s are `DeclarationBlock`s, and will be needed for #1194. --- tests/ParserTest.php | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/tests/ParserTest.php b/tests/ParserTest.php index da991ee7..15af0fd7 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -91,17 +91,14 @@ public function files(): void public function colorParsing(): void { $document = self::parsedStructureForFile('colortest'); - foreach ($document->getAllRuleSets() as $ruleSet) { - if (!($ruleSet instanceof DeclarationBlock)) { - continue; - } - $selectors = $ruleSet->getSelectors(); + foreach ($document->getAllDeclarationBlocks() as $declarationBlock) { + $selectors = $declarationBlock->getSelectors(); $selector = $selectors[0]->getSelector(); if ($selector === '#mine') { - $colorRules = $ruleSet->getRules('color'); + $colorRules = $declarationBlock->getRules('color'); $colorRuleValue = $colorRules[0]->getValue(); self::assertSame('red', $colorRuleValue); - $colorRules = $ruleSet->getRules('background-'); + $colorRules = $declarationBlock->getRules('background-'); $colorRuleValue = $colorRules[0]->getValue(); self::assertInstanceOf(Color::class, $colorRuleValue); self::assertEquals([ @@ -109,7 +106,7 @@ public function colorParsing(): void 'g' => new Size(35.0, null, true, $colorRuleValue->getLineNumber()), 'b' => new Size(35.0, null, true, $colorRuleValue->getLineNumber()), ], $colorRuleValue->getColor()); - $colorRules = $ruleSet->getRules('border-color'); + $colorRules = $declarationBlock->getRules('border-color'); $colorRuleValue = $colorRules[0]->getValue(); self::assertInstanceOf(Color::class, $colorRuleValue); self::assertEquals([ @@ -125,7 +122,7 @@ public function colorParsing(): void 'b' => new Size(231.0, null, true, $colorRuleValue->getLineNumber()), 'a' => new Size('0000.3', null, true, $colorRuleValue->getLineNumber()), ], $colorRuleValue->getColor()); - $colorRules = $ruleSet->getRules('outline-color'); + $colorRules = $declarationBlock->getRules('outline-color'); $colorRuleValue = $colorRules[0]->getValue(); self::assertInstanceOf(Color::class, $colorRuleValue); self::assertEquals([ @@ -134,7 +131,7 @@ public function colorParsing(): void 'b' => new Size(34.0, null, true, $colorRuleValue->getLineNumber()), ], $colorRuleValue->getColor()); } elseif ($selector === '#yours') { - $colorRules = $ruleSet->getRules('background-color'); + $colorRules = $declarationBlock->getRules('background-color'); $colorRuleValue = $colorRules[0]->getValue(); self::assertInstanceOf(Color::class, $colorRuleValue); self::assertEquals([ @@ -150,7 +147,7 @@ public function colorParsing(): void 'l' => new Size(220.0, '%', true, $colorRuleValue->getLineNumber()), 'a' => new Size(0000.3, null, true, $colorRuleValue->getLineNumber()), ], $colorRuleValue->getColor()); - $colorRules = $ruleSet->getRules('outline-color'); + $colorRules = $declarationBlock->getRules('outline-color'); self::assertEmpty($colorRules); } } @@ -179,13 +176,13 @@ public function colorParsing(): void public function unicodeParsing(): void { $document = self::parsedStructureForFile('unicode'); - foreach ($document->getAllDeclarationBlocks() as $ruleSet) { - $selectors = $ruleSet->getSelectors(); + foreach ($document->getAllDeclarationBlocks() as $declarationBlock) { + $selectors = $declarationBlock->getSelectors(); $selector = $selectors[0]->getSelector(); if (\substr($selector, 0, \strlen('.test-')) !== '.test-') { continue; } - $contentRules = $ruleSet->getRules('content'); + $contentRules = $declarationBlock->getRules('content'); $firstContentRuleAsString = $contentRules[0]->getValue()->render(OutputFormat::create()); if ($selector === '.test-1') { self::assertSame('" "', $firstContentRuleAsString); From ac31718ae203d7d3d26e5676a20862dd422a1575 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Thu, 24 Jul 2025 09:19:10 +0100 Subject: [PATCH 519/555] [TASK] Add tests for `RuleSet` constructor (#1348) --- tests/Unit/RuleSet/RuleSetTest.php | 37 ++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/Unit/RuleSet/RuleSetTest.php b/tests/Unit/RuleSet/RuleSetTest.php index 83456f89..431b0076 100644 --- a/tests/Unit/RuleSet/RuleSetTest.php +++ b/tests/Unit/RuleSet/RuleSetTest.php @@ -41,4 +41,41 @@ public function implementsCSSListItem(): void { self::assertInstanceOf(CSSListItem::class, $this->subject); } + + /** + * @test + */ + public function getLineNumberByDefaultReturnsNull(): void + { + $result = $this->subject->getLineNumber(); + + self::assertNull($result); + } + + /** + * @return array}> + */ + public function provideLineNumber(): array + { + return [ + 'line 1' => [1], + 'line 42' => [42], + ]; + } + + /** + * @test + * + * @param int<1, max> $lineNumber + * + * @dataProvider provideLineNumber + */ + public function getLineNumberReturnsLineNumberPassedToConstructor(int $lineNumber): void + { + $subject = new RuleSet($lineNumber); + + $result = $subject->getLineNumber(); + + self::assertSame($result, $subject->getLineNumber()); + } } From c5bce4c67a8478f36fc7eae308aa6aece0ce6b17 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Thu, 24 Jul 2025 17:42:51 +0100 Subject: [PATCH 520/555] [BUGFIX] Correct an `assert` added in #1348 (#1349) --- tests/Unit/RuleSet/RuleSetTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/RuleSet/RuleSetTest.php b/tests/Unit/RuleSet/RuleSetTest.php index 431b0076..27e8aaee 100644 --- a/tests/Unit/RuleSet/RuleSetTest.php +++ b/tests/Unit/RuleSet/RuleSetTest.php @@ -76,6 +76,6 @@ public function getLineNumberReturnsLineNumberPassedToConstructor(int $lineNumbe $result = $subject->getLineNumber(); - self::assertSame($result, $subject->getLineNumber()); + self::assertSame($lineNumber, $result); } } From 41c9edfac2f4e36d25d040ce0bad710505e005e7 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Thu, 24 Jul 2025 19:22:31 +0100 Subject: [PATCH 521/555] [TASK] Add tests for `DeclarationBlock` constructor (#1350) The class extends `RuleSet`, but the constructor behaviour needs to be tested for each class. --- tests/Unit/RuleSet/DeclarationBlockTest.php | 38 +++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/Unit/RuleSet/DeclarationBlockTest.php b/tests/Unit/RuleSet/DeclarationBlockTest.php index 5559c044..3d85d98c 100644 --- a/tests/Unit/RuleSet/DeclarationBlockTest.php +++ b/tests/Unit/RuleSet/DeclarationBlockTest.php @@ -53,6 +53,44 @@ public function implementsPositionable(): void self::assertInstanceOf(Positionable::class, $this->subject); } + /** + * @test + */ + public function getLineNumberByDefaultReturnsNull(): void + { + $result = $this->subject->getLineNumber(); + + self::assertNull($result); + } + + /** + * @return array|null}> + */ + public function provideLineNumber(): array + { + return [ + 'null' => [null], + 'line 1' => [1], + 'line 42' => [42], + ]; + } + + /** + * @test + * + * @param int<1, max>|null $lineNumber + * + * @dataProvider provideLineNumber + */ + public function getLineNumberReturnsLineNumberPassedToConstructor(?int $lineNumber): void + { + $subject = new DeclarationBlock($lineNumber); + + $result = $subject->getLineNumber(); + + self::assertSame($lineNumber, $result); + } + /** * @return array */ From b2028eb9519718a97c006c0536e0998f570b3bc7 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Thu, 24 Jul 2025 19:23:12 +0100 Subject: [PATCH 522/555] [TASK] Test `RuleSet` constructor with `null` explicitly passed (#1351) --- tests/Unit/RuleSet/RuleSetTest.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/Unit/RuleSet/RuleSetTest.php b/tests/Unit/RuleSet/RuleSetTest.php index 27e8aaee..92c056ca 100644 --- a/tests/Unit/RuleSet/RuleSetTest.php +++ b/tests/Unit/RuleSet/RuleSetTest.php @@ -53,11 +53,12 @@ public function getLineNumberByDefaultReturnsNull(): void } /** - * @return array}> + * @return array|null}> */ public function provideLineNumber(): array { return [ + 'null' => [null], 'line 1' => [1], 'line 42' => [42], ]; @@ -66,11 +67,11 @@ public function provideLineNumber(): array /** * @test * - * @param int<1, max> $lineNumber + * @param int<1, max>|null $lineNumber * * @dataProvider provideLineNumber */ - public function getLineNumberReturnsLineNumberPassedToConstructor(int $lineNumber): void + public function getLineNumberReturnsLineNumberPassedToConstructor(?int $lineNumber): void { $subject = new RuleSet($lineNumber); From 3b6055f794a2fae5744c4f5a25b6b5821c189811 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Fri, 25 Jul 2025 10:16:59 +0100 Subject: [PATCH 523/555] [BUGFIX] Update class diagram to show `RuleSet` as concrete (#1352) Missed in #1341. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 29253a34..638d43e9 100644 --- a/README.md +++ b/README.md @@ -699,7 +699,6 @@ classDiagram <> } class RuleSet { - <> } class RuleValueList { } From c1e0ef7c4446c80ae3c34d83e8b4da29d3862aa0 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Fri, 25 Jul 2025 15:28:05 +0100 Subject: [PATCH 524/555] [TASK] Use delegation for `DeclarationBlock` -> `RuleSet` (#1194) ... rather than inheritance. This will allow `DeclarationBlock` to instead extend `CSSBlockList` in order to support [CSS nesting](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting). This is a slightly-breaking change, since now `CSSBlockList::getAllRuleSets()` will include the `RuleSet` property of the `DeclarationBlock` instead of the `DeclarationBlock` itself. Part of #1170. --- CHANGELOG.md | 2 + README.md | 6 +- src/CSSList/CSSBlockList.php | 2 + src/RuleSet/DeclarationBlock.php | 100 +++++++++++++++++++- tests/ParserTest.php | 22 ++--- tests/RuleSet/DeclarationBlockTest.php | 7 +- tests/Unit/CSSList/CSSBlockListTest.php | 12 +-- tests/Unit/RuleSet/DeclarationBlockTest.php | 79 ++++++++++++++++ 8 files changed, 205 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2064f81f..506b6b1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ Please also have a look at our ### Changed +- `DeclarationBlock` no longer extends `RuleSet` and instead has a `RuleSet` as + a property; use `getRuleSet()` to access it directly (#1194) - The default line (and column) number is now `null` (not zero) (#1288) - `setPosition()` (in `Rule` and other classes) now has fluent interface, returning itself (#1259) diff --git a/README.md b/README.md index 638d43e9..176f49ca 100644 --- a/README.md +++ b/README.md @@ -752,7 +752,11 @@ classDiagram CSSFunction <|-- Color: inheritance Positionable <|.. Comment: realization Renderable <|.. Comment: realization - RuleSet <|-- DeclarationBlock: inheritance + CSSElement <|.. DeclarationBlock: realization + CSSListItem <|.. DeclarationBlock: realization + Positionable <|.. DeclarationBlock: realization + RuleContainer <|.. DeclarationBlock: realization + DeclarationBlock ..> RuleSet : dependency DeclarationBlock ..> Selector: dependency CSSBlockList <|-- Document: inheritance AtRule <|.. Import: realization diff --git a/src/CSSList/CSSBlockList.php b/src/CSSList/CSSBlockList.php index 2dfd284f..122ac5d1 100644 --- a/src/CSSList/CSSBlockList.php +++ b/src/CSSList/CSSBlockList.php @@ -56,6 +56,8 @@ public function getAllRuleSets(): array $result[] = $item; } elseif ($item instanceof CSSBlockList) { $result = \array_merge($result, $item->getAllRuleSets()); + } elseif ($item instanceof DeclarationBlock) { + $result[] = $item->getRuleSet(); } } diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 68926deb..4d077546 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -4,15 +4,21 @@ namespace Sabberworm\CSS\RuleSet; +use Sabberworm\CSS\Comment\CommentContainer; +use Sabberworm\CSS\CSSElement; use Sabberworm\CSS\CSSList\CSSList; +use Sabberworm\CSS\CSSList\CSSListItem; use Sabberworm\CSS\CSSList\KeyFrame; use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Parsing\OutputException; 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\Property\KeyframeSelector; use Sabberworm\CSS\Property\Selector; +use Sabberworm\CSS\Rule\Rule; /** * This class represents a `RuleSet` constrained by a `Selector`. @@ -21,14 +27,33 @@ * matching elements. * * Declaration blocks usually appear directly inside a `Document` or another `CSSList` (mostly a `MediaQuery`). + * + * Note that `CSSListItem` extends both `Commentable` and `Renderable`, so those interfaces must also be implemented. */ -class DeclarationBlock extends RuleSet +class DeclarationBlock implements CSSElement, CSSListItem, Positionable, RuleContainer { + use CommentContainer; + use Position; + /** * @var array */ private $selectors = []; + /** + * @var RuleSet + */ + private $ruleSet; + + /** + * @param int<1, max>|null $lineNumber + */ + public function __construct(?int $lineNumber = null) + { + $this->ruleSet = new RuleSet($lineNumber); + $this->setPosition($lineNumber); + } + /** * @throws UnexpectedTokenException * @throws UnexpectedEOFException @@ -107,7 +132,9 @@ public static function parse(ParserState $parserState, ?CSSList $list = null): ? } } $result->setComments($comments); - RuleSet::parseRuleSet($parserState, $result); + + RuleSet::parseRuleSet($parserState, $result->getRuleSet()); + return $result; } @@ -175,6 +202,73 @@ public function getSelectors(): array return $this->selectors; } + public function getRuleSet(): RuleSet + { + return $this->ruleSet; + } + + /** + * @see RuleSet::addRule() + */ + public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void + { + $this->ruleSet->addRule($ruleToAdd, $sibling); + } + + /** + * @see RuleSet::getRules() + * + * @return array, Rule> + */ + public function getRules(?string $searchPattern = null): array + { + return $this->ruleSet->getRules($searchPattern); + } + + /** + * @see RuleSet::setRules() + * + * @param array $rules + */ + public function setRules(array $rules): void + { + $this->ruleSet->setRules($rules); + } + + /** + * @see RuleSet::getRulesAssoc() + * + * @return array + */ + public function getRulesAssoc(?string $searchPattern = null): array + { + return $this->ruleSet->getRulesAssoc($searchPattern); + } + + /** + * @see RuleSet::removeRule() + */ + public function removeRule(Rule $ruleToRemove): void + { + $this->ruleSet->removeRule($ruleToRemove); + } + + /** + * @see RuleSet::removeMatchingRules() + */ + public function removeMatchingRules(string $searchPattern): void + { + $this->ruleSet->removeMatchingRules($searchPattern); + } + + /** + * @see RuleSet::removeAllRules() + */ + public function removeAllRules(): void + { + $this->ruleSet->removeAllRules(); + } + /** * @return non-empty-string * @@ -198,7 +292,7 @@ public function render(OutputFormat $outputFormat): string ); $result .= $outputFormat->getContentAfterDeclarationBlockSelectors(); $result .= $formatter->spaceBeforeOpeningBrace() . '{'; - $result .= $this->renderRules($outputFormat); + $result .= $this->ruleSet->render($outputFormat); $result .= '}'; $result .= $outputFormat->getContentAfterDeclarationBlock(); diff --git a/tests/ParserTest.php b/tests/ParserTest.php index 15af0fd7..0217eba9 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -38,7 +38,7 @@ final class ParserTest extends TestCase /** * @test */ - public function parseForOneRuleSetReturnsDocumentWithOneRuleSet(): void + public function parseForOneDeclarationBlockReturnsDocumentWithOneDeclarationBlock(): void { $css = '.thing { left: 10px; }'; $parser = new Parser($css); @@ -49,7 +49,7 @@ public function parseForOneRuleSetReturnsDocumentWithOneRuleSet(): void $cssList = $document->getContents(); self::assertCount(1, $cssList); - self::assertInstanceOf(RuleSet::class, $cssList[0]); + self::assertInstanceOf(DeclarationBlock::class, $cssList[0]); } /** @@ -926,9 +926,9 @@ public function missingPropertyValueStrict(): void public function missingPropertyValueLenient(): void { $parsed = self::parsedStructureForFile('missing-property-value', Settings::create()->withLenientParsing(true)); - $rulesets = $parsed->getAllRuleSets(); - self::assertCount(1, $rulesets); - $block = $rulesets[0]; + $declarationBlocks = $parsed->getAllDeclarationBlocks(); + self::assertCount(1, $declarationBlocks); + $block = $declarationBlocks[0]; self::assertInstanceOf(DeclarationBlock::class, $block); self::assertEquals([new Selector('div')], $block->getSelectors()); $rules = $block->getRules(); @@ -1055,7 +1055,7 @@ public function commentExtracting(): void // $this->assertSame("* Number 5 *", $fooBarBlockComments[1]->getComment()); // Declaration rules. - self::assertInstanceOf(RuleSet::class, $fooBarBlock); + self::assertInstanceOf(DeclarationBlock::class, $fooBarBlock); $fooBarRules = $fooBarBlock->getRules(); $fooBarRule = $fooBarRules[0]; $fooBarRuleComments = $fooBarRule->getComments(); @@ -1076,7 +1076,7 @@ public function commentExtracting(): void self::assertSame('* Number 10 *', $fooBarComments[0]->getComment()); // Media -> declaration -> rule. - self::assertInstanceOf(RuleSet::class, $mediaRules[0]); + self::assertInstanceOf(DeclarationBlock::class, $mediaRules[0]); $fooBarRules = $mediaRules[0]->getRules(); $fooBarChildComments = $fooBarRules[0]->getComments(); self::assertCount(1, $fooBarChildComments); @@ -1092,7 +1092,7 @@ public function flatCommentExtractingOneComment(): void $document = $parser->parse(); $contents = $document->getContents(); - self::assertInstanceOf(RuleSet::class, $contents[0]); + self::assertInstanceOf(DeclarationBlock::class, $contents[0]); $divRules = $contents[0]->getRules(); $comments = $divRules[0]->getComments(); @@ -1109,7 +1109,7 @@ public function flatCommentExtractingTwoConjoinedCommentsForOneRule(): void $document = $parser->parse(); $contents = $document->getContents(); - self::assertInstanceOf(RuleSet::class, $contents[0]); + self::assertInstanceOf(DeclarationBlock::class, $contents[0]); $divRules = $contents[0]->getRules(); $comments = $divRules[0]->getComments(); @@ -1127,7 +1127,7 @@ public function flatCommentExtractingTwoSpaceSeparatedCommentsForOneRule(): void $document = $parser->parse(); $contents = $document->getContents(); - self::assertInstanceOf(RuleSet::class, $contents[0]); + self::assertInstanceOf(DeclarationBlock::class, $contents[0]); $divRules = $contents[0]->getRules(); $comments = $divRules[0]->getComments(); @@ -1145,7 +1145,7 @@ public function flatCommentExtractingCommentsForTwoRules(): void $document = $parser->parse(); $contents = $document->getContents(); - self::assertInstanceOf(RuleSet::class, $contents[0]); + self::assertInstanceOf(DeclarationBlock::class, $contents[0]); $divRules = $contents[0]->getRules(); $rule1Comments = $divRules[0]->getComments(); $rule2Comments = $divRules[1]->getComments(); diff --git a/tests/RuleSet/DeclarationBlockTest.php b/tests/RuleSet/DeclarationBlockTest.php index 5aaf0662..63411a64 100644 --- a/tests/RuleSet/DeclarationBlockTest.php +++ b/tests/RuleSet/DeclarationBlockTest.php @@ -8,13 +8,12 @@ use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Parser; use Sabberworm\CSS\Rule\Rule; -use Sabberworm\CSS\RuleSet\RuleSet; +use Sabberworm\CSS\RuleSet\DeclarationBlock; use Sabberworm\CSS\Settings as ParserSettings; use Sabberworm\CSS\Value\Size; /** * @covers \Sabberworm\CSS\RuleSet\DeclarationBlock - * @covers \Sabberworm\CSS\RuleSet\RuleSet */ final class DeclarationBlockTest extends TestCase { @@ -31,7 +30,7 @@ public function overrideRules(): void $contents = $document->getContents(); $wrapper = $contents[0]; - self::assertInstanceOf(RuleSet::class, $wrapper); + self::assertInstanceOf(DeclarationBlock::class, $wrapper); self::assertCount(2, $wrapper->getRules()); $wrapper->setRules([$rule]); @@ -52,7 +51,7 @@ public function ruleInsertion(): void $contents = $document->getContents(); $wrapper = $contents[0]; - self::assertInstanceOf(RuleSet::class, $wrapper); + self::assertInstanceOf(DeclarationBlock::class, $wrapper); $leftRules = $wrapper->getRules('left'); self::assertCount(1, $leftRules); diff --git a/tests/Unit/CSSList/CSSBlockListTest.php b/tests/Unit/CSSList/CSSBlockListTest.php index 41f2b41f..ce7e5447 100644 --- a/tests/Unit/CSSList/CSSBlockListTest.php +++ b/tests/Unit/CSSList/CSSBlockListTest.php @@ -157,7 +157,7 @@ public function getAllRuleSetsWhenNoContentSetReturnsEmptyArray(): void /** * @test */ - public function getAllRuleSetsReturnsOneDeclarationBlockDirectlySetAsContent(): void + public function getAllRuleSetsReturnsRuleSetFromOneDeclarationBlockDirectlySetAsContent(): void { $subject = new ConcreteCSSBlockList(); @@ -166,7 +166,7 @@ public function getAllRuleSetsReturnsOneDeclarationBlockDirectlySetAsContent(): $result = $subject->getAllRuleSets(); - self::assertSame([$declarationBlock], $result); + self::assertSame([$declarationBlock->getRuleSet()], $result); } /** @@ -187,7 +187,7 @@ public function getAllRuleSetsReturnsOneAtRuleSetDirectlySetAsContent(): void /** * @test */ - public function getAllRuleSetsReturnsMultipleDeclarationBlocksDirectlySetAsContents(): void + public function getAllRuleSetsReturnsRuleSetsFromMultipleDeclarationBlocksDirectlySetAsContents(): void { $subject = new ConcreteCSSBlockList(); @@ -197,7 +197,7 @@ public function getAllRuleSetsReturnsMultipleDeclarationBlocksDirectlySetAsConte $result = $subject->getAllRuleSets(); - self::assertSame([$declarationBlock1, $declarationBlock2], $result); + self::assertSame([$declarationBlock1->getRuleSet(), $declarationBlock2->getRuleSet()], $result); } /** @@ -219,7 +219,7 @@ public function getAllRuleSetsReturnsMultipleAtRuleSetsDirectlySetAsContents(): /** * @test */ - public function getAllRuleSetsReturnsDeclarationBlocksWithinAtRuleBlockList(): void + public function getAllRuleSetsReturnsRuleSetsFromDeclarationBlocksWithinAtRuleBlockList(): void { $subject = new ConcreteCSSBlockList(); @@ -230,7 +230,7 @@ public function getAllRuleSetsReturnsDeclarationBlocksWithinAtRuleBlockList(): v $result = $subject->getAllRuleSets(); - self::assertSame([$declarationBlock], $result); + self::assertSame([$declarationBlock->getRuleSet()], $result); } /** diff --git a/tests/Unit/RuleSet/DeclarationBlockTest.php b/tests/Unit/RuleSet/DeclarationBlockTest.php index 3d85d98c..4b20e9fc 100644 --- a/tests/Unit/RuleSet/DeclarationBlockTest.php +++ b/tests/Unit/RuleSet/DeclarationBlockTest.php @@ -10,7 +10,9 @@ use Sabberworm\CSS\Parsing\ParserState; use Sabberworm\CSS\Position\Positionable; use Sabberworm\CSS\Property\Selector; +use Sabberworm\CSS\Rule\Rule; use Sabberworm\CSS\RuleSet\DeclarationBlock; +use Sabberworm\CSS\RuleSet\RuleSet; use Sabberworm\CSS\Settings; use TRegx\PhpUnit\DataProviders\DataProvider; @@ -19,6 +21,8 @@ */ final class DeclarationBlockTest extends TestCase { + use RuleContainerTest; + /** * @var DeclarationBlock */ @@ -199,4 +203,79 @@ static function (Selector $selectorObject): string { $declarationBlock->getSelectors() ); } + + /** + * @test + */ + public function getRuleSetOnVirginReturnsARuleSet(): void + { + $result = $this->subject->getRuleSet(); + + self::assertInstanceOf(RuleSet::class, $result); + } + + /** + * @test + */ + public function getRuleSetAfterRulesSetReturnsARuleSet(): void + { + $this->subject->setRules([new Rule('color')]); + + $result = $this->subject->getRuleSet(); + + self::assertInstanceOf(RuleSet::class, $result); + } + + /** + * @test + */ + public function getRuleSetOnVirginReturnsObjectWithoutRules(): void + { + $result = $this->subject->getRuleSet(); + + self::assertSame([], $result->getRules()); + } + + /** + * @test + * + * @param list $propertyNamesToSet + * + * @dataProvider providePropertyNames + */ + public function getRuleSetReturnsObjectWithRulesSet(array $propertyNamesToSet): void + { + $rules = self::createRulesFromPropertyNames($propertyNamesToSet); + $this->subject->setRules($rules); + + $result = $this->subject->getRuleSet(); + + self::assertSame($rules, $result->getRules()); + } + + /** + * @test + */ + public function getRuleSetByDefaultReturnsObjectWithNullLineNumber(): void + { + $result = $this->subject->getRuleSet(); + + self::assertNull($result->getLineNumber()); + } + + /** + * @test + * + * @param int<1, max>|null $lineNumber + * + * @dataProvider provideLineNumber + */ + public function getRuleSetReturnsObjectWithLineNumberPassedToConstructor(?int $lineNumber): void + { + $subject = new DeclarationBlock($lineNumber); + + $result = $subject->getRuleSet(); + + self::assertSame($lineNumber, $result->getLineNumber()); + } } From 7413819b1e94b090ef41b45921e42faf9f8061d9 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 26 Jul 2025 19:56:45 +0200 Subject: [PATCH 525/555] [TASK] Update the development tools (#1334) --- .phive/phars.xml | 2 +- composer.json | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.phive/phars.xml b/.phive/phars.xml index 711a07e2..ee251298 100644 --- a/.phive/phars.xml +++ b/.phive/phars.xml @@ -1,5 +1,5 @@ - + diff --git a/composer.json b/composer.json index 3cfe10b3..0ad39b30 100644 --- a/composer.json +++ b/composer.json @@ -29,12 +29,12 @@ "require-dev": { "php-parallel-lint/php-parallel-lint": "1.4.0", "phpstan/extension-installer": "1.4.3", - "phpstan/phpstan": "1.12.27 || 2.1.17", - "phpstan/phpstan-phpunit": "1.4.2 || 2.0.6", - "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.4", + "phpstan/phpstan": "1.12.28 || 2.1.19", + "phpstan/phpstan-phpunit": "1.4.2 || 2.0.7", + "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.6", "phpunit/phpunit": "8.5.42", "rawr/phpunit-data-provider": "3.3.1", - "rector/rector": "1.2.10 || 2.1.1", + "rector/rector": "1.2.10 || 2.1.2", "rector/type-perfect": "1.0.0 || 2.1.0" }, "suggest": { From 75fdfb514ac0448adca7302fd8367491ae75266a Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 27 Jul 2025 00:24:13 +0200 Subject: [PATCH 526/555] [TASK] Prepare release of version 9.0.0 (#1328) Closes #1326 --- .github/dependabot.yml | 4 ++-- CHANGELOG.md | 16 ++++++++++++++-- composer.json | 2 +- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4f6aacc9..48f2db4f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,7 +8,7 @@ updates: interval: "daily" commit-message: prefix: "[Dependabot] " - milestone: 1 + milestone: 9 - package-ecosystem: "composer" directory: "/" @@ -24,4 +24,4 @@ updates: versioning-strategy: "increase" commit-message: prefix: "[Dependabot] " - milestone: 1 + milestone: 9 diff --git a/CHANGELOG.md b/CHANGELOG.md index 506b6b1b..64d3b3f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,20 @@ Please also have a look at our ### Added +### Changed + +### Deprecated + +### Removed + +### Fixed + +### Documentation + +## 9.0.0: New features, deprecation removals and bug fixes + +### Added + - Interface `RuleContainer` for `RuleSet` `Rule` manipulation methods (#1256) - Partial support for CSS Color Module Level 4: - `rgb` and `rgba`, and `hsl` and `hsla` are now aliases (#797} @@ -43,8 +57,6 @@ Please also have a look at our #1187, #1190, #1192, #1193, #1203) - Add visibility to all class/interface constants (#469) -### Deprecated - ### Removed - Remove `getLineNo()` from these classes (use `getLineNumber()` instead): diff --git a/composer.json b/composer.json index 0ad39b30..6d3c3a41 100644 --- a/composer.json +++ b/composer.json @@ -61,7 +61,7 @@ }, "extra": { "branch-alias": { - "dev-main": "9.0.x-dev" + "dev-main": "9.1.x-dev" } }, "scripts": { From 54574e3de2f8cdc91175ffd2337e5c6804a7d729 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sun, 27 Jul 2025 08:24:01 +0100 Subject: [PATCH 527/555] [DOCS] Correct `}` to `)` in changelog (#1354) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64d3b3f2..72a6cb3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ Please also have a look at our - Interface `RuleContainer` for `RuleSet` `Rule` manipulation methods (#1256) - Partial support for CSS Color Module Level 4: - - `rgb` and `rgba`, and `hsl` and `hsla` are now aliases (#797} + - `rgb` and `rgba`, and `hsl` and `hsla` are now aliases (#797) - Parse color functions that use the "modern" syntax (#800) - Render RGB functions with "modern" syntax when required (#840) - Support `none` as color function component value (#859) From c32ebc683f95eecde08a8cf51351e45d292f7658 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 29 Jul 2025 00:24:31 +0200 Subject: [PATCH 528/555] [TASK] Raise PHPStan to level 5 (#1356) --- config/phpstan-baseline.neon | 78 ++++++++++++++++++++++++++++++++++++ config/phpstan.neon | 2 +- 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 486ed7da..1180333e 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -6,6 +6,12 @@ parameters: count: 1 path: ../src/CSSList/CSSList.php + - + message: '#^Parameter \#2 \$found of class Sabberworm\\CSS\\Parsing\\UnexpectedTokenException constructor expects string, Sabberworm\\CSS\\Value\\CSSFunction\|Sabberworm\\CSS\\Value\\CSSString\|Sabberworm\\CSS\\Value\\LineName\|Sabberworm\\CSS\\Value\\Size\|Sabberworm\\CSS\\Value\\URL given\.$#' + identifier: argument.type + count: 1 + path: ../src/CSSList/CSSList.php + - message: '#^Parameters should have "Sabberworm\\CSS\\CSSList\\CSSListItem\|array" types as the only types passed to this method$#' identifier: typePerfect.narrowPublicClassMethodParamType @@ -30,6 +36,18 @@ parameters: count: 1 path: ../src/RuleSet/DeclarationBlock.php + - + message: '#^Parameter \#2 \$arguments of class Sabberworm\\CSS\\Value\\CSSFunction constructor expects array\\|Sabberworm\\CSS\\Value\\RuleValueList, Sabberworm\\CSS\\Value\\Value\|string given\.$#' + identifier: argument.type + count: 1 + path: ../src/Value/CSSFunction.php + + - + message: '#^Parameter \#2 \$offset of method Sabberworm\\CSS\\Parsing\\ParserState\:\:peek\(\) expects int\<0, max\>, \-1 given\.$#' + identifier: argument.type + count: 2 + path: ../src/Value/CalcFunction.php + - message: '#^Cannot call method getSize\(\) on Sabberworm\\CSS\\Value\\Value\|string\.$#' identifier: method.nonObject @@ -48,8 +66,68 @@ parameters: count: 1 path: ../src/Value/Size.php + - + message: '#^Parameter \#2 \$arguments of class Sabberworm\\CSS\\Value\\CSSFunction constructor expects array\\|Sabberworm\\CSS\\Value\\RuleValueList, Sabberworm\\CSS\\Value\\Value\|string given\.$#' + identifier: argument.type + count: 1 + path: ../src/Value/Value.php + + - + message: '#^Parameter \#2 \$offset of method Sabberworm\\CSS\\Parsing\\ParserState\:\:peek\(\) expects int\<0, max\>, \-1 given\.$#' + identifier: argument.type + count: 1 + path: ../src/Value/Value.php + - message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertSame\(\) with ''red'' and Sabberworm\\CSS\\Value\\Value will always evaluate to false\.$#' identifier: staticMethod.impossibleType count: 1 path: ../tests/ParserTest.php + + - + message: '#^Parameter \#1 \$message of static method PHPUnit\\Framework\\Assert\:\:fail\(\) expects string, Exception given\.$#' + identifier: argument.type + count: 1 + path: ../tests/ParserTest.php + + - + message: '#^Parameter \#1 \$value of method Sabberworm\\CSS\\Rule\\Rule\:\:setValue\(\) expects Sabberworm\\CSS\\Value\\RuleValueList\|string\|null, Sabberworm\\CSS\\Value\\Size given\.$#' + identifier: argument.type + count: 3 + path: ../tests/RuleSet/DeclarationBlockTest.php + + - + message: '#^Parameter \#1 \$type of class Sabberworm\\CSS\\CSSList\\AtRuleBlockList constructor expects non\-empty\-string, '''' given\.$#' + identifier: argument.type + count: 3 + path: ../tests/Unit/CSSList/AtRuleBlockListTest.php + + - + message: '#^Parameter \#1 \$value of method Sabberworm\\CSS\\Rule\\Rule\:\:setValue\(\) expects Sabberworm\\CSS\\Value\\RuleValueList\|string\|null, Sabberworm\\CSS\\Value\\CSSFunction given\.$#' + identifier: argument.type + count: 2 + path: ../tests/Unit/CSSList/CSSBlockListTest.php + + - + message: '#^Parameter \#1 \$value of method Sabberworm\\CSS\\Rule\\Rule\:\:setValue\(\) expects Sabberworm\\CSS\\Value\\RuleValueList\|string\|null, Sabberworm\\CSS\\Value\\CSSString given\.$#' + identifier: argument.type + count: 10 + path: ../tests/Unit/CSSList/CSSBlockListTest.php + + - + message: '#^Parameter \#1 \$selectors of method Sabberworm\\CSS\\CSSList\\CSSList\:\:removeDeclarationBlockBySelector\(\) expects array\\|Sabberworm\\CSS\\RuleSet\\DeclarationBlock\|string, array\ given\.$#' + identifier: argument.type + count: 2 + path: ../tests/Unit/CSSList/CSSListTest.php + + - + message: '#^Parameter \#3 \$matchType of class Sabberworm\\CSS\\Parsing\\UnexpectedEOFException constructor expects ''count''\|''custom''\|''expression''\|''identifier''\|''literal''\|''search'', ''coding'' given\.$#' + identifier: argument.type + count: 1 + path: ../tests/Unit/Parsing/UnexpectedEOFExceptionTest.php + + - + message: '#^Parameter \#3 \$matchType of class Sabberworm\\CSS\\Parsing\\UnexpectedTokenException constructor expects ''count''\|''custom''\|''expression''\|''identifier''\|''literal''\|''search'', ''coding'' given\.$#' + identifier: argument.type + count: 1 + path: ../tests/Unit/Parsing/UnexpectedTokenExceptionTest.php diff --git a/config/phpstan.neon b/config/phpstan.neon index 9eee8659..998d565b 100644 --- a/config/phpstan.neon +++ b/config/phpstan.neon @@ -8,7 +8,7 @@ parameters: phpVersion: 70200 - level: 4 + level: 5 paths: - %currentWorkingDirectory%/bin/ From 33db9f7f9ebc9e1e34ee706b49ada10bb7534330 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 30 Jul 2025 00:02:19 +0200 Subject: [PATCH 529/555] [CLEANUP] Ignore warnings for explicitly invalid values in tests (#1358) --- config/phpstan-baseline.neon | 12 ------------ tests/Unit/Parsing/UnexpectedEOFExceptionTest.php | 1 + tests/Unit/Parsing/UnexpectedTokenExceptionTest.php | 1 + 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 1180333e..7cc38e69 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -119,15 +119,3 @@ parameters: identifier: argument.type count: 2 path: ../tests/Unit/CSSList/CSSListTest.php - - - - message: '#^Parameter \#3 \$matchType of class Sabberworm\\CSS\\Parsing\\UnexpectedEOFException constructor expects ''count''\|''custom''\|''expression''\|''identifier''\|''literal''\|''search'', ''coding'' given\.$#' - identifier: argument.type - count: 1 - path: ../tests/Unit/Parsing/UnexpectedEOFExceptionTest.php - - - - message: '#^Parameter \#3 \$matchType of class Sabberworm\\CSS\\Parsing\\UnexpectedTokenException constructor expects ''count''\|''custom''\|''expression''\|''identifier''\|''literal''\|''search'', ''coding'' given\.$#' - identifier: argument.type - count: 1 - path: ../tests/Unit/Parsing/UnexpectedTokenExceptionTest.php diff --git a/tests/Unit/Parsing/UnexpectedEOFExceptionTest.php b/tests/Unit/Parsing/UnexpectedEOFExceptionTest.php index 32a649a0..b3c2ffcc 100644 --- a/tests/Unit/Parsing/UnexpectedEOFExceptionTest.php +++ b/tests/Unit/Parsing/UnexpectedEOFExceptionTest.php @@ -85,6 +85,7 @@ public function messageForInvalidMatchTypeRefersToTokenNotFound(): void $expected = 'tea'; $found = 'coffee'; + // @phpstan-ignore-next-line argument.type We're explicitly testing with an invalid value here. $exception = new UnexpectedEOFException($expected, $found, 'coding'); $expectedMessage = 'Token “' . $expected . '” (coding) not found. Got “' . $found . '”.'; diff --git a/tests/Unit/Parsing/UnexpectedTokenExceptionTest.php b/tests/Unit/Parsing/UnexpectedTokenExceptionTest.php index d4fa9eca..6adb3d7e 100644 --- a/tests/Unit/Parsing/UnexpectedTokenExceptionTest.php +++ b/tests/Unit/Parsing/UnexpectedTokenExceptionTest.php @@ -85,6 +85,7 @@ public function messageForInvalidMatchTypeRefersToTokenNotFound(): void $expected = 'tea'; $found = 'coffee'; + // @phpstan-ignore-next-line argument.type We're explicitly testing with an invalid value here. $exception = new UnexpectedTokenException($expected, $found, 'coding'); $expectedMessage = 'Token “' . $expected . '” (coding) not found. Got “' . $found . '”.'; From 99fcc08609328c40fa0e730e2a39ba1075c00ba2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 17:22:37 +0200 Subject: [PATCH 530/555] [Dependabot] Update phpunit/phpunit requirement from 8.5.42 to 8.5.43 (#1360) Updates the requirements on [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) to permit the latest version. - [Release notes](https://github.com/sebastianbergmann/phpunit/releases) - [Changelog](https://github.com/sebastianbergmann/phpunit/blob/8.5.43/ChangeLog-8.5.md) - [Commits](https://github.com/sebastianbergmann/phpunit/compare/8.5.42...8.5.43) --- updated-dependencies: - dependency-name: phpunit/phpunit dependency-version: 8.5.43 dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 6d3c3a41..bc4bf13b 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "phpstan/phpstan": "1.12.28 || 2.1.19", "phpstan/phpstan-phpunit": "1.4.2 || 2.0.7", "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.6", - "phpunit/phpunit": "8.5.42", + "phpunit/phpunit": "8.5.43", "rawr/phpunit-data-provider": "3.3.1", "rector/rector": "1.2.10 || 2.1.2", "rector/type-perfect": "1.0.0 || 2.1.0" From 31c925fc04a9544b3106ed75f031d2a830a16fa2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 14:05:46 +0200 Subject: [PATCH 531/555] [Dependabot] Bump actions/checkout from 4 to 5 (#1361) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 6 +++--- .github/workflows/codecoverage.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 73e68ff8..6585fbc5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install PHP uses: shivammathur/setup-php@v2 @@ -63,7 +63,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install PHP uses: shivammathur/setup-php@v2 @@ -112,7 +112,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install PHP uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/codecoverage.yml b/.github/workflows/codecoverage.yml index dff38197..29c37ba5 100644 --- a/.github/workflows/codecoverage.yml +++ b/.github/workflows/codecoverage.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install PHP uses: shivammathur/setup-php@v2 From b4b527211f93c66f69ad52406d719e51df1a9115 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Aug 2025 12:12:12 +0200 Subject: [PATCH 532/555] [Dependabot] Update phpunit/phpunit requirement from 8.5.43 to 8.5.44 (#1362) Updates the requirements on [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) to permit the latest version. - [Release notes](https://github.com/sebastianbergmann/phpunit/releases) - [Changelog](https://github.com/sebastianbergmann/phpunit/blob/8.5.44/ChangeLog-8.5.md) - [Commits](https://github.com/sebastianbergmann/phpunit/compare/8.5.43...8.5.44) --- updated-dependencies: - dependency-name: phpunit/phpunit dependency-version: 8.5.44 dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index bc4bf13b..d431ad43 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "phpstan/phpstan": "1.12.28 || 2.1.19", "phpstan/phpstan-phpunit": "1.4.2 || 2.0.7", "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.6", - "phpunit/phpunit": "8.5.43", + "phpunit/phpunit": "8.5.44", "rawr/phpunit-data-provider": "3.3.1", "rector/rector": "1.2.10 || 2.1.2", "rector/type-perfect": "1.0.0 || 2.1.0" From 5c23dc7098585401ba03b336ed489c58aec6f59c Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 21 Aug 2025 18:04:10 +0200 Subject: [PATCH 533/555] [TASK] Raise PHPStan to level 6 (#1364) --- config/phpstan.neon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/phpstan.neon b/config/phpstan.neon index 998d565b..55ba682a 100644 --- a/config/phpstan.neon +++ b/config/phpstan.neon @@ -8,7 +8,7 @@ parameters: phpVersion: 70200 - level: 5 + level: 6 paths: - %currentWorkingDirectory%/bin/ From 25877ed2ec350ec64de508cd605622fe2067d2ec Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 21 Aug 2025 20:00:57 +0200 Subject: [PATCH 534/555] [TASK] Add `thecodingmachine/safe` (#1453) (#1366) Safe-PHP https://github.com/thecodingmachine/safe provides rewrites of PHP functions to throw an exception instead of returning `false` when an error is encountered. This will allow us to drop out custom `preg_*` wrapper class and to increase type safety in our codebase. Also drop the PHP-CS-Fixer rule that adds a trailing backslash to calls to native PHP functions (as this would change the Safe-PHP calls back to their unsafe versions). The actual code changes will come in subsequent commits. Part of #1168 --- .github/dependabot.yml | 1 + composer.json | 3 ++- config/php-cs-fixer.php | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 48f2db4f..ae39e76b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -21,6 +21,7 @@ updates: - dependency-name: "phpunit/phpunit" versions: [ ">= 9.0.0" ] - dependency-name: "rector/rector" + - dependency-name: "thecodingmachine/safe" versioning-strategy: "increase" commit-message: prefix: "[Dependabot] " diff --git a/composer.json b/composer.json index d431ad43..8a1c49a3 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,8 @@ "homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser", "require": { "php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", - "ext-iconv": "*" + "ext-iconv": "*", + "thecodingmachine/safe": "^1.3 || ^2.5 || ^3.3" }, "require-dev": { "php-parallel-lint/php-parallel-lint": "1.4.0", diff --git a/config/php-cs-fixer.php b/config/php-cs-fixer.php index 96b39bbb..c10bf59a 100644 --- a/config/php-cs-fixer.php +++ b/config/php-cs-fixer.php @@ -40,7 +40,6 @@ 'yoda_style' => ['equal' => false, 'identical' => false, 'less_and_greater' => false], // function notation - 'native_function_invocation' => ['include' => ['@all']], 'nullable_type_declaration_for_default_null_value' => true, // import From 64f524daae7800c68fcda9252cab06ff5f9c350e Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Thu, 21 Aug 2025 23:06:34 +0200 Subject: [PATCH 535/555] [BUGFIX] Use the safe `file_get_contents` in `quickdump.php` (#1367) --- bin/quickdump.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bin/quickdump.php b/bin/quickdump.php index c759d028..f694fb94 100755 --- a/bin/quickdump.php +++ b/bin/quickdump.php @@ -3,13 +3,15 @@ declare(strict_types=1); +use function Safe\file_get_contents; + /** * This script is used for generating the examples in the README. */ require_once(__DIR__ . '/../vendor/autoload.php'); -$sSource = \file_get_contents('php://stdin'); +$sSource = file_get_contents('php://stdin'); $oParser = new Sabberworm\CSS\Parser($sSource); $oDoc = $oParser->parse(); From 276dd01bc5279e733fd2ede8cd744ecf6588b6b2 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 22 Aug 2025 17:18:31 +0200 Subject: [PATCH 536/555] [BUGFIX] Use the safe regexp functions in `CSSList` (#1368) --- CHANGELOG.md | 2 ++ src/CSSList/CSSList.php | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72a6cb3b..e3aa7db1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ Please also have a look at our ### Fixed +- Use typesafe versions of PHP functions (#1368) + ### Documentation ## 9.0.0: New features, deprecation removals and bug fixes diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index ee09b79e..e09a03a9 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -25,6 +25,8 @@ use Sabberworm\CSS\Value\URL; use Sabberworm\CSS\Value\Value; +use function Safe\preg_match; + /** * This is the most generic container available. It can contain `DeclarationBlock`s (rule sets with a selector), * `RuleSet`s as well as other `CSSList` objects. @@ -242,7 +244,7 @@ private static function identifierIs(string $identifier, string $match): bool return true; } - return \preg_match("/^(-\\w+-)?$match$/i", $identifier) === 1; + return preg_match("/^(-\\w+-)?$match$/i", $identifier) === 1; } /** From 1e3c2a0388aa28ed1226b1419d018fdccf38af3b Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 22 Aug 2025 17:19:29 +0200 Subject: [PATCH 537/555] [CLEANUP] Avoid Hungarian notation in `quickdump.php` (#1369) --- bin/quickdump.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/bin/quickdump.php b/bin/quickdump.php index f694fb94..dc0fe869 100755 --- a/bin/quickdump.php +++ b/bin/quickdump.php @@ -3,6 +3,8 @@ declare(strict_types=1); +use Sabberworm\CSS\Parser; + use function Safe\file_get_contents; /** @@ -11,17 +13,17 @@ require_once(__DIR__ . '/../vendor/autoload.php'); -$sSource = file_get_contents('php://stdin'); -$oParser = new Sabberworm\CSS\Parser($sSource); +$source = file_get_contents('php://stdin'); +$parser = new Parser($source); -$oDoc = $oParser->parse(); +$document = $parser->parse(); echo "\n" . '#### Input' . "\n\n```css\n"; -print $sSource; +print $source; echo "\n```\n\n" . '#### Structure (`var_dump()`)' . "\n\n```php\n"; -\var_dump($oDoc); +\var_dump($document); echo "\n```\n\n" . '#### Output (`render()`)' . "\n\n```css\n"; -print $oDoc->render(); +print $document->render(); echo "\n```\n"; From 995442b5169adeb4fc2058b9c6d37e796f82b88a Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 23 Aug 2025 17:07:57 +0200 Subject: [PATCH 538/555] [BUGFIX] Use the safe regexp functions in `ParserState` (#1370) Part of #1168 --- CHANGELOG.md | 2 +- src/Parsing/ParserState.php | 25 +++++++++++-------------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3aa7db1..3ef217d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ Please also have a look at our ### Fixed -- Use typesafe versions of PHP functions (#1368) +- Use typesafe versions of PHP functions (#1368, #1370) ### Documentation diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index 74ad0efe..cc69ed97 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -7,6 +7,10 @@ use Sabberworm\CSS\Comment\Comment; use Sabberworm\CSS\Settings; +use function Safe\iconv; +use function Safe\preg_match; +use function Safe\preg_split; + /** * @internal since 8.7.0 */ @@ -63,8 +67,6 @@ public function __construct(string $text, Settings $parserSettings, int $lineNum /** * Sets the charset to be used if the CSS does not contain an `@charset` declaration. - * - * @throws SourceException if the charset is UTF-8 and the content has invalid byte sequences */ public function setCharset(string $charset): void { @@ -122,7 +124,7 @@ public function parseIdentifier(bool $ignoreCase = true): string } $character = null; while (!$this->isEnd() && ($character = $this->parseCharacter(true)) !== null) { - if (\preg_match('/[a-zA-Z0-9\\x{00A0}-\\x{FFFF}_-]/Sux', $character)) { + if (preg_match('/[a-zA-Z0-9\\x{00A0}-\\x{FFFF}_-]/Sux', $character) !== 0) { $result .= $character; } else { $result .= '\\' . $character; @@ -146,13 +148,13 @@ public function parseCharacter(bool $isForIdentifier): ?string if ($this->comes('\\n') || $this->comes('\\r')) { return ''; } - if (\preg_match('/[0-9a-fA-F]/Su', $this->peek()) === 0) { + if (preg_match('/[0-9a-fA-F]/Su', $this->peek()) === 0) { return $this->consume(1); } $hexCodePoint = $this->consumeExpression('/^[0-9a-fA-F]{1,6}/u', 6); if ($this->strlen($hexCodePoint) < 6) { // Consume whitespace after incomplete unicode escape - if (\preg_match('/\\s/isSu', $this->peek())) { + if (preg_match('/\\s/isSu', $this->peek()) !== 0) { if ($this->comes('\\r\\n')) { $this->consume(2); } else { @@ -166,7 +168,7 @@ public function parseCharacter(bool $isForIdentifier): ?string $utf32EncodedCharacter .= \chr($codePoint & 0xff); $codePoint = $codePoint >> 8; } - return \iconv('utf-32le', $this->charset, $utf32EncodedCharacter); + return iconv('utf-32le', $this->charset, $utf32EncodedCharacter); } if ($isForIdentifier) { $peek = \ord($this->peek()); @@ -198,7 +200,7 @@ public function consumeWhiteSpace(): array { $comments = []; do { - while (\preg_match('/\\s/isSu', $this->peek()) === 1) { + while (preg_match('/\\s/isSu', $this->peek()) === 1) { $this->consume(1); } if ($this->parserSettings->usesLenientParsing()) { @@ -291,7 +293,7 @@ public function consumeExpression(string $expression, ?int $maximumLength = null { $matches = null; $input = ($maximumLength !== null) ? $this->peek($maximumLength) : $this->inputLeft(); - if (\preg_match($expression, $input, $matches, PREG_OFFSET_CAPTURE) !== 1) { + if (preg_match($expression, $input, $matches, PREG_OFFSET_CAPTURE) !== 1) { throw new UnexpectedTokenException($expression, $this->peek(5), 'expression', $this->lineNumber); } @@ -437,17 +439,12 @@ private function strtolower(string $string): string /** * @return list - * - * @throws SourceException if the charset is UTF-8 and the string contains invalid byte sequences */ private function strsplit(string $string): array { if ($this->parserSettings->hasMultibyteSupport()) { if ($this->streql($this->charset, 'utf-8')) { - $result = \preg_split('//u', $string, -1, PREG_SPLIT_NO_EMPTY); - if (!\is_array($result)) { - throw new SourceException('`preg_split` failed with error ' . \preg_last_error()); - } + $result = preg_split('//u', $string, -1, PREG_SPLIT_NO_EMPTY); } else { $length = \mb_strlen($string, $this->charset); $result = []; From 8263604b5be05271e118eca898b93272b65be91e Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Mon, 25 Aug 2025 09:03:08 +0100 Subject: [PATCH 539/555] [BUGFIX] Improve selector validation performance (#1372) Avoid [catastrophic backtracking](https://www.regular-expressions.info/catastrophic.html) in selector validation regular expression by using possessive quantifier with mutually exclusive alternations. Also remove outdated description from DocBlock, but add description for extended class summarizing differences. --- CHANGELOG.md | 2 ++ src/Property/KeyframeSelector.php | 44 ++++++++++++++++++++++--------- src/Property/Selector.php | 29 +++++++++++++++----- 3 files changed, 56 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ef217d7..a60c8981 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ Please also have a look at our ### Fixed +- Improve performance of selector validation + (avoiding silent PCRE catastrophic failure) (#1372) - Use typesafe versions of PHP functions (#1368, #1370) ### Documentation diff --git a/src/Property/KeyframeSelector.php b/src/Property/KeyframeSelector.php index 2ab8ca97..47881771 100644 --- a/src/Property/KeyframeSelector.php +++ b/src/Property/KeyframeSelector.php @@ -7,21 +7,41 @@ class KeyframeSelector extends Selector { /** - * regexp for specificity calculations + * This differs from the parent class: + * - comma is not allowed unless escaped or quoted; + * - percentage value is allowed by itself. * - * @var string + * @var non-empty-string * * @internal since 8.5.2 */ public const SELECTOR_VALIDATION_RX = '/ - ^( - (?: - [a-zA-Z0-9\\x{00A0}-\\x{FFFF}_^$|*="\'~\\[\\]()\\-\\s\\.:#+>]* # any sequence of valid unescaped characters - (?:\\\\.)? # a single escaped character - (?:([\'"]).*?(?]++ + | + # one or more escaped characters + (?:\\\\.)++ + | + # quoted text, like in `[id="example"]` + (?: + # opening quote + ([\'"]) + (?: + # sequence of characters except closing quote or backslash + (?:(?!\\g{-1}|\\\\).)++ + | + # one or more escaped characters + (?:\\\\.)++ + )*+ # zero or more times + # closing quote or end (unmatched quote is currently allowed) + (?:\\g{-1}|$) + ) + )*+ # zero or more times + | + # keyframe animation progress percentage (e.g. 50%), untrimmed + \\s*+(\\d++%)\\s*+ + )$ + /ux'; } diff --git a/src/Property/Selector.php b/src/Property/Selector.php index daeed850..df8d0e90 100644 --- a/src/Property/Selector.php +++ b/src/Property/Selector.php @@ -15,19 +15,34 @@ class Selector implements Renderable { /** - * regexp for specificity calculations - * - * @var string + * @var non-empty-string * * @internal since 8.5.2 */ public const SELECTOR_VALIDATION_RX = '/ ^( (?: - [a-zA-Z0-9\\x{00A0}-\\x{FFFF}_^$|*="\'~\\[\\]()\\-\\s\\.:#+>,]* # any sequence of valid unescaped characters - (?:\\\\.)? # a single escaped character - (?:([\'"]).*?(?,]++ + | + # one or more escaped characters + (?:\\\\.)++ + | + # quoted text, like in `[id="example"]` + (?: + # opening quote + ([\'"]) + (?: + # sequence of characters except closing quote or backslash + (?:(?!\\g{-1}|\\\\).)++ + | + # one or more escaped characters + (?:\\\\.)++ + )*+ # zero or more times + # closing quote or end (unmatched quote is currently allowed) + (?:\\g{-1}|$) + ) + )*+ # zero or more times )$ /ux'; From a72e71e9f8715fd0fa676e4624827f00f99378c4 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 25 Aug 2025 14:49:45 +0200 Subject: [PATCH 540/555] [BUGFIX] Use the safe regexp functions in `Selector` (#1371) Part of #1168 --- config/phpstan-baseline.neon | 6 ------ src/Property/Selector.php | 4 +++- tests/ParserTest.php | 6 +----- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon index 7cc38e69..6205096a 100644 --- a/config/phpstan-baseline.neon +++ b/config/phpstan-baseline.neon @@ -84,12 +84,6 @@ parameters: count: 1 path: ../tests/ParserTest.php - - - message: '#^Parameter \#1 \$message of static method PHPUnit\\Framework\\Assert\:\:fail\(\) expects string, Exception given\.$#' - identifier: argument.type - count: 1 - path: ../tests/ParserTest.php - - message: '#^Parameter \#1 \$value of method Sabberworm\\CSS\\Rule\\Rule\:\:setValue\(\) expects Sabberworm\\CSS\\Value\\RuleValueList\|string\|null, Sabberworm\\CSS\\Value\\Size given\.$#' identifier: argument.type diff --git a/src/Property/Selector.php b/src/Property/Selector.php index df8d0e90..a647378e 100644 --- a/src/Property/Selector.php +++ b/src/Property/Selector.php @@ -8,6 +8,8 @@ use Sabberworm\CSS\Property\Selector\SpecificityCalculator; use Sabberworm\CSS\Renderable; +use function Safe\preg_match; + /** * Class representing a single CSS selector. Selectors have to be split by the comma prior to being passed into this * class. @@ -57,7 +59,7 @@ class Selector implements Renderable public static function isValid(string $selector): bool { // Note: We need to use `static::` here as the constant is overridden in the `KeyframeSelector` class. - $numberOfMatches = \preg_match(static::SELECTOR_VALIDATION_RX, $selector); + $numberOfMatches = preg_match(static::SELECTOR_VALIDATION_RX, $selector); return $numberOfMatches === 1; } diff --git a/tests/ParserTest.php b/tests/ParserTest.php index 0217eba9..f6425a0b 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -73,11 +73,7 @@ public function files(): void continue; } $parser = new Parser(\file_get_contents($directory . '/' . $filename)); - try { - self::assertNotEquals('', $parser->parse()->render()); - } catch (\Exception $e) { - self::fail($e); - } + self::assertNotSame('', $parser->parse()->render()); } \closedir($directoryHandle); } From dbddfc384c346632d6036d32cb16fc8d8d9e8e7b Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 25 Aug 2025 17:47:56 +0200 Subject: [PATCH 541/555] [BUGFIX] Use safe `file_get_contents` in `LenientParsingTest` (#1373) Part of #1168 --- tests/RuleSet/LenientParsingTest.php | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/tests/RuleSet/LenientParsingTest.php b/tests/RuleSet/LenientParsingTest.php index c014f021..ae249701 100644 --- a/tests/RuleSet/LenientParsingTest.php +++ b/tests/RuleSet/LenientParsingTest.php @@ -10,6 +10,8 @@ use Sabberworm\CSS\Parsing\UnexpectedTokenException; use Sabberworm\CSS\Settings; +use function Safe\file_get_contents; + /** * @coversNothing */ @@ -23,7 +25,7 @@ public function faultToleranceOff(): void $this->expectException(UnexpectedTokenException::class); $pathToFile = __DIR__ . '/../fixtures/-fault-tolerance.css'; - $parser = new Parser(\file_get_contents($pathToFile), Settings::create()->beStrict()); + $parser = new Parser(file_get_contents($pathToFile), Settings::create()->beStrict()); $parser->parse(); } @@ -33,7 +35,7 @@ public function faultToleranceOff(): void public function faultToleranceOn(): void { $pathToFile = __DIR__ . '/../fixtures/-fault-tolerance.css'; - $parser = new Parser(\file_get_contents($pathToFile), Settings::create()->withLenientParsing(true)); + $parser = new Parser(file_get_contents($pathToFile), Settings::create()->withLenientParsing(true)); $result = $parser->parse(); self::assertSame( '.test1 {}' . "\n" . '.test2 {hello: 2.2;hello: 2000000000000.2;}' . "\n" . '#test {}' . "\n" @@ -50,7 +52,7 @@ public function endToken(): void $this->expectException(UnexpectedTokenException::class); $pathToFile = __DIR__ . '/../fixtures/-end-token.css'; - $parser = new Parser(\file_get_contents($pathToFile), Settings::create()->beStrict()); + $parser = new Parser(file_get_contents($pathToFile), Settings::create()->beStrict()); $parser->parse(); } @@ -62,7 +64,7 @@ public function endToken2(): void $this->expectException(UnexpectedTokenException::class); $pathToFile = __DIR__ . '/../fixtures/-end-token-2.css'; - $parser = new Parser(\file_get_contents($pathToFile), Settings::create()->beStrict()); + $parser = new Parser(file_get_contents($pathToFile), Settings::create()->beStrict()); $parser->parse(); } @@ -72,7 +74,7 @@ public function endToken2(): void public function endTokenPositive(): void { $pathToFile = __DIR__ . '/../fixtures/-end-token.css'; - $parser = new Parser(\file_get_contents($pathToFile), Settings::create()->withLenientParsing(true)); + $parser = new Parser(file_get_contents($pathToFile), Settings::create()->withLenientParsing(true)); $result = $parser->parse(); self::assertSame('', $result->render()); } @@ -83,7 +85,7 @@ public function endTokenPositive(): void public function endToken2Positive(): void { $pathToFile = __DIR__ . '/../fixtures/-end-token-2.css'; - $parser = new Parser(\file_get_contents($pathToFile), Settings::create()->withLenientParsing(true)); + $parser = new Parser(file_get_contents($pathToFile), Settings::create()->withLenientParsing(true)); $result = $parser->parse(); self::assertSame( '#home .bg-layout {background-image: url("/bundles/main/img/bg1.png?5");}', @@ -98,7 +100,7 @@ public function localeTrap(): void { \setlocale(LC_ALL, 'pt_PT', 'no'); $pathToFile = __DIR__ . '/../fixtures/-fault-tolerance.css'; - $parser = new Parser(\file_get_contents($pathToFile), Settings::create()->withLenientParsing(true)); + $parser = new Parser(file_get_contents($pathToFile), Settings::create()->withLenientParsing(true)); $result = $parser->parse(); self::assertSame( '.test1 {}' . "\n" . '.test2 {hello: 2.2;hello: 2000000000000.2;}' . "\n" . '#test {}' . "\n" @@ -113,7 +115,7 @@ public function localeTrap(): void public function caseInsensitivity(): void { $pathToFile = __DIR__ . '/../fixtures/case-insensitivity.css'; - $parser = new Parser(\file_get_contents($pathToFile)); + $parser = new Parser(file_get_contents($pathToFile)); $result = $parser->parse(); self::assertSame( @@ -132,7 +134,7 @@ public function caseInsensitivity(): void public function cssWithInvalidColorStillGetsParsedAsDocument(): void { $pathToFile = __DIR__ . '/../fixtures/invalid-color.css'; - $parser = new Parser(\file_get_contents($pathToFile), Settings::create()->withLenientParsing(true)); + $parser = new Parser(file_get_contents($pathToFile), Settings::create()->withLenientParsing(true)); $result = $parser->parse(); self::assertInstanceOf(Document::class, $result); @@ -146,7 +148,7 @@ public function invalidColorStrict(): void $this->expectException(UnexpectedTokenException::class); $pathToFile = __DIR__ . '/../fixtures/invalid-color.css'; - $parser = new Parser(\file_get_contents($pathToFile), Settings::create()->beStrict()); + $parser = new Parser(file_get_contents($pathToFile), Settings::create()->beStrict()); $parser->parse(); } } From 6bdbc432d6a88ae95de2fcabdc94f9131ffc987b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Sep 2025 17:22:00 +0100 Subject: [PATCH 542/555] [Dependabot] Update phpunit/phpunit requirement from 8.5.44 to 8.5.45 (#1375) Updates the requirements on [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) to permit the latest version. - [Release notes](https://github.com/sebastianbergmann/phpunit/releases) - [Changelog](https://github.com/sebastianbergmann/phpunit/blob/8.5.45/ChangeLog-8.5.md) - [Commits](https://github.com/sebastianbergmann/phpunit/compare/8.5.44...8.5.45) --- updated-dependencies: - dependency-name: phpunit/phpunit dependency-version: 8.5.45 dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 8a1c49a3..6fda45a0 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "phpstan/phpstan": "1.12.28 || 2.1.19", "phpstan/phpstan-phpunit": "1.4.2 || 2.0.7", "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.6", - "phpunit/phpunit": "8.5.44", + "phpunit/phpunit": "8.5.45", "rawr/phpunit-data-provider": "3.3.1", "rector/rector": "1.2.10 || 2.1.2", "rector/type-perfect": "1.0.0 || 2.1.0" From 2212ada34d58f8f5c33ac2fcb26e66089f3f9067 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 12 Sep 2025 22:49:27 +0200 Subject: [PATCH 543/555] [TASK] Update the development tools (#1353) --- .phive/phars.xml | 4 ++-- composer.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.phive/phars.xml b/.phive/phars.xml index ee251298..d9ab49f3 100644 --- a/.phive/phars.xml +++ b/.phive/phars.xml @@ -1,5 +1,5 @@ - - + + diff --git a/composer.json b/composer.json index 6fda45a0..5cf149d7 100644 --- a/composer.json +++ b/composer.json @@ -30,12 +30,12 @@ "require-dev": { "php-parallel-lint/php-parallel-lint": "1.4.0", "phpstan/extension-installer": "1.4.3", - "phpstan/phpstan": "1.12.28 || 2.1.19", + "phpstan/phpstan": "1.12.28 || 2.1.25", "phpstan/phpstan-phpunit": "1.4.2 || 2.0.7", "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.6", "phpunit/phpunit": "8.5.45", "rawr/phpunit-data-provider": "3.3.1", - "rector/rector": "1.2.10 || 2.1.2", + "rector/rector": "1.2.10 || 2.1.7", "rector/type-perfect": "1.0.0 || 2.1.0" }, "suggest": { From 3b723d884b3f09cfcf9a474367f565a29d698517 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 12 Sep 2025 22:54:14 +0200 Subject: [PATCH 544/555] [FEATURE] Add support for PHP 8.5 (#1355) --- .github/workflows/ci.yml | 4 ++-- CHANGELOG.md | 2 ++ composer.json | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6585fbc5..251e8fc4 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: [ '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4' ] + php-version: [ '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5' ] steps: - name: Checkout @@ -59,7 +59,7 @@ jobs: strategy: fail-fast: false matrix: - php-version: [ '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4' ] + php-version: [ '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5' ] steps: - name: Checkout diff --git a/CHANGELOG.md b/CHANGELOG.md index a60c8981..59773494 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ Please also have a look at our ### Added +- Add support for PHP 8.5 (#1355) + ### Changed ### Deprecated diff --git a/composer.json b/composer.json index 5cf149d7..9b17f01b 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ ], "homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser", "require": { - "php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", + "php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", "ext-iconv": "*", "thecodingmachine/safe": "^1.3 || ^2.5 || ^3.3" }, From a727341dd41c9fab020363e1009fd87b7ae6113f Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sat, 13 Sep 2025 18:08:02 +0200 Subject: [PATCH 545/555] [TASK] Prepare release of version 9.1.0 (#1376) --- .github/dependabot.yml | 4 ++-- CHANGELOG.md | 14 ++++++++++---- composer.json | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ae39e76b..2d4dc55a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,7 +8,7 @@ updates: interval: "daily" commit-message: prefix: "[Dependabot] " - milestone: 9 + milestone: 10 - package-ecosystem: "composer" directory: "/" @@ -25,4 +25,4 @@ updates: versioning-strategy: "increase" commit-message: prefix: "[Dependabot] " - milestone: 9 + milestone: 10 diff --git a/CHANGELOG.md b/CHANGELOG.md index 59773494..83705952 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,8 +10,6 @@ Please also have a look at our ### Added -- Add support for PHP 8.5 (#1355) - ### Changed ### Deprecated @@ -20,12 +18,20 @@ Please also have a look at our ### Fixed +### Documentation + +## 9.1.0: Add support for PHP 8.5 + +### Added + +- Add support for PHP 8.5 (#1355) + +### Fixed + - Improve performance of selector validation (avoiding silent PCRE catastrophic failure) (#1372) - Use typesafe versions of PHP functions (#1368, #1370) -### Documentation - ## 9.0.0: New features, deprecation removals and bug fixes ### Added diff --git a/composer.json b/composer.json index 9b17f01b..a9f1ca57 100644 --- a/composer.json +++ b/composer.json @@ -62,7 +62,7 @@ }, "extra": { "branch-alias": { - "dev-main": "9.1.x-dev" + "dev-main": "9.2.x-dev" } }, "scripts": { From 1b363fdbdc6dd0ca0f4bf98d3a4d7f388133f1fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 Sep 2025 09:37:21 +0200 Subject: [PATCH 546/555] [Dependabot] Update phpunit/phpunit requirement from 8.5.45 to 8.5.46 (#1377) Updates the requirements on [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) to permit the latest version. - [Release notes](https://github.com/sebastianbergmann/phpunit/releases) - [Changelog](https://github.com/sebastianbergmann/phpunit/blob/8.5.46/ChangeLog-8.5.md) - [Commits](https://github.com/sebastianbergmann/phpunit/compare/8.5.45...8.5.46) --- updated-dependencies: - dependency-name: phpunit/phpunit dependency-version: 8.5.46 dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index a9f1ca57..0a06263d 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "phpstan/phpstan": "1.12.28 || 2.1.25", "phpstan/phpstan-phpunit": "1.4.2 || 2.0.7", "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.6", - "phpunit/phpunit": "8.5.45", + "phpunit/phpunit": "8.5.46", "rawr/phpunit-data-provider": "3.3.1", "rector/rector": "1.2.10 || 2.1.7", "rector/type-perfect": "1.0.0 || 2.1.0" From 629d38084d4a01a581d26627a343515473475efa Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Sun, 14 Sep 2025 23:18:53 +0200 Subject: [PATCH 547/555] [BUGFIX] Use safe file functions in `ParserTest` (#1378) Part of #1168 --- tests/ParserTest.php | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/tests/ParserTest.php b/tests/ParserTest.php index f6425a0b..b80280a7 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -30,6 +30,9 @@ use Sabberworm\CSS\Value\URL; use Sabberworm\CSS\Value\ValueList; +use function Safe\file_get_contents; +use function Safe\opendir; + /** * @covers \Sabberworm\CSS\Parser */ @@ -58,25 +61,26 @@ public function parseForOneDeclarationBlockReturnsDocumentWithOneDeclarationBloc public function files(): void { $directory = __DIR__ . '/fixtures'; - if ($directoryHandle = \opendir($directory)) { - /* This is the correct way to loop over the directory. */ - while (false !== ($filename = \readdir($directoryHandle))) { - if (\strpos($filename, '.') === 0) { - continue; - } - if (\strrpos($filename, '.css') !== \strlen($filename) - \strlen('.css')) { - continue; - } - if (\strpos($filename, '-') === 0) { - // Either a file which SHOULD fail (at least in strict mode) - // or a future test of an as-of-now missing feature - continue; - } - $parser = new Parser(\file_get_contents($directory . '/' . $filename)); - self::assertNotSame('', $parser->parse()->render()); + $directoryHandle = opendir($directory); + + /* This is the correct way to loop over the directory. */ + while (false !== ($filename = \readdir($directoryHandle))) { + if (\strpos($filename, '.') === 0) { + continue; } - \closedir($directoryHandle); + if (\strrpos($filename, '.css') !== \strlen($filename) - \strlen('.css')) { + continue; + } + if (\strpos($filename, '-') === 0) { + // Either a file which SHOULD fail (at least in strict mode) + // or a future test of an as-of-now missing feature + continue; + } + $parser = new Parser(file_get_contents($directory . '/' . $filename)); + self::assertNotSame('', $parser->parse()->render()); } + + \closedir($directoryHandle); } /** @@ -943,7 +947,7 @@ public function missingPropertyValueLenient(): void public static function parsedStructureForFile($filename, $settings = null): Document { $filename = __DIR__ . "/fixtures/$filename.css"; - $parser = new Parser(\file_get_contents($filename), $settings); + $parser = new Parser(file_get_contents($filename), $settings); return $parser->parse(); } From c1929d433d93a75815c52ec1abd4d32b5685a3c7 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Mon, 15 Sep 2025 14:51:20 +0200 Subject: [PATCH 548/555] [BUGFIX] Use safe preg functions in `Value` (#1379) Also use typesafe comparisons in the affected line. Part of #1168 --- CHANGELOG.md | 2 ++ src/Value/Value.php | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83705952..7052cd6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ Please also have a look at our ### Fixed +- Use typesafe versions of PHP functions (#1379) + ### Documentation ## 9.1.0: Add support for PHP 8.5 diff --git a/src/Value/Value.php b/src/Value/Value.php index 9f4fe1e6..263c420d 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -12,6 +12,8 @@ use Sabberworm\CSS\Position\Position; use Sabberworm\CSS\Position\Positionable; +use function Safe\preg_match; + /** * Abstract base class for specific classes of CSS values: `Size`, `Color`, `CSSString` and `URL`, and another * abstract subclass `ValueList`. @@ -204,7 +206,9 @@ private static function parseUnicodeRangeValue(ParserState $parserState): string $codepointMaxLength = 13; // Max length is 2 six-digit code points + the dash(-) between them } $range .= $parserState->consume(1); - } while (\strlen($range) < $codepointMaxLength && \preg_match('/[A-Fa-f0-9\\?-]/', $parserState->peek())); + } while ( + (\strlen($range) < $codepointMaxLength) && (preg_match('/[A-Fa-f0-9\\?-]/', $parserState->peek()) === 1) + ); return "U+{$range}"; } From 9ad776b64dde25c10950537f5a41e37a9c46cd29 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Tue, 16 Sep 2025 10:29:33 +0200 Subject: [PATCH 549/555] [BUGFIX] Use safe preg functions in `Size` (#1380) Also use typesafe comparisons in the affected line. Part of #1168 --- CHANGELOG.md | 2 +- src/Value/Size.php | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7052cd6b..85b7392c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ Please also have a look at our ### Fixed -- Use typesafe versions of PHP functions (#1379) +- Use typesafe versions of PHP functions (#1379, #1380) ### Documentation diff --git a/src/Value/Size.php b/src/Value/Size.php index 74e8baf7..eac736d7 100644 --- a/src/Value/Size.php +++ b/src/Value/Size.php @@ -9,6 +9,9 @@ use Sabberworm\CSS\Parsing\UnexpectedEOFException; use Sabberworm\CSS\Parsing\UnexpectedTokenException; +use function Safe\preg_match; +use function Safe\preg_replace; + /** * A `Size` consists of a numeric `size` value and a unit. */ @@ -202,9 +205,9 @@ public function render(OutputFormat $outputFormat): string { $locale = \localeconv(); $decimalPoint = \preg_quote($locale['decimal_point'], '/'); - $size = \preg_match('/[\\d\\.]+e[+-]?\\d+/i', (string) $this->size) - ? \preg_replace("/$decimalPoint?0+$/", '', \sprintf('%f', $this->size)) : (string) $this->size; + $size = preg_match('/[\\d\\.]+e[+-]?\\d+/i', (string) $this->size) === 1 + ? preg_replace("/$decimalPoint?0+$/", '', \sprintf('%f', $this->size)) : (string) $this->size; - return \preg_replace(["/$decimalPoint/", '/^(-?)0\\./'], ['.', '$1.'], $size) . ($this->unit ?? ''); + return preg_replace(["/$decimalPoint/", '/^(-?)0\\./'], ['.', '$1.'], $size) . ($this->unit ?? ''); } } From 6bfa357588e538fb9bd222003b5680b89dc34566 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 19 Sep 2025 17:13:56 +0200 Subject: [PATCH 550/555] [BUGFIX] Use safe preg functions in `CSSString` (#1382) Part of #1168 --- CHANGELOG.md | 2 +- src/Value/CSSString.php | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85b7392c..c3d18917 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ Please also have a look at our ### Fixed -- Use typesafe versions of PHP functions (#1379, #1380) +- Use typesafe versions of PHP functions (#1379, #1380, #1382) ### Documentation diff --git a/src/Value/CSSString.php b/src/Value/CSSString.php index 8c4cf656..569311d7 100644 --- a/src/Value/CSSString.php +++ b/src/Value/CSSString.php @@ -10,6 +10,8 @@ use Sabberworm\CSS\Parsing\UnexpectedEOFException; use Sabberworm\CSS\Parsing\UnexpectedTokenException; +use function Safe\preg_match; + /** * This class is a wrapper for quoted strings to distinguish them from keywords. * @@ -54,7 +56,7 @@ public static function parse(ParserState $parserState): CSSString $content = null; if ($quote === null) { // Unquoted strings end in whitespace or with braces, brackets, parentheses - while (\preg_match('/[\\s{}()<>\\[\\]]/isu', $parserState->peek()) !== 1) { + while (preg_match('/[\\s{}()<>\\[\\]]/isu', $parserState->peek()) === 0) { $result .= $parserState->parseCharacter(false); } } else { From 3bc7e7f0ddc30fc0709c7123905d7ca767e0d1c1 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 19 Sep 2025 17:39:17 +0200 Subject: [PATCH 551/555] [BUGFIX] Use safe preg functions in `Rule` (#1383) Part of #1168 --- CHANGELOG.md | 2 +- src/Rule/Rule.php | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3d18917..cb49ebbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ Please also have a look at our ### Fixed -- Use typesafe versions of PHP functions (#1379, #1380, #1382) +- Use typesafe versions of PHP functions (#1379, #1380, #1382, #1383) ### Documentation diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index 8e2e81d0..96ef1b4d 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -17,6 +17,8 @@ use Sabberworm\CSS\Value\RuleValueList; use Sabberworm\CSS\Value\Value; +use function Safe\preg_match; + /** * `Rule`s just have a string key (the rule) and a 'Value'. * @@ -100,7 +102,7 @@ public static function parse(ParserState $parserState, array $commentsBeforeRule */ private static function listDelimiterForRule(string $rule): array { - if (\preg_match('/^font($|-)/', $rule)) { + if (preg_match('/^font($|-)/', $rule) === 1) { return [',', '/', ' ']; } From e788f3e213661a8ef9868e74cc0d2bf571bf16be Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 19 Sep 2025 18:04:05 +0200 Subject: [PATCH 552/555] [BUGFIX] Use safe preg functions in `SpecificityCalculator` (#1384) Part of #1168 --- CHANGELOG.md | 2 +- src/Property/Selector/SpecificityCalculator.php | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb49ebbd..8978fbb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ Please also have a look at our ### Fixed -- Use typesafe versions of PHP functions (#1379, #1380, #1382, #1383) +- Use typesafe versions of PHP functions (#1379, #1380, #1382, #1383, #1384) ### Documentation diff --git a/src/Property/Selector/SpecificityCalculator.php b/src/Property/Selector/SpecificityCalculator.php index 745f229d..b2f1323e 100644 --- a/src/Property/Selector/SpecificityCalculator.php +++ b/src/Property/Selector/SpecificityCalculator.php @@ -4,6 +4,8 @@ namespace Sabberworm\CSS\Property\Selector; +use function Safe\preg_match_all; + /** * Utility class to calculate the specificity of a CSS selector. * @@ -67,8 +69,8 @@ public static function calculate(string $selector): int /// @todo should exclude \# as well as "#" $matches = null; $b = \substr_count($selector, '#'); - $c = \preg_match_all(self::NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX, $selector, $matches); - $d = \preg_match_all(self::ELEMENTS_AND_PSEUDO_ELEMENTS_RX, $selector, $matches); + $c = preg_match_all(self::NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX, $selector, $matches); + $d = preg_match_all(self::ELEMENTS_AND_PSEUDO_ELEMENTS_RX, $selector, $matches); self::$cache[$selector] = ($a * 1000) + ($b * 100) + ($c * 10) + $d; } From 9516df155a625c79b870ed5ce4e7cfa28ab8e438 Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Fri, 19 Sep 2025 18:29:36 +0200 Subject: [PATCH 553/555] [TASK] Add PHPStan rules for Safe-PHP (#1385) This will prevent unsafe function usage from getting added. Closes #1168 --- .github/dependabot.yml | 1 + composer.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 2d4dc55a..76a72c26 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -22,6 +22,7 @@ updates: versions: [ ">= 9.0.0" ] - dependency-name: "rector/rector" - dependency-name: "thecodingmachine/safe" + - dependency-name: "thecodingmachine/phpstan-safe-rule" versioning-strategy: "increase" commit-message: prefix: "[Dependabot] " diff --git a/composer.json b/composer.json index 0a06263d..18c0deba 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,8 @@ "phpunit/phpunit": "8.5.46", "rawr/phpunit-data-provider": "3.3.1", "rector/rector": "1.2.10 || 2.1.7", - "rector/type-perfect": "1.0.0 || 2.1.0" + "rector/type-perfect": "1.0.0 || 2.1.0", + "thecodingmachine/phpstan-safe-rule": "1.2.0 || 1.4.1" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" From aa54150556dc7e793e1c7c5b73f081522d6e47b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 13:16:36 +0200 Subject: [PATCH 554/555] [Dependabot] Update phpunit/phpunit requirement from 8.5.46 to 8.5.47 (#1386) Updates the requirements on [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) to permit the latest version. - [Release notes](https://github.com/sebastianbergmann/phpunit/releases) - [Changelog](https://github.com/sebastianbergmann/phpunit/blob/8.5.47/ChangeLog-8.5.md) - [Commits](https://github.com/sebastianbergmann/phpunit/compare/8.5.46...8.5.47) --- updated-dependencies: - dependency-name: phpunit/phpunit dependency-version: 8.5.47 dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 18c0deba..76880c6d 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "phpstan/phpstan": "1.12.28 || 2.1.25", "phpstan/phpstan-phpunit": "1.4.2 || 2.0.7", "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.6", - "phpunit/phpunit": "8.5.46", + "phpunit/phpunit": "8.5.47", "rawr/phpunit-data-provider": "3.3.1", "rector/rector": "1.2.10 || 2.1.7", "rector/type-perfect": "1.0.0 || 2.1.0", From 92432abc08758613a166fc5af5fa89c15aca4af3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Sep 2025 11:06:36 +0000 Subject: [PATCH 555/555] [Dependabot] Update phpunit/phpunit requirement from 8.5.47 to 8.5.48 (#1387) Updates the requirements on [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) to permit the latest version. - [Release notes](https://github.com/sebastianbergmann/phpunit/releases) - [Changelog](https://github.com/sebastianbergmann/phpunit/blob/8.5.48/ChangeLog-8.5.md) - [Commits](https://github.com/sebastianbergmann/phpunit/compare/8.5.47...8.5.48) --- updated-dependencies: - dependency-name: phpunit/phpunit dependency-version: 8.5.48 dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 76880c6d..bec30b48 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "phpstan/phpstan": "1.12.28 || 2.1.25", "phpstan/phpstan-phpunit": "1.4.2 || 2.0.7", "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.6", - "phpunit/phpunit": "8.5.47", + "phpunit/phpunit": "8.5.48", "rawr/phpunit-data-provider": "3.3.1", "rector/rector": "1.2.10 || 2.1.7", "rector/type-perfect": "1.0.0 || 2.1.0",