diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 951992d2..2bdb67c1 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,8 +1,10 @@
# https://help.github.com/en/categories/automating-your-workflow-with-github-actions
on:
- pull_request:
push:
+ branches:
+ - main
+ pull_request:
schedule:
- cron: '3 3 * * 1'
@@ -14,11 +16,11 @@ jobs:
runs-on: ubuntu-22.04
strategy:
matrix:
- php-version: [ '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2' ]
+ php-version: [ '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3' ]
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Install PHP
uses: shivammathur/setup-php@v2
@@ -39,15 +41,11 @@ jobs:
strategy:
fail-fast: false
matrix:
- php-version: [ '5.6', '7.0', '7.1', '7.2', '7.3' ]
- coverage: [ 'none' ]
- include:
- - php-version: '7.4'
- coverage: xdebug
+ php-version: [ '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3' ]
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Install PHP
uses: shivammathur/setup-php@v2
@@ -55,13 +53,13 @@ jobs:
php-version: ${{ matrix.php-version }}
ini-values: error_reporting=E_ALL
tools: composer:v2
- coverage: "${{ matrix.coverage }}"
+ coverage: none
- name: Show the Composer configuration
run: composer config --global --list
- name: Cache dependencies installed with composer
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ~/.cache/composer
key: php${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.json') }}
@@ -74,14 +72,7 @@ jobs:
composer show;
- name: Run Tests
- run: ./vendor/bin/phpunit --coverage-clover build/coverage/xml
-
- - name: Upload coverage results to Codacy
- env:
- CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
- if: "${{ matrix.coverage != 'none' && env.CODACY_PROJECT_TOKEN != '' }}"
- run: |
- ./vendor/bin/codacycoverage clover build/coverage/xml
+ run: ./vendor/bin/phpunit
static-analysis:
name: Static Analysis
@@ -93,17 +84,15 @@ jobs:
strategy:
fail-fast: false
matrix:
- include:
- - command: sniffer
- php-version: '7.4'
- - command: fixer
- php-version: '7.4'
- - command: stan
- php-version: '7.4'
+ command:
+ - fixer
+ - stan
+ php-version:
+ - '8.3'
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Install PHP
uses: shivammathur/setup-php@v2
@@ -116,7 +105,7 @@ jobs:
run: composer config --global --list
- name: Cache dependencies installed with composer
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ~/.cache/composer
key: php${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.json') }}
@@ -130,7 +119,7 @@ jobs:
- name: Install development tools
run: |
- phive --no-progress install --trust-gpg-keys BBAB5DF0A0D6672989CF1869E82B2FB314E9906E,A972B9ABB95D0B760B51442231C7E470E2138192,D32680D5957DC7116BE29C14CF1A108D0E7AE720
+ phive --no-progress install --trust-gpg-keys BBAB5DF0A0D6672989CF1869E82B2FB314E9906E
- name: Run Command
run: composer ci:php:${{ matrix.command }}
diff --git a/.github/workflows/codecoverage.yml b/.github/workflows/codecoverage.yml
new file mode 100644
index 00000000..adcca2ee
--- /dev/null
+++ b/.github/workflows/codecoverage.yml
@@ -0,0 +1,57 @@
+# https://help.github.com/en/categories/automating-your-workflow-with-github-actions
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+
+name: Code coverage
+
+jobs:
+ code-coverage:
+ name: Code coverage
+
+ runs-on: ubuntu-22.04
+
+ strategy:
+ matrix:
+ php-version: [ '7.4' ]
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Install PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php-version }}
+ ini-values: error_reporting=E_ALL
+ tools: composer:v2
+ coverage: xdebug
+
+ - name: Show the Composer configuration
+ run: composer config --global --list
+
+ - name: Cache dependencies installed with composer
+ uses: actions/cache@v4
+ with:
+ path: ~/.cache/composer
+ key: php${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.json') }}
+ restore-keys: |
+ php${{ matrix.php-version }}-composer-
+
+ - name: Install Composer dependencies
+ run: |
+ composer update --with-dependencies --no-progress;
+ composer show;
+
+ - name: Run Tests
+ run: ./vendor/bin/phpunit --coverage-clover build/coverage/xml
+
+ - name: Upload coverage results to Codacy
+ env:
+ CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
+ if: "${{ env.CODACY_PROJECT_TOKEN != '' }}"
+ run: |
+ ./vendor/bin/codacycoverage clover build/coverage/xml
diff --git a/.gitignore b/.gitignore
index c1747f26..acf0d9d8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
/.phive/*
/.php-cs-fixer.cache
/.php_cs.cache
+/.phpunit.result.cache
/composer.lock
/phpstan.neon
/vendor/
diff --git a/.phive/phars.xml b/.phive/phars.xml
index d353fbf9..8b65eea8 100644
--- a/.phive/phars.xml
+++ b/.phive/phars.xml
@@ -1,7 +1,4 @@
-
-
-
-
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a23fd0e6..f31a4a61 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,47 @@
-# Revision History
+# Changelog
+
+All notable changes to this project will be documented in this file.
+This project adheres to [Semantic Versioning](https://semver.org/).
+
+## x.y.z
+
+### Added
+
+- Add a class diagram to the README (#482)
+- Add support for the `dvh`, `lvh` and `svh` length units (#415)
+- Add more tests (#449)
+
+### Changed
+
+- Improve performance of Value::parseValue with many delimiters by refactoring to remove array_search()
+- Add visibility to all class/interface constants (#469)
+
+### Deprecated
+
+### Removed
+
+- Drop support for PHP < 7.2 (#420)
+
+### 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
+
+- Add a method to get an import's media queries (#384)
+- Add more unit tests (#381, #382)
+
+### Fixed
+
+- Retain CSSList and Rule comments when rendering CSS (#351)
+- Replace invalid `turns` unit with `turn` (#350)
+- Also allow string values for rules (#348)
+- Fix invalid calc parsing (#169)
+- Handle scientific notation when parsing sizes (#179)
+- Fix PHP 8.1 compatibility in `ParserState::strsplit()` (#344)
## 8.4.0
diff --git a/README.md b/README.md
index 90428cbf..9dab4549 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# PHP CSS Parser
-[![Build Status](https://github.com/sabberworm/PHP-CSS-Parser/workflows/CI/badge.svg?branch=master)](https://github.com/sabberworm/PHP-CSS-Parser/actions/)
+[![Build Status](https://github.com/MyIntervals/PHP-CSS-Parser/workflows/CI/badge.svg?branch=main)](https://github.com/MyIntervals/PHP-CSS-Parser/actions/)
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.
@@ -158,7 +158,7 @@ foreach($cssDocument->getAllRuleSets() as $oRuleSet) {
// Note that the added dash will make this remove all rules starting with
// `font-` (like `font-size`, `font-weight`, etc.) as well as a potential
// `font` rule.
- $oRuleSet->removeRule('font-');
+ $oRuleSet->removeRule('font-');
$oRuleSet->removeRule('cursor');
}
```
@@ -615,6 +615,170 @@ class Sabberworm\CSS\CSSList\Document#4 (2) {
#header {margin: 10px 2em 1cm 2%;font-family: Verdana,Helvetica,"Gill Sans",sans-serif;color: red !important;}
```
+## Class diagram
+
+```mermaid
+classDiagram
+ direction LR
+
+ %% Start of the part generated from the PHP code using tasuku43/mermaid-class-diagram
+
+ class Renderable {
+ <>
+ }
+ class DeclarationBlock {
+ }
+ class RuleSet {
+ <>
+ }
+ class AtRuleSet {
+ }
+ class KeyframeSelector {
+ }
+ class AtRule {
+ <>
+ }
+ class Charset {
+ }
+ class Import {
+ }
+ class Selector {
+ }
+ class CSSNamespace {
+ }
+ class Settings {
+ }
+ class Rule {
+ }
+ class Parser {
+ }
+ class OutputFormatter {
+ }
+ class OutputFormat {
+ }
+ class OutputException {
+ }
+ class UnexpectedEOFException {
+ }
+ class SourceException {
+ }
+ class UnexpectedTokenException {
+ }
+ class ParserState {
+ }
+ class Anchor {
+ }
+ class CSSBlockList {
+ <>
+ }
+ class Document {
+ }
+ class CSSList {
+ <>
+ }
+ class KeyFrame {
+ }
+ class AtRuleBlockList {
+ }
+ class Color {
+ }
+ class URL {
+ }
+ class CalcRuleValueList {
+ }
+ class ValueList {
+ <>
+ }
+ class CalcFunction {
+ }
+ class LineName {
+ }
+ class Value {
+ <>
+ }
+ class Size {
+ }
+ class CSSString {
+ }
+ class PrimitiveValue {
+ <>
+ }
+ class CSSFunction {
+ }
+ class RuleValueList {
+ }
+ class Commentable {
+ <>
+ }
+ class Comment {
+ }
+
+ RuleSet <|-- DeclarationBlock: inheritance
+ Renderable <|.. RuleSet: realization
+ Commentable <|.. RuleSet: realization
+ RuleSet <|-- AtRuleSet: inheritance
+ AtRule <|.. AtRuleSet: realization
+ Selector <|-- KeyframeSelector: inheritance
+ Renderable <|-- AtRule: inheritance
+ Commentable <|-- AtRule: inheritance
+ AtRule <|.. Charset: realization
+ AtRule <|.. Import: realization
+ AtRule <|.. CSSNamespace: realization
+ Renderable <|.. Rule: realization
+ Commentable <|.. Rule: realization
+ SourceException <|-- OutputException: inheritance
+ UnexpectedTokenException <|-- UnexpectedEOFException: inheritance
+ Exception <|-- SourceException: inheritance
+ SourceException <|-- UnexpectedTokenException: inheritance
+ CSSList <|-- CSSBlockList: inheritance
+ CSSBlockList <|-- Document: inheritance
+ Renderable <|.. CSSList: realization
+ Commentable <|.. CSSList: realization
+ CSSList <|-- KeyFrame: inheritance
+ AtRule <|.. KeyFrame: realization
+ CSSBlockList <|-- AtRuleBlockList: inheritance
+ AtRule <|.. AtRuleBlockList: realization
+ CSSFunction <|-- Color: inheritance
+ PrimitiveValue <|-- URL: inheritance
+ RuleValueList <|-- CalcRuleValueList: inheritance
+ Value <|-- ValueList: inheritance
+ CSSFunction <|-- CalcFunction: inheritance
+ ValueList <|-- LineName: inheritance
+ Renderable <|.. Value: realization
+ PrimitiveValue <|-- Size: inheritance
+ PrimitiveValue <|-- CSSString: inheritance
+ Value <|-- PrimitiveValue: inheritance
+ ValueList <|-- CSSFunction: inheritance
+ ValueList <|-- RuleValueList: inheritance
+ Renderable <|.. Comment: realization
+
+ %% 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
+```
+
## Contributors/Thanks to
* [oliverklee](https://github.com/oliverklee) for lots of refactorings, code modernizations and CI integrations
@@ -633,5 +797,17 @@ class Sabberworm\CSS\CSSList\Document#4 (2) {
## Misc
-* 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 unit tests for this project, run `composer install` to install phpunit and use `./vendor/bin/phpunit`.
+### 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`.
+
diff --git a/composer.json b/composer.json
index a1f5677b..e8e42ce9 100644
--- a/composer.json
+++ b/composer.json
@@ -12,15 +12,26 @@
"authors": [
{
"name": "Raphael Schweikert"
+ },
+ {
+ "name": "Oliver Klee",
+ "email": "github@oliverklee.de"
+ },
+ {
+ "name": "Jake Hotson",
+ "email": "jake.github@qzdesign.co.uk"
}
],
"require": {
- "php": ">=5.6.20",
+ "php": ">=7.2.0",
"ext-iconv": "*"
},
"require-dev": {
- "phpunit/phpunit": "^5.7.27",
- "codacy/coverage": "^1.4.3"
+ "codacy/coverage": "^1.4.3",
+ "phpstan/extension-installer": "^1.3.1",
+ "phpstan/phpstan": "^1.10.65",
+ "phpstan/phpstan-phpunit": "^1.3.16",
+ "phpunit/phpunit": "^8.5.37"
},
"suggest": {
"ext-mbstring": "for parsing UTF-8 CSS"
@@ -35,35 +46,56 @@
"Sabberworm\\CSS\\Tests\\": "tests/"
}
},
+ "config": {
+ "allow-plugins": {
+ "phpstan/extension-installer": true
+ },
+ "preferred-install": {
+ "*": "dist"
+ },
+ "sort-packages": true
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-main": "9.0.x-dev"
+ }
+ },
"scripts": {
"ci": [
- "@ci:static"
+ "@ci:static",
+ "@ci:dynamic"
+ ],
+ "ci:dynamic": [
+ "@ci:tests"
],
- "ci:php:fixer": "@php ./.phive/php-cs-fixer.phar --config=config/php-cs-fixer.php fix --dry-run -v --show-progress=dots bin src tests",
- "ci:php:sniffer": "@php ./.phive/phpcs.phar --standard=config/phpcs.xml bin src tests",
- "ci:php:stan": "@php ./.phive/phpstan.phar --configuration=config/phpstan.neon",
+ "ci:php:fixer": "\"./.phive/php-cs-fixer\" --config=config/php-cs-fixer.php fix --dry-run -v --show-progress=dots --diff bin src tests",
+ "ci:php:stan": "phpstan --no-progress --configuration=config/phpstan.neon",
"ci:static": [
"@ci:php:fixer",
- "@ci:php:sniffer",
"@ci:php:stan"
],
+ "ci:tests": [
+ "@ci:tests:unit"
+ ],
+ "ci:tests:sof": "\"./vendor/bin/phpunit\" --stop-on-failure --do-not-cache-result",
+ "ci:tests:unit": "\"./vendor/bin/phpunit\" --do-not-cache-result",
"fix:php": [
- "@fix:php:fixer",
- "@fix:php:sniffer"
+ "@fix:php:fixer"
],
- "fix:php:fixer": "@php ./.phive/php-cs-fixer.phar --config=config/php-cs-fixer.php fix bin src tests",
- "fix:php:sniffer": "@php ./.phive/phpcbf.phar --standard=config/phpcs.xml bin src tests",
- "phpstan:baseline": "@php ./.phive/phpstan.phar --configuration=config/phpstan.neon --generate-baseline=config/phpstan-baseline.neon"
+ "fix:php:fixer": "\"./.phive/php-cs-fixer\" --config=config/php-cs-fixer.php fix bin src tests",
+ "phpstan:baseline": "phpstan --configuration=config/phpstan.neon --generate-baseline=config/phpstan-baseline.neon"
},
"scripts-descriptions": {
- "ci": "Runs all dynamic and static code checks (i.e. currently, only the static checks).",
+ "ci": "Runs all dynamic and static code checks.",
+ "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:sniffer": "Checks the code style with PHP_CodeSniffer.",
"ci:php:stan": "Checks the types with PHPStan.",
"ci:static": "Runs all static code analysis checks for the code.",
+ "ci:tests": "Runs all dynamic tests (i.e., currently, the unit tests).",
+ "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.",
"fix:php:fixer": "Fixes autofixable issues found by PHP CS Fixer.",
- "fix:php:sniffer": "Fixes autofixable issues found by PHP_CodeSniffer.",
- "phpstand:baseline": "Updates the PHPStan baseline file to match the code."
+ "phpstan:baseline": "Updates the PHPStan baseline file to match the code."
}
}
diff --git a/config/php-cs-fixer.php b/config/php-cs-fixer.php
index 88a9a692..76f52077 100644
--- a/config/php-cs-fixer.php
+++ b/config/php-cs-fixer.php
@@ -8,9 +8,8 @@
->setRiskyAllowed(true)
->setRules(
[
- '@PSR12' => true,
- // Disable constant visibility from the PSR12 rule set as this would break compatibility with PHP < 7.1.
- 'visibility_required' => ['elements' => ['property', 'method']],
+ '@PER-CS2.0' => true,
+ '@PER-CS2.0:risky' => true,
'@PHPUnit50Migration:risky' => true,
'@PHPUnit52Migration:risky' => true,
@@ -18,15 +17,18 @@
'@PHPUnit55Migration:risky' => true,
'@PHPUnit56Migration:risky' => true,
'@PHPUnit57Migration:risky' => true,
+ '@PHPUnit60Migration:risky' => true,
+ '@PHPUnit75Migration:risky' => true,
+ '@PHPUnit84Migration:risky' => true,
'php_unit_construct' => true,
- 'php_unit_dedicate_assert' => ['target' => '5.6'],
- 'php_unit_expectation' => ['target' => '5.6'],
+ 'php_unit_dedicate_assert' => ['target' => 'newest'],
+ 'php_unit_expectation' => ['target' => 'newest'],
'php_unit_fqcn_annotation' => true,
'php_unit_method_casing' => true,
- 'php_unit_mock' => ['target' => '5.5'],
+ 'php_unit_mock' => ['target' => 'newest'],
'php_unit_mock_short_will_return' => true,
- 'php_unit_namespaced' => ['target' => '5.7'],
+ 'php_unit_namespaced' => ['target' => 'newest'],
'php_unit_set_up_tear_down_visibility' => true,
'php_unit_test_annotation' => ['style' => 'annotation'],
'php_unit_test_case_static_method_calls' => ['call_type' => 'self'],
diff --git a/config/phpcs.xml b/config/phpcs.xml
deleted file mode 100644
index 14473bb2..00000000
--- a/config/phpcs.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
- This standard requires PHP_CodeSniffer >= 3.6.0.
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/config/phpstan-baseline.neon b/config/phpstan-baseline.neon
index b730548c..243e36c4 100644
--- a/config/phpstan-baseline.neon
+++ b/config/phpstan-baseline.neon
@@ -10,11 +10,6 @@ parameters:
count: 2
path: ../src/RuleSet/DeclarationBlock.php
- -
- message: "#^Variable \\$oRule might not be defined\\.$#"
- count: 2
- path: ../src/RuleSet/DeclarationBlock.php
-
-
message: "#^Variable \\$oVal might not be defined\\.$#"
count: 1
diff --git a/phpunit.xml b/phpunit.xml
index 5f3dd458..1060f329 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -1,6 +1,12 @@
+ xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.5/phpunit.xsd"
+ beStrictAboutChangesToGlobalState="true"
+ beStrictAboutCoversAnnotation="true"
+ cacheResult="false"
+ colors="true"
+ forceCoversAnnotation="true"
+>
tests
diff --git a/src/CSSList/CSSBlockList.php b/src/CSSList/CSSBlockList.php
index fce7913e..6b02edfb 100644
--- a/src/CSSList/CSSBlockList.php
+++ b/src/CSSList/CSSBlockList.php
@@ -113,7 +113,7 @@ protected function allSelectors(array &$aResult, $sSpecificitySearch = null)
$sComparator = $aSpecificitySearch[0];
$iTargetSpecificity = $aSpecificitySearch[1];
}
- $iTargetSpecificity = (int)$iTargetSpecificity;
+ $iTargetSpecificity = (int) $iTargetSpecificity;
$iSelectorSpecificity = $oSelector->getSpecificity();
$bMatches = false;
switch ($sComparator) {
diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php
index dcd8c331..603f662b 100644
--- a/src/CSSList/CSSList.php
+++ b/src/CSSList/CSSList.php
@@ -131,18 +131,15 @@ private static function parseListItem(ParserState $oParserState, CSSList $oList)
}
return $oAtRule;
} elseif ($oParserState->comes('}')) {
- if (!$oParserState->getSettings()->bLenientParsing) {
- throw new UnexpectedTokenException('CSS selector', '}', 'identifier', $oParserState->currentLine());
- } else {
- if ($bIsRoot) {
- if ($oParserState->getSettings()->bLenientParsing) {
- return DeclarationBlock::parse($oParserState);
- } else {
- throw new SourceException("Unopened {", $oParserState->currentLine());
- }
+ if ($bIsRoot) {
+ if ($oParserState->getSettings()->bLenientParsing) {
+ return DeclarationBlock::parse($oParserState);
} else {
- return null;
+ throw new SourceException("Unopened {", $oParserState->currentLine());
}
+ } else {
+ // End of list
+ return null;
}
} else {
return DeclarationBlock::parse($oParserState, $oList);
diff --git a/src/OutputFormat.php b/src/OutputFormat.php
index 96f26e14..9cc2ae4d 100644
--- a/src/OutputFormat.php
+++ b/src/OutputFormat.php
@@ -165,9 +165,7 @@ class OutputFormat
*/
private $iIndentationLevel = 0;
- public function __construct()
- {
- }
+ public function __construct() {}
/**
* @param string $sName
diff --git a/src/OutputFormatter.php b/src/OutputFormatter.php
index 7418494c..501d15da 100644
--- a/src/OutputFormatter.php
+++ b/src/OutputFormatter.php
@@ -215,6 +215,7 @@ public function removeLastSemicolon($sString)
/**
*
* @param array $aComments
+ *
* @return string
*/
public function comments(Commentable $oCommentable)
diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php
index 7a99f327..06b083e6 100644
--- a/src/Parsing/ParserState.php
+++ b/src/Parsing/ParserState.php
@@ -9,8 +9,10 @@ class ParserState
{
/**
* @var null
+ *
+ * @internal
*/
- const EOF = null;
+ public const EOF = null;
/**
* @var Settings
diff --git a/src/Parsing/UnexpectedEOFException.php b/src/Parsing/UnexpectedEOFException.php
index 368ec70c..825cc0c4 100644
--- a/src/Parsing/UnexpectedEOFException.php
+++ b/src/Parsing/UnexpectedEOFException.php
@@ -7,6 +7,4 @@
*
* Extends `UnexpectedTokenException` in order to preserve backwards compatibility.
*/
-class UnexpectedEOFException extends UnexpectedTokenException
-{
-}
+class UnexpectedEOFException extends UnexpectedTokenException {}
diff --git a/src/Property/AtRule.php b/src/Property/AtRule.php
index 9536ff5e..64efd4de 100644
--- a/src/Property/AtRule.php
+++ b/src/Property/AtRule.php
@@ -12,15 +12,19 @@ interface AtRule extends Renderable, Commentable
* we’re whitelisting the block rules and have anything else be treated as a set rule.
*
* @var string
+ *
+ * @internal
*/
- const BLOCK_RULES = 'media/document/supports/region-style/font-feature-values';
+ 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
*/
- const SET_RULES = 'font-face/counter-style/page/swash/styleset/annotation';
+ public const SET_RULES = 'font-face/counter-style/page/swash/styleset/annotation';
/**
* @return string|null
diff --git a/src/Property/KeyframeSelector.php b/src/Property/KeyframeSelector.php
index 14ea5ebb..7675252c 100644
--- a/src/Property/KeyframeSelector.php
+++ b/src/Property/KeyframeSelector.php
@@ -8,8 +8,10 @@ class KeyframeSelector extends Selector
* regexp for specificity calculations
*
* @var string
+ *
+ * @internal
*/
- const SELECTOR_VALIDATION_RX = '/
+ public const SELECTOR_VALIDATION_RX = '/
^(
(?:
[a-zA-Z0-9\x{00A0}-\x{FFFF}_^$|*="\'~\[\]()\-\s\.:#+>]* # any sequence of valid unescaped characters
diff --git a/src/Property/Selector.php b/src/Property/Selector.php
index 70c9b2fd..0bf433d1 100644
--- a/src/Property/Selector.php
+++ b/src/Property/Selector.php
@@ -13,7 +13,7 @@ class Selector
*
* @var string
*/
- const NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX = '/
+ private const NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX = '/
(\.[\w]+) # classes
|
\[(\w+) # attributes
@@ -37,7 +37,7 @@ class Selector
*
* @var string
*/
- const ELEMENTS_AND_PSEUDO_ELEMENTS_RX = '/
+ private const ELEMENTS_AND_PSEUDO_ELEMENTS_RX = '/
((^|[\s\+\>\~]+)[\w]+ # elements
|
\:{1,2}( # pseudo-elements
@@ -49,8 +49,10 @@ class Selector
* regexp for specificity calculations
*
* @var string
+ *
+ * @internal
*/
- const SELECTOR_VALIDATION_RX = '/
+ public const SELECTOR_VALIDATION_RX = '/
^(
(?:
[a-zA-Z0-9\x{00A0}-\x{FFFF}_^$|*="\'~\[\]()\-\s\.:#+>]* # any sequence of valid unescaped characters
diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php
index de487bc1..91f5ecef 100644
--- a/src/RuleSet/DeclarationBlock.php
+++ b/src/RuleSet/DeclarationBlock.php
@@ -565,6 +565,7 @@ public function expandListStyleShorthand()
public function createShorthandProperties(array $aProperties, $sShorthand)
{
$aRules = $this->getRulesAssoc();
+ $oRule = null;
$aNewValues = [];
foreach ($aProperties as $sProperty) {
if (!isset($aRules[$sProperty])) {
@@ -585,7 +586,7 @@ public function createShorthandProperties(array $aProperties, $sShorthand)
$this->removeRule($sProperty);
}
}
- if (count($aNewValues)) {
+ if ($aNewValues !== [] && $oRule instanceof Rule) {
$oNewRule = new Rule($sShorthand, $oRule->getLineNo(), $oRule->getColNo());
foreach ($aNewValues as $mValue) {
$oNewRule->addValue($mValue);
@@ -681,9 +682,9 @@ public function createDimensionsShorthand()
$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]) {
+ 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 {
diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php
index 300dc3ec..e3235c81 100644
--- a/src/Value/CSSFunction.php
+++ b/src/Value/CSSFunction.php
@@ -18,7 +18,7 @@ class CSSFunction extends ValueList
/**
* @param string $sName
- * @param RuleValueList|array $aArguments
+ * @param RuleValueList|array $aArguments
* @param string $sSeparator
* @param int $iLineNo
*/
@@ -72,7 +72,7 @@ public function setName($sName)
}
/**
- * @return array
+ * @return array
*/
public function getArguments()
{
diff --git a/src/Value/CalcFunction.php b/src/Value/CalcFunction.php
index 5ffd071f..f06d9b7e 100644
--- a/src/Value/CalcFunction.php
+++ b/src/Value/CalcFunction.php
@@ -11,12 +11,12 @@ class CalcFunction extends CSSFunction
/**
* @var int
*/
- const T_OPERAND = 1;
+ private const T_OPERAND = 1;
/**
* @var int
*/
- const T_OPERATOR = 2;
+ private const T_OPERATOR = 2;
/**
* @param ParserState $oParserState
diff --git a/src/Value/Color.php b/src/Value/Color.php
index 1cf00cce..55acdaaa 100644
--- a/src/Value/Color.php
+++ b/src/Value/Color.php
@@ -14,7 +14,7 @@
class Color extends CSSFunction
{
/**
- * @param array $aColor
+ * @param array $aColor
* @param int $iLineNo
*/
public function __construct(array $aColor, $iLineNo = 0)
@@ -56,12 +56,19 @@ public static function parse(ParserState $oParserState, $bIgnoreCase = false)
$oParserState->currentLine()
),
];
- } else {
+ } elseif ($oParserState->strlen($sValue) === 6) {
$aColor = [
'r' => new Size(intval($sValue[0] . $sValue[1], 16), null, true, $oParserState->currentLine()),
'g' => new Size(intval($sValue[2] . $sValue[3], 16), null, true, $oParserState->currentLine()),
'b' => new Size(intval($sValue[4] . $sValue[5], 16), null, true, $oParserState->currentLine()),
];
+ } else {
+ throw new UnexpectedTokenException(
+ 'Invalid hex color value',
+ $sValue,
+ 'custom',
+ $oParserState->currentLine()
+ );
}
} else {
$sColorMode = $oParserState->parseIdentifier(true);
@@ -118,7 +125,7 @@ private static function mapRange($fVal, $fFromMin, $fFromMax, $fToMin, $fToMax)
}
/**
- * @return array
+ * @return array
*/
public function getColor()
{
@@ -126,7 +133,7 @@ public function getColor()
}
/**
- * @param array $aColor
+ * @param array $aColor
*
* @return void
*/
diff --git a/src/Value/LineName.php b/src/Value/LineName.php
index e231ce38..6eaf7620 100644
--- a/src/Value/LineName.php
+++ b/src/Value/LineName.php
@@ -10,7 +10,7 @@
class LineName extends ValueList
{
/**
- * @param array $aComponents
+ * @param array $aComponents
* @param int $iLineNo
*/
public function __construct(array $aComponents = [], $iLineNo = 0)
diff --git a/src/Value/Size.php b/src/Value/Size.php
index 36a32381..f8336b1f 100644
--- a/src/Value/Size.php
+++ b/src/Value/Size.php
@@ -17,17 +17,22 @@ class Size extends PrimitiveValue
*
* @var array
*/
- const ABSOLUTE_SIZE_UNITS = ['px', 'cm', 'mm', 'mozmm', 'in', 'pt', 'pc', 'vh', 'vw', 'vmin', 'vmax', 'rem'];
+ private const ABSOLUTE_SIZE_UNITS = [
+ 'px', 'pt', 'pc',
+ 'cm', 'mm', 'mozmm', 'in',
+ 'vh', 'dvh', 'svh', 'lvh',
+ 'vw', 'vmin', 'vmax', 'rem',
+ ];
/**
* @var array
*/
- const RELATIVE_SIZE_UNITS = ['%', 'em', 'ex', 'ch', 'fr'];
+ private const RELATIVE_SIZE_UNITS = ['%', 'em', 'ex', 'ch', 'fr'];
/**
* @var array
*/
- const NON_SIZE_UNITS = ['deg', 'grad', 'rad', 's', 'ms', 'turn', 'Hz', 'kHz'];
+ private const NON_SIZE_UNITS = ['deg', 'grad', 'rad', 's', 'ms', 'turn', 'Hz', 'kHz'];
/**
* @var array>|null
@@ -58,7 +63,7 @@ class Size extends PrimitiveValue
public function __construct($fSize, $sUnit = null, $bIsColorComponent = false, $iLineNo = 0)
{
parent::__construct($iLineNo);
- $this->fSize = (float)$fSize;
+ $this->fSize = (float) $fSize;
$this->sUnit = $sUnit;
$this->bIsColorComponent = $bIsColorComponent;
}
@@ -103,7 +108,7 @@ public static function parse(ParserState $oParserState, $bIsColorComponent = fal
}
}
}
- return new Size((float)$sSize, $sUnit, $bIsColorComponent, $oParserState->currentLine());
+ return new Size((float) $sSize, $sUnit, $bIsColorComponent, $oParserState->currentLine());
}
/**
@@ -150,7 +155,7 @@ public function getUnit()
*/
public function setSize($fSize)
{
- $this->fSize = (float)$fSize;
+ $this->fSize = (float) $fSize;
}
/**
@@ -211,7 +216,7 @@ public function render(OutputFormat $oOutputFormat)
{
$l = localeconv();
$sPoint = preg_quote($l['decimal_point'], '/');
- $sSize = preg_match("/[\d\.]+e[+-]?\d+/i", (string)$this->fSize)
+ $sSize = preg_match("/[\d\.]+e[+-]?\d+/i", (string) $this->fSize)
? preg_replace("/$sPoint?0+$/", "", sprintf("%f", $this->fSize)) : $this->fSize;
return preg_replace(["/$sPoint/", "/^(-?)0\./"], ['.', '$1.'], $sSize)
. ($this->sUnit === null ? '' : $this->sUnit);
diff --git a/src/Value/Value.php b/src/Value/Value.php
index a920396b..5b52a22d 100644
--- a/src/Value/Value.php
+++ b/src/Value/Value.php
@@ -30,22 +30,22 @@ public function __construct($iLineNo = 0)
/**
* @param array $aListDelimiters
*
- * @return RuleValueList|CSSFunction|CSSString|LineName|Size|URL|string
+ * @return Value|string
*
* @throws UnexpectedTokenException
* @throws UnexpectedEOFException
*/
public static function parseValue(ParserState $oParserState, array $aListDelimiters = [])
{
- /** @var array $aStack */
+ /** @var array $aStack */
$aStack = [];
$oParserState->consumeWhiteSpace();
//Build a list of delimiters and parsed values
while (
!($oParserState->comes('}') || $oParserState->comes(';') || $oParserState->comes('!')
- || $oParserState->comes(')')
- || $oParserState->comes('\\')
- || $oParserState->isEnd())
+ || $oParserState->comes(')')
+ || $oParserState->comes('\\')
+ || $oParserState->isEnd())
) {
if (count($aStack) > 0) {
$bFoundDelimiter = false;
@@ -67,23 +67,30 @@ public static function parseValue(ParserState $oParserState, array $aListDelimit
}
// Convert the list to list objects
foreach ($aListDelimiters as $sDelimiter) {
- if (count($aStack) === 1) {
+ $iStackLength = count($aStack);
+ if ($iStackLength === 1) {
return $aStack[0];
}
- $iStartPosition = null;
- while (($iStartPosition = array_search($sDelimiter, $aStack, true)) !== false) {
+ $aNewStack = [];
+ for ($iStartPosition = 0; $iStartPosition < $iStackLength; ++$iStartPosition) {
+ if ($iStartPosition === ($iStackLength - 1) || $sDelimiter !== $aStack[$iStartPosition + 1]) {
+ $aNewStack[] = $aStack[$iStartPosition];
+ continue;
+ }
$iLength = 2; //Number of elements to be joined
- for ($i = $iStartPosition + 2; $i < count($aStack); $i += 2, ++$iLength) {
+ for ($i = $iStartPosition + 3; $i < $iStackLength; $i += 2, ++$iLength) {
if ($sDelimiter !== $aStack[$i]) {
break;
}
}
$oList = new RuleValueList($sDelimiter, $oParserState->currentLine());
- for ($i = $iStartPosition - 1; $i - $iStartPosition + 1 < $iLength * 2; $i += 2) {
+ for ($i = $iStartPosition; $i - $iStartPosition < $iLength * 2; $i += 2) {
$oList->addListComponent($aStack[$i]);
}
- array_splice($aStack, $iStartPosition - 1, $iLength * 2 - 1, [$oList]);
+ $aNewStack[] = $oList;
+ $iStartPosition += $iLength * 2 - 2;
}
+ $aStack = $aNewStack;
}
if (!isset($aStack[0])) {
throw new UnexpectedTokenException(
diff --git a/src/Value/ValueList.php b/src/Value/ValueList.php
index a93acc7b..382c3baa 100644
--- a/src/Value/ValueList.php
+++ b/src/Value/ValueList.php
@@ -13,7 +13,7 @@
abstract class ValueList extends Value
{
/**
- * @var array
+ * @var array
*/
protected $aComponents;
@@ -23,8 +23,7 @@ abstract class ValueList extends Value
protected $sSeparator;
/**
- * phpcs:ignore Generic.Files.LineLength
- * @param array|RuleValueList|CSSFunction|CSSString|LineName|Size|URL|string $aComponents
+ * @param array|Value|string $aComponents
* @param string $sSeparator
* @param int $iLineNo
*/
@@ -39,7 +38,7 @@ public function __construct($aComponents = [], $sSeparator = ',', $iLineNo = 0)
}
/**
- * @param RuleValueList|CSSFunction|CSSString|LineName|Size|URL|string $mComponent
+ * @param Value|string $mComponent
*
* @return void
*/
@@ -49,7 +48,7 @@ public function addListComponent($mComponent)
}
/**
- * @return array
+ * @return array
*/
public function getListComponents()
{
@@ -57,7 +56,7 @@ public function getListComponents()
}
/**
- * @param array $aComponents
+ * @param array $aComponents
*
* @return void
*/
diff --git a/tests/CSSList/AtRuleBlockListTest.php b/tests/CSSList/AtRuleBlockListTest.php
index 48e6e578..dcc8db5c 100644
--- a/tests/CSSList/AtRuleBlockListTest.php
+++ b/tests/CSSList/AtRuleBlockListTest.php
@@ -7,12 +7,46 @@
use Sabberworm\CSS\CSSList\AtRuleBlockList;
use Sabberworm\CSS\Parser;
use Sabberworm\CSS\Renderable;
+use Sabberworm\CSS\Settings;
/**
* @covers \Sabberworm\CSS\CSSList\AtRuleBlockList
*/
-class AtRuleBlockListTest extends TestCase
+final class AtRuleBlockListTest extends TestCase
{
+ /**
+ * @return array
+ */
+ public static function provideMinWidthMediaRule(): array
+ {
+ return [
+ 'without spaces around arguments' => ['@media(min-width: 768px){.class{color:red}}'],
+ 'with spaces around arguments' => ['@media (min-width: 768px) {.class{color:red}}'],
+ ];
+ }
+
+ /**
+ * @return array
+ */
+ public static function provideSyntacticlyCorrectAtRule(): 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;
+ }
+ .flex-container {
+ display: flex;
+ }
+ }
+ '],
+ ];
+ }
+
/**
* @test
*/
@@ -43,23 +77,12 @@ public function implementsCommentable()
self::assertInstanceOf(Commentable::class, $subject);
}
- /**
- * @return array>
- */
- public function mediaRuleDataProvider()
- {
- return [
- 'without spaces around arguments' => ['@media(min-width: 768px){.class{color:red}}'],
- 'with spaces around arguments' => ['@media (min-width: 768px) {.class{color:red}}'],
- ];
- }
-
/**
* @test
*
* @param string $css
*
- * @dataProvider mediaRuleDataProvider
+ * @dataProvider provideMinWidthMediaRule
*/
public function parsesRuleNameOfMediaQueries($css)
{
@@ -74,7 +97,7 @@ public function parsesRuleNameOfMediaQueries($css)
*
* @param string $css
*
- * @dataProvider mediaRuleDataProvider
+ * @dataProvider provideMinWidthMediaRule
*/
public function parsesArgumentsOfMediaQueries($css)
{
@@ -83,4 +106,17 @@ public function parsesArgumentsOfMediaQueries($css)
self::assertSame('(min-width: 768px)', $atRuleBlockList->atRuleArgs());
}
+
+ /**
+ * @test
+ *
+ * @dataProvider provideMinWidthMediaRule
+ * @dataProvider provideSyntacticlyCorrectAtRule
+ */
+ public function parsesSyntacticlyCorrectAtRuleInStrictMode(string $css): void
+ {
+ $contents = (new Parser($css, Settings::create()->beStrict()))->parse()->getContents();
+
+ self::assertNotEmpty($contents, 'Failing CSS: `' . $css . '`');
+ }
}
diff --git a/tests/CSSList/DocumentTest.php b/tests/CSSList/DocumentTest.php
index a727400b..a052e449 100644
--- a/tests/CSSList/DocumentTest.php
+++ b/tests/CSSList/DocumentTest.php
@@ -11,14 +11,14 @@
/**
* @covers \Sabberworm\CSS\CSSList\Document
*/
-class DocumentTest extends TestCase
+final class DocumentTest extends TestCase
{
/**
* @var Document
*/
private $subject;
- protected function setUp()
+ protected function setUp(): void
{
$this->subject = new Document();
}
@@ -50,7 +50,7 @@ public function getContentsInitiallyReturnsEmptyArray()
/**
* @return array>>
*/
- public function contentsDataProvider()
+ public static function contentsDataProvider()
{
return [
'empty array' => [[]],
diff --git a/tests/CSSList/KeyFrameTest.php b/tests/CSSList/KeyFrameTest.php
index 080d5f94..293e4f3a 100644
--- a/tests/CSSList/KeyFrameTest.php
+++ b/tests/CSSList/KeyFrameTest.php
@@ -11,14 +11,14 @@
/**
* @covers \Sabberworm\CSS\CSSList\KeyFrame
*/
-class KeyFrameTest extends TestCase
+final class KeyFrameTest extends TestCase
{
/**
* @var KeyFrame
*/
protected $subject;
- protected function setUp()
+ protected function setUp(): void
{
$this->subject = new KeyFrame();
}
diff --git a/tests/Comment/CommentTest.php b/tests/Comment/CommentTest.php
index 29385f01..7261e9ba 100644
--- a/tests/Comment/CommentTest.php
+++ b/tests/Comment/CommentTest.php
@@ -14,7 +14,7 @@
* @covers \Sabberworm\CSS\OutputFormat
* @covers \Sabberworm\CSS\OutputFormatter
*/
-class CommentTest extends TestCase
+final class CommentTest extends TestCase
{
/**
* @test
@@ -91,7 +91,7 @@ public function toStringRendersCommentEnclosedInCommentDelimiters()
$subject->setComment($comment);
- self::assertSame('/*' . $comment . '*/', (string)$subject);
+ self::assertSame('/*' . $comment . '*/', (string) $subject);
}
/**
@@ -140,11 +140,11 @@ public function keepCommentsInOutput()
', $oCss->render(OutputFormat::createPretty()));
self::assertSame(
'/** Number 11 **//**' . "\n"
- . ' * Comments' . "\n"
- . ' *//* Hell */@import url("some/url.css") screen;'
- . '/* Number 4 *//* Number 5 */.foo,#bar{'
- . '/* Number 6 */background-color:#000;}@media screen{'
- . '/** Number 10 **/#foo.bar{/** Number 10b **/position:absolute;}}',
+ . ' * Comments' . "\n"
+ . ' *//* Hell */@import url("some/url.css") screen;'
+ . '/* Number 4 *//* Number 5 */.foo,#bar{'
+ . '/* Number 6 */background-color:#000;}@media screen{'
+ . '/** Number 10 **/#foo.bar{/** Number 10b **/position:absolute;}}',
$oCss->render(OutputFormat::createCompact()->setRenderComments(true))
);
}
@@ -170,8 +170,8 @@ public function stripCommentsFromOutput()
', $oCss->render(OutputFormat::createPretty()->setRenderComments(false)));
self::assertSame(
'@import url("some/url.css") screen;'
- . '.foo,#bar{background-color:#000;}'
- . '@media screen{#foo.bar{position:absolute;}}',
+ . '.foo,#bar{background-color:#000;}'
+ . '@media screen{#foo.bar{position:absolute;}}',
$oCss->render(OutputFormat::createCompact())
);
}
diff --git a/tests/OutputFormatTest.php b/tests/OutputFormatTest.php
index 0de39123..66843eae 100644
--- a/tests/OutputFormatTest.php
+++ b/tests/OutputFormatTest.php
@@ -11,12 +11,12 @@
/**
* @covers \Sabberworm\CSS\OutputFormat
*/
-class OutputFormatTest extends TestCase
+final class OutputFormatTest extends TestCase
{
/**
* @var string
*/
- const TEST_CSS = <<oParser = new Parser(self::TEST_CSS);
$this->oDocument = $this->oParser->parse();
diff --git a/tests/ParserTest.php b/tests/ParserTest.php
index 74449ee2..a48ac0e7 100644
--- a/tests/ParserTest.php
+++ b/tests/ParserTest.php
@@ -35,7 +35,7 @@
* @covers \Sabberworm\CSS\Value\Size::parse
* @covers \Sabberworm\CSS\Value\URL::parse
*/
-class ParserTest extends TestCase
+final class ParserTest extends TestCase
{
/**
* @test
@@ -146,6 +146,8 @@ public function colorParsing()
'l' => new Size(220.0, '%', true, $oColor->getLineNo()),
'a' => new Size(0000.3, null, true, $oColor->getLineNo()),
], $oColor->getColor());
+ $aColorRule = $oRuleSet->getRules('outline-color');
+ self::assertEmpty($aColorRule);
}
}
foreach ($oDoc->getAllValues('color') as $sColor) {
diff --git a/tests/RuleSet/DeclarationBlockTest.php b/tests/RuleSet/DeclarationBlockTest.php
index 49526952..31537a9b 100644
--- a/tests/RuleSet/DeclarationBlockTest.php
+++ b/tests/RuleSet/DeclarationBlockTest.php
@@ -10,7 +10,7 @@
/**
* @covers \Sabberworm\CSS\RuleSet\DeclarationBlock
*/
-class DeclarationBlockTest extends TestCase
+final class DeclarationBlockTest extends TestCase
{
/**
* @param string $sCss
@@ -27,13 +27,13 @@ public function expandBorderShorthand($sCss, $sExpected)
foreach ($oDoc->getAllDeclarationBlocks() as $oDeclaration) {
$oDeclaration->expandBorderShorthand();
}
- self::assertSame(trim((string)$oDoc), $sExpected);
+ self::assertSame(trim((string) $oDoc), $sExpected);
}
/**
* @return array>
*/
- public function expandBorderShorthandProvider()
+ public static function expandBorderShorthandProvider()
{
return [
['body{ border: 2px solid #000 }', 'body {border-width: 2px;border-style: solid;border-color: #000;}'],
@@ -60,13 +60,13 @@ public function expandFontShorthand($sCss, $sExpected)
foreach ($oDoc->getAllDeclarationBlocks() as $oDeclaration) {
$oDeclaration->expandFontShorthand();
}
- self::assertSame(trim((string)$oDoc), $sExpected);
+ self::assertSame(trim((string) $oDoc), $sExpected);
}
/**
* @return array>
*/
- public function expandFontShorthandProvider()
+ public static function expandFontShorthandProvider()
{
return [
[
@@ -116,13 +116,13 @@ public function expandBackgroundShorthand($sCss, $sExpected)
foreach ($oDoc->getAllDeclarationBlocks() as $oDeclaration) {
$oDeclaration->expandBackgroundShorthand();
}
- self::assertSame(trim((string)$oDoc), $sExpected);
+ self::assertSame(trim((string) $oDoc), $sExpected);
}
/**
* @return array>
*/
- public function expandBackgroundShorthandProvider()
+ public static function expandBackgroundShorthandProvider()
{
return [
['body {border: 1px;}', 'body {border: 1px;}'],
@@ -169,13 +169,13 @@ public function expandDimensionsShorthand($sCss, $sExpected)
foreach ($oDoc->getAllDeclarationBlocks() as $oDeclaration) {
$oDeclaration->expandDimensionsShorthand();
}
- self::assertSame(trim((string)$oDoc), $sExpected);
+ self::assertSame(trim((string) $oDoc), $sExpected);
}
/**
* @return array>
*/
- public function expandDimensionsShorthandProvider()
+ public static function expandDimensionsShorthandProvider()
{
return [
['body {border: 1px;}', 'body {border: 1px;}'],
@@ -207,13 +207,13 @@ public function createBorderShorthand($sCss, $sExpected)
foreach ($oDoc->getAllDeclarationBlocks() as $oDeclaration) {
$oDeclaration->createBorderShorthand();
}
- self::assertSame(trim((string)$oDoc), $sExpected);
+ self::assertSame(trim((string) $oDoc), $sExpected);
}
/**
* @return array>
*/
- public function createBorderShorthandProvider()
+ public static function createBorderShorthandProvider()
{
return [
['body {border-width: 2px;border-style: solid;border-color: #000;}', 'body {border: 2px solid #000;}'],
@@ -238,13 +238,13 @@ public function createFontShorthand($sCss, $sExpected)
foreach ($oDoc->getAllDeclarationBlocks() as $oDeclaration) {
$oDeclaration->createFontShorthand();
}
- self::assertSame(trim((string)$oDoc), $sExpected);
+ self::assertSame(trim((string) $oDoc), $sExpected);
}
/**
* @return array>
*/
- public function createFontShorthandProvider()
+ public static function createFontShorthandProvider()
{
return [
['body {font-size: 12px; font-family: serif}', 'body {font: 12px serif;}'],
@@ -281,13 +281,13 @@ public function createDimensionsShorthand($sCss, $sExpected)
foreach ($oDoc->getAllDeclarationBlocks() as $oDeclaration) {
$oDeclaration->createDimensionsShorthand();
}
- self::assertSame(trim((string)$oDoc), $sExpected);
+ self::assertSame(trim((string) $oDoc), $sExpected);
}
/**
* @return array>
*/
- public function createDimensionsShorthandProvider()
+ public static function createDimensionsShorthandProvider()
{
return [
['body {border: 1px;}', 'body {border: 1px;}'],
@@ -319,13 +319,13 @@ public function createBackgroundShorthand($sCss, $sExpected)
foreach ($oDoc->getAllDeclarationBlocks() as $oDeclaration) {
$oDeclaration->createBackgroundShorthand();
}
- self::assertSame(trim((string)$oDoc), $sExpected);
+ self::assertSame(trim((string) $oDoc), $sExpected);
}
/**
* @return array>
*/
- public function createBackgroundShorthandProvider()
+ public static function createBackgroundShorthandProvider()
{
return [
['body {border: 1px;}', 'body {border: 1px;}'],
diff --git a/tests/RuleSet/LenientParsingTest.php b/tests/RuleSet/LenientParsingTest.php
index 5f5f224a..39e7bf74 100644
--- a/tests/RuleSet/LenientParsingTest.php
+++ b/tests/RuleSet/LenientParsingTest.php
@@ -19,7 +19,7 @@
* @covers \Sabberworm\CSS\Value\Size::parse
* @covers \Sabberworm\CSS\Value\URL::parse
*/
-class LenientParsingTest extends TestCase
+final class LenientParsingTest extends TestCase
{
/**
* @test
@@ -131,4 +131,26 @@ public function caseInsensitivity()
$oResult->render()
);
}
+
+ /**
+ * @test
+ */
+ public function invalidColor()
+ {
+ $sFile = __DIR__ . '/../fixtures/invalid-color.css';
+ $oParser = new Parser(file_get_contents($sFile), Settings::create()->withLenientParsing(true));
+ $oParser->parse();
+ }
+
+ /**
+ * @test
+ */
+ public function invalidColorStrict()
+ {
+ $this->expectException(UnexpectedTokenException::class);
+
+ $sFile = __DIR__ . '/../fixtures/invalid-color.css';
+ $oParser = new Parser(file_get_contents($sFile), Settings::create()->beStrict());
+ $oParser->parse();
+ }
}
diff --git a/tests/SettingsTest.php b/tests/SettingsTest.php
new file mode 100644
index 00000000..859059a3
--- /dev/null
+++ b/tests/SettingsTest.php
@@ -0,0 +1,155 @@
+subject = Settings::create();
+ }
+
+ /**
+ * @test
+ */
+ public function createReturnsInstance(): void
+ {
+ $settings = Settings::create();
+
+ self::assertInstanceOf(Settings::class, $settings);
+ }
+
+ /**
+ * @test
+ */
+ public function createReturnsANewInstanceForEachCall(): void
+ {
+ $settings1 = Settings::create();
+ $settings2 = Settings::create();
+
+ self::assertNotSame($settings1, $settings2);
+ }
+
+ /**
+ * @test
+ */
+ public function multibyteSupportByDefaultStateOfMbStringExtension(): void
+ {
+ self::assertSame(extension_loaded('mbstring'), $this->subject->bMultibyteSupport);
+ }
+
+ /**
+ * @test
+ */
+ public function withMultibyteSupportProvidesFluentInterface(): void
+ {
+ self::assertSame($this->subject, $this->subject->withMultibyteSupport());
+ }
+
+ /**
+ * @return array
+ */
+ public static function booleanDataProvider(): array
+ {
+ return [
+ 'true' => [true],
+ 'false' => [false],
+ ];
+ }
+
+ /**
+ * @test
+ * @dataProvider booleanDataProvider
+ */
+ public function withMultibyteSupportSetsMultibyteSupport(bool $value): void
+ {
+ $this->subject->withMultibyteSupport($value);
+
+ self::assertSame($value, $this->subject->bMultibyteSupport);
+ }
+
+ /**
+ * @test
+ */
+ public function defaultCharsetByDefaultIsUtf8(): void
+ {
+ self::assertSame('utf-8', $this->subject->sDefaultCharset);
+ }
+
+ /**
+ * @test
+ */
+ public function withDefaultCharsetProvidesFluentInterface(): void
+ {
+ self::assertSame($this->subject, $this->subject->withDefaultCharset('UTF-8'));
+ }
+
+ /**
+ * @test
+ */
+ public function withDefaultCharsetSetsDefaultCharset(): void
+ {
+ $charset = 'ISO-8859-1';
+ $this->subject->withDefaultCharset($charset);
+
+ self::assertSame($charset, $this->subject->sDefaultCharset);
+ }
+
+ /**
+ * @test
+ */
+ public function lenientParsingByDefaultIsTrue(): void
+ {
+ self::assertTrue($this->subject->bLenientParsing);
+ }
+
+ /**
+ * @test
+ */
+ public function withLenientParsingProvidesFluentInterface(): void
+ {
+ self::assertSame($this->subject, $this->subject->withLenientParsing());
+ }
+
+ /**
+ * @test
+ * @dataProvider booleanDataProvider
+ */
+ public function withLenientParsingSetsLenientParsing(bool $value): void
+ {
+ $this->subject->withLenientParsing($value);
+
+ self::assertSame($value, $this->subject->bLenientParsing);
+ }
+
+ /**
+ * @test
+ */
+ public function beStrictProvidesFluentInterface(): void
+ {
+ self::assertSame($this->subject, $this->subject->beStrict());
+ }
+
+ /**
+ * @test
+ */
+ public function beStrictSetsLenientParsingToFalse(): void
+ {
+ $this->subject->beStrict();
+
+ self::assertFalse($this->subject->bLenientParsing);
+ }
+}
diff --git a/tests/Value/CalcRuleValueListTest.php b/tests/Value/CalcRuleValueListTest.php
index 0a2c5304..0ce279fd 100644
--- a/tests/Value/CalcRuleValueListTest.php
+++ b/tests/Value/CalcRuleValueListTest.php
@@ -9,7 +9,7 @@
/**
* @covers \Sabberworm\CSS\Value\CalcRuleValueList
*/
-class CalcRuleValueListTest extends TestCase
+final class CalcRuleValueListTest extends TestCase
{
/**
* @test
diff --git a/tests/Value/SizeTest.php b/tests/Value/SizeTest.php
new file mode 100644
index 00000000..3aecce05
--- /dev/null
+++ b/tests/Value/SizeTest.php
@@ -0,0 +1,51 @@
+
+ */
+ 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',
+ ];
+
+ return \array_combine(
+ $units,
+ \array_map(
+ function (string $unit): array {
+ return [$unit];
+ },
+ $units
+ )
+ );
+ }
+
+ /**
+ * @test
+ *
+ * @dataProvider provideUnit
+ */
+ public function parsesUnit(string $unit): void
+ {
+ $subject = Size::parse(new ParserState('1' . $unit, Settings::create()));
+
+ self::assertSame($unit, $subject->getUnit());
+ }
+}
diff --git a/tests/fixtures/colortest.css b/tests/fixtures/colortest.css
index 1c89cf41..f834aa77 100644
--- a/tests/fixtures/colortest.css
+++ b/tests/fixtures/colortest.css
@@ -9,6 +9,7 @@
#yours {
background-color: hsl(220, 10%, 220%);
background-color: hsla(220, 10%, 220%, 0.3);
+ outline-color: #22;
}
#variables {
diff --git a/tests/fixtures/invalid-color.css b/tests/fixtures/invalid-color.css
new file mode 100644
index 00000000..31602f37
--- /dev/null
+++ b/tests/fixtures/invalid-color.css
@@ -0,0 +1,11 @@
+#test {
+ color: #a;
+ background: #ab;
+}
+
+body
+ color: #abcd;
+ background: #abcde;
+}
+
+a { color: #fffff;}