From ddd999ef663b8169892356014e4fcadc768f0673 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sun, 30 Mar 2025 07:34:48 +0100 Subject: [PATCH 1/9] [TASK] Add and implement `Positionable` interface This the backport of #1221 and #1225. --- src/Position/Position.php | 68 ++++++ src/Position/Positionable.php | 45 ++++ .../Position/Fixtures/ConcretePosition.php | 13 ++ tests/Unit/Position/PositionTest.php | 194 ++++++++++++++++++ .../UnitDeprecated/Position/PositionTest.php | 135 ++++++++++++ 5 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 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 000000000..655f34b9b --- /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 000000000..124752feb --- /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 000000000..0db387065 --- /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/Position/PositionTest.php b/tests/UnitDeprecated/Position/PositionTest.php new file mode 100644 index 000000000..2e06cc6d8 --- /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 70ae485abb2d7c95e46061b67c464d3d344337ae Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Tue, 1 Apr 2025 09:32:54 +0100 Subject: [PATCH 2/9] Apply #1225. --- CHANGELOG.md | 18 +++++++++++ src/CSSList/CSSList.php | 23 ++++---------- src/Comment/Comment.php | 21 ++++--------- src/Parsing/SourceException.php | 20 ++++--------- src/Property/CSSNamespace.php | 16 ++++------ src/Property/Charset.php | 16 ++++------ src/Property/Import.php | 23 ++++---------- src/Rule/Rule.php | 51 +++++--------------------------- src/RuleSet/DeclarationBlock.php | 5 +++- src/RuleSet/RuleSet.php | 23 ++++---------- src/Value/CSSFunction.php | 2 +- src/Value/Value.php | 21 ++++--------- 12 files changed, 76 insertions(+), 163 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cc851c51..79cd6ce3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,28 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Added +- Methods `getLineNumber` and `getColumnNumber` which return a nullable `int` + for the following classes: + `Comment`, `CSSList`, `SourceException`, `Charset`, `CSSNamespace`, `Import`, + `Rule`, `DeclarationBlock`, `RuleSet`, `CSSFunction`, `Value` (#1225) +- `Positionable` interface for CSS items that may have a position + (line and perhaps column number) in the parsed CSS (#1221) + ### Changed +- Implement `Positionable` in the following CSS item classes: + `Comment`, `CSSList`, `SourceException`, `Charset`, `CSSNamespace`, `Import`, + `Rule`, `DeclarationBlock`, `RuleSet`, `CSSFunction`, `Value` (#1225) + ### Deprecated +- `getLineNo()` is deprecated in these classes (use `getLineNumber()` instead): + `Comment`, `CSSList`, `SourceException`, `Charset`, `CSSNamespace`, `Import`, + `Rule`, `DeclarationBlock`, `RuleSet`, `CSSFunction`, `Value` (#1225) +- `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 ### Fixed diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 0f40d40a5..2156aa111 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -9,6 +9,8 @@ use Sabberworm\CSS\Parsing\SourceException; use Sabberworm\CSS\Parsing\UnexpectedEOFException; use Sabberworm\CSS\Parsing\UnexpectedTokenException; +use Sabberworm\CSS\Position\Position; +use Sabberworm\CSS\Position\Positionable; use Sabberworm\CSS\Property\AtRule; use Sabberworm\CSS\Property\Charset; use Sabberworm\CSS\Property\CSSNamespace; @@ -29,8 +31,10 @@ * * It can also contain `Import` and `Charset` objects stemming from at-rules. */ -abstract class CSSList implements Renderable, Commentable +abstract class CSSList implements Commentable, Positionable, Renderable { + use Position; + /** * @var array * @@ -45,13 +49,6 @@ abstract class CSSList implements Renderable, Commentable */ protected $aContents; - /** - * @var int - * - * @internal since 8.8.0 - */ - protected $iLineNo; - /** * @param int $iLineNo */ @@ -59,7 +56,7 @@ public function __construct($iLineNo = 0) { $this->aComments = []; $this->aContents = []; - $this->iLineNo = $iLineNo; + $this->setPosition($iLineNo); } /** @@ -258,14 +255,6 @@ private static function identifierIs($sIdentifier, $sMatch) ?: preg_match("/^(-\\w+-)?$sMatch$/i", $sIdentifier) === 1; } - /** - * @return int - */ - public function getLineNo() - { - return $this->iLineNo; - } - /** * Prepends an item to the list of contents. * diff --git a/src/Comment/Comment.php b/src/Comment/Comment.php index 1d8810ec1..fb571b403 100644 --- a/src/Comment/Comment.php +++ b/src/Comment/Comment.php @@ -4,15 +4,12 @@ use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Renderable; +use Sabberworm\CSS\Position\Position; +use Sabberworm\CSS\Position\Positionable; -class Comment implements Renderable +class Comment implements Positionable, Renderable { - /** - * @var int - * - * @internal since 8.8.0 - */ - protected $iLineNo; + use Position; /** * @var string @@ -28,7 +25,7 @@ class Comment implements Renderable public function __construct($sComment = '', $iLineNo = 0) { $this->sComment = $sComment; - $this->iLineNo = $iLineNo; + $this->setPosition($iLineNo); } /** @@ -39,14 +36,6 @@ public function getComment() return $this->sComment; } - /** - * @return int - */ - public function getLineNo() - { - return $this->iLineNo; - } - /** * @param string $sComment * diff --git a/src/Parsing/SourceException.php b/src/Parsing/SourceException.php index 1ca668a99..1aa27b437 100644 --- a/src/Parsing/SourceException.php +++ b/src/Parsing/SourceException.php @@ -2,12 +2,12 @@ namespace Sabberworm\CSS\Parsing; -class SourceException extends \Exception +use Sabberworm\CSS\Position\Position; +use Sabberworm\CSS\Position\Positionable; + +class SourceException extends \Exception implements Positionable { - /** - * @var int - */ - private $iLineNo; + use Position; /** * @param string $sMessage @@ -15,18 +15,10 @@ class SourceException extends \Exception */ public function __construct($sMessage, $iLineNo = 0) { - $this->iLineNo = $iLineNo; + $this->setPosition($iLineNo); if (!empty($iLineNo)) { $sMessage .= " [line no: $iLineNo]"; } parent::__construct($sMessage); } - - /** - * @return int - */ - public function getLineNo() - { - return $this->iLineNo; - } } diff --git a/src/Property/CSSNamespace.php b/src/Property/CSSNamespace.php index 0d90cc3bf..188d3581f 100644 --- a/src/Property/CSSNamespace.php +++ b/src/Property/CSSNamespace.php @@ -4,12 +4,16 @@ use Sabberworm\CSS\Comment\Comment; use Sabberworm\CSS\OutputFormat; +use Sabberworm\CSS\Position\Position; +use Sabberworm\CSS\Position\Positionable; /** * `CSSNamespace` represents an `@namespace` rule. */ -class CSSNamespace implements AtRule +class CSSNamespace implements AtRule, Positionable { + use Position; + /** * @var string */ @@ -41,18 +45,10 @@ public function __construct($mUrl, $sPrefix = null, $iLineNo = 0) { $this->mUrl = $mUrl; $this->sPrefix = $sPrefix; - $this->iLineNo = $iLineNo; + $this->setPosition($iLineNo); $this->aComments = []; } - /** - * @return int - */ - public function getLineNo() - { - return $this->iLineNo; - } - /** * @return string * diff --git a/src/Property/Charset.php b/src/Property/Charset.php index de9016ad4..1ebff3f3a 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -4,6 +4,8 @@ use Sabberworm\CSS\Comment\Comment; use Sabberworm\CSS\OutputFormat; +use Sabberworm\CSS\Position\Position; +use Sabberworm\CSS\Position\Positionable; use Sabberworm\CSS\Value\CSSString; /** @@ -14,8 +16,10 @@ * - May only appear at the very top of a Document’s contents. * - Must not appear more than once. */ -class Charset implements AtRule +class Charset implements AtRule, Positionable { + use Position; + /** * @var CSSString */ @@ -42,18 +46,10 @@ class Charset implements AtRule public function __construct(CSSString $oCharset, $iLineNo = 0) { $this->oCharset = $oCharset; - $this->iLineNo = $iLineNo; + $this->setPosition($iLineNo); $this->aComments = []; } - /** - * @return int - */ - public function getLineNo() - { - return $this->iLineNo; - } - /** * @param string|CSSString $oCharset * diff --git a/src/Property/Import.php b/src/Property/Import.php index e3f104743..5b4744939 100644 --- a/src/Property/Import.php +++ b/src/Property/Import.php @@ -4,13 +4,17 @@ use Sabberworm\CSS\Comment\Comment; use Sabberworm\CSS\OutputFormat; +use Sabberworm\CSS\Position\Position; +use Sabberworm\CSS\Position\Positionable; use Sabberworm\CSS\Value\URL; /** * Class representing an `@import` rule. */ -class Import implements AtRule +class Import implements AtRule, Positionable { + use Position; + /** * @var URL */ @@ -21,13 +25,6 @@ class Import implements AtRule */ private $sMediaQuery; - /** - * @var int - * - * @internal since 8.8.0 - */ - protected $iLineNo; - /** * @var array * @@ -44,18 +41,10 @@ public function __construct(URL $oLocation, $sMediaQuery, $iLineNo = 0) { $this->oLocation = $oLocation; $this->sMediaQuery = $sMediaQuery; - $this->iLineNo = $iLineNo; + $this->setPosition($iLineNo); $this->aComments = []; } - /** - * @return int - */ - public function getLineNo() - { - return $this->iLineNo; - } - /** * @param URL $oLocation * diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index 037132e33..e358d93f4 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -8,6 +8,8 @@ use Sabberworm\CSS\Parsing\ParserState; use Sabberworm\CSS\Parsing\UnexpectedEOFException; use Sabberworm\CSS\Parsing\UnexpectedTokenException; +use Sabberworm\CSS\Position\Position; +use Sabberworm\CSS\Position\Positionable; use Sabberworm\CSS\Renderable; use Sabberworm\CSS\Value\RuleValueList; use Sabberworm\CSS\Value\Value; @@ -17,8 +19,10 @@ * * In CSS, `Rule`s are expressed as follows: “key: value[0][0] value[0][1], value[1][0] value[1][1];” */ -class Rule implements Renderable, Commentable +class Rule implements Commentable, Positionable, Renderable { + use Position; + /** * @var string */ @@ -39,18 +43,6 @@ class Rule implements Renderable, Commentable */ private $aIeHack; - /** - * @var int - */ - protected $iLineNo; - - /** - * @var int - * - * @internal since 8.8.0 - */ - protected $iColNo; - /** * @var array * @@ -69,8 +61,7 @@ public function __construct($sRule, $iLineNo = 0, $iColNo = 0) $this->mValue = null; $this->bIsImportant = false; $this->aIeHack = []; - $this->iLineNo = $iLineNo; - $this->iColNo = $iColNo; + $this->setPosition($iLineNo, $iColNo); $this->aComments = []; } @@ -142,34 +133,6 @@ private static function listDelimiterForRule($sRule) } } - /** - * @return int - */ - public function getLineNo() - { - return $this->iLineNo; - } - - /** - * @return int - */ - public function getColNo() - { - return $this->iColNo; - } - - /** - * @param int $iLine - * @param int $iColumn - * - * @return void - */ - public function setPosition($iLine, $iColumn) - { - $this->iColNo = $iColumn; - $this->iLineNo = $iLine; - } - /** * @param string $sRule * @@ -295,7 +258,7 @@ public function addValue($mValue, $sType = ' ') } if (!$this->mValue instanceof RuleValueList || $this->mValue->getListSeparator() !== $sType) { $mCurrentValue = $this->mValue; - $this->mValue = new RuleValueList($sType, $this->iLineNo); + $this->mValue = new RuleValueList($sType, $this->getLineNumber()); if ($mCurrentValue) { $this->mValue->addListComponent($mCurrentValue); } diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index 3b6c87755..33426fd34 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -851,7 +851,10 @@ public function render($oOutputFormat) $sResult = $oOutputFormat->comments($this); if (count($this->aSelectors) === 0) { // If all the selectors have been removed, this declaration block becomes invalid - throw new OutputException("Attempt to print declaration block with missing selector", $this->iLineNo); + throw new OutputException( + 'Attempt to print declaration block with missing selector', + $this->getLineNumber() + ); } $sResult .= $oOutputFormat->sBeforeDeclarationBlock; $sResult .= $oOutputFormat->implode( diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index c08433898..85b94f263 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -8,6 +8,8 @@ use Sabberworm\CSS\Parsing\ParserState; use Sabberworm\CSS\Parsing\UnexpectedEOFException; use Sabberworm\CSS\Parsing\UnexpectedTokenException; +use Sabberworm\CSS\Position\Position; +use Sabberworm\CSS\Position\Positionable; use Sabberworm\CSS\Renderable; use Sabberworm\CSS\Rule\Rule; @@ -20,8 +22,10 @@ * If you want to manipulate a `RuleSet`, use the methods `addRule(Rule $rule)`, `getRules()` and `removeRule($rule)` * (which accepts either a `Rule` or a rule name; optionally suffixed by a dash to remove all related rules). */ -abstract class RuleSet implements Renderable, Commentable +abstract class RuleSet implements Commentable, Positionable, Renderable { + use Position; + /** * the rules in this rule set, using the property name as the key, * with potentially multiple rules per property name. @@ -30,13 +34,6 @@ abstract class RuleSet implements Renderable, Commentable */ private $aRules; - /** - * @var int - * - * @internal since 8.8.0 - */ - protected $iLineNo; - /** * @var array * @@ -50,7 +47,7 @@ abstract class RuleSet implements Renderable, Commentable public function __construct($iLineNo = 0) { $this->aRules = []; - $this->iLineNo = $iLineNo; + $this->setPosition($iLineNo); $this->aComments = []; } @@ -102,14 +99,6 @@ public static function parseRuleSet(ParserState $oParserState, RuleSet $oRuleSet $oParserState->consume('}'); } - /** - * @return int - */ - public function getLineNo() - { - return $this->iLineNo; - } - /** * @param Rule|null $oSibling * diff --git a/src/Value/CSSFunction.php b/src/Value/CSSFunction.php index fcf641d59..703f66585 100644 --- a/src/Value/CSSFunction.php +++ b/src/Value/CSSFunction.php @@ -34,7 +34,7 @@ public function __construct($sName, $aArguments, $sSeparator = ',', $iLineNo = 0 $aArguments = $aArguments->getListComponents(); } $this->sName = $sName; - $this->iLineNo = $iLineNo; + $this->setPosition($iLineNo); // TODO: redundant? parent::__construct($aArguments, $sSeparator, $iLineNo); } diff --git a/src/Value/Value.php b/src/Value/Value.php index 3816289df..beb74464a 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -6,27 +6,24 @@ use Sabberworm\CSS\Parsing\SourceException; use Sabberworm\CSS\Parsing\UnexpectedEOFException; use Sabberworm\CSS\Parsing\UnexpectedTokenException; +use Sabberworm\CSS\Position\Position; +use Sabberworm\CSS\Position\Positionable; use Sabberworm\CSS\Renderable; /** * Abstract base class for specific classes of CSS values: `Size`, `Color`, `CSSString` and `URL`, and another * abstract subclass `ValueList`. */ -abstract class Value implements Renderable +abstract class Value implements Positionable, Renderable { - /** - * @var int - * - * @internal since 8.8.0 - */ - protected $iLineNo; + use Position; /** * @param int $iLineNo */ public function __construct($iLineNo = 0) { - $this->iLineNo = $iLineNo; + $this->setPosition($iLineNo); } /** @@ -218,12 +215,4 @@ private static function parseUnicodeRangeValue(ParserState $oParserState) } while (strlen($sRange) < $iCodepointMaxLength && preg_match("/[A-Fa-f0-9\?-]/", $oParserState->peek())); return "U+{$sRange}"; } - - /** - * @return int - */ - public function getLineNo() - { - return $this->iLineNo; - } } From b584b14f015f44e5103867dbd599545c1a13a7f9 Mon Sep 17 00:00:00 2001 From: Jake Hotson Date: Mon, 7 Apr 2025 00:04:29 +0100 Subject: [PATCH 3/9] Update shorthand-related methods for compatibility --- 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 33426fd34..939190914 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -434,8 +434,8 @@ public function expandBackgroundShorthand() 'background-repeat' => ['repeat'], 'background-attachment' => ['scroll'], 'background-position' => [ - new Size(0, '%', false, $this->iLineNo), - new Size(0, '%', false, $this->iLineNo), + new Size(0, '%', false, $this->getLineNumber()), + new Size(0, '%', false, $this->getLineNumber()), ], ]; $mRuleValue = $oRule->getValue(); @@ -801,7 +801,7 @@ public function createFontShorthand() $aLHValues = $mRuleValue->getListComponents(); } if ($aLHValues[0] !== 'normal') { - $val = new RuleValueList('/', $this->iLineNo); + $val = new RuleValueList('/', $this->getLineNumber()); $val->addListComponent($aFSValues[0]); $val->addListComponent($aLHValues[0]); $oNewRule->addValue($val); @@ -817,7 +817,7 @@ public function createFontShorthand() } else { $aFFValues = $mRuleValue->getListComponents(); } - $oFFValue = new RuleValueList(',', $this->iLineNo); + $oFFValue = new RuleValueList(',', $this->getLineNumber()); $oFFValue->setListComponents($aFFValues); $oNewRule->addValue($oFFValue); From 23a56e5da7163e8c16fcfdd1b2cee8a233ade00b Mon Sep 17 00:00:00 2001 From: Jake Hotson Date: Mon, 7 Apr 2025 00:08:11 +0100 Subject: [PATCH 4/9] Add "rawr/cross-data-providers" needed for tests --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index acf650a2d..e8973f092 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,8 @@ "ext-iconv": "*" }, "require-dev": { - "phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.41" + "phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.41", + "rawr/cross-data-providers": "2.4.0" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" From 0f85ed0d4035d94bdd1c7f150390922dbe5a8202 Mon Sep 17 00:00:00 2001 From: Jake Hotson Date: Mon, 7 Apr 2025 00:28:36 +0100 Subject: [PATCH 5/9] Use `getLineNo()` not `getLineNumber()` as replacement for property access This will ensure unchanged behaviour. `getLineNumber()` will return `null` instead of `0`. --- 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 939190914..9cc14ebf7 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -434,8 +434,8 @@ public function expandBackgroundShorthand() 'background-repeat' => ['repeat'], 'background-attachment' => ['scroll'], 'background-position' => [ - new Size(0, '%', false, $this->getLineNumber()), - new Size(0, '%', false, $this->getLineNumber()), + new Size(0, '%', false, $this->getLineNo()), + new Size(0, '%', false, $this->getLineNo()), ], ]; $mRuleValue = $oRule->getValue(); @@ -801,7 +801,7 @@ public function createFontShorthand() $aLHValues = $mRuleValue->getListComponents(); } if ($aLHValues[0] !== 'normal') { - $val = new RuleValueList('/', $this->getLineNumber()); + $val = new RuleValueList('/', $this->getLineNo()); $val->addListComponent($aFSValues[0]); $val->addListComponent($aLHValues[0]); $oNewRule->addValue($val); @@ -817,7 +817,7 @@ public function createFontShorthand() } else { $aFFValues = $mRuleValue->getListComponents(); } - $oFFValue = new RuleValueList(',', $this->getLineNumber()); + $oFFValue = new RuleValueList(',', $this->getLineNo()); $oFFValue->setListComponents($aFFValues); $oNewRule->addValue($oFFValue); From 4ecf7d95b31267d37395f9ec23f5e88409ad8663 Mon Sep 17 00:00:00 2001 From: Jake Hotson Date: Mon, 7 Apr 2025 00:33:24 +0100 Subject: [PATCH 6/9] Remove use of PHP language features not available in 5.6 --- src/Position/Position.php | 18 +++--- src/Position/Positionable.php | 10 +-- tests/Unit/Position/PositionTest.php | 63 ++++++++++++++----- .../UnitDeprecated/Position/PositionTest.php | 45 +++++++++---- 4 files changed, 98 insertions(+), 38 deletions(-) diff --git a/src/Position/Position.php b/src/Position/Position.php index 655f34b9b..1c4d0df02 100644 --- a/src/Position/Position.php +++ b/src/Position/Position.php @@ -26,7 +26,7 @@ trait Position /** * @return int<1, max>|null */ - public function getLineNumber(): ?int + public function getLineNumber() { return $this->lineNumber; } @@ -34,15 +34,17 @@ public function getLineNumber(): ?int /** * @return int<0, max> */ - public function getLineNo(): int + public function getLineNo() { - return $this->getLineNumber() ?? 0; + $lineNumber = $this->getLineNumber(); + + return $lineNumber !== null ? $lineNumber : 0; } /** * @return int<0, max>|null */ - public function getColumnNumber(): ?int + public function getColumnNumber() { return $this->columnNumber; } @@ -50,16 +52,18 @@ public function getColumnNumber(): ?int /** * @return int<0, max> */ - public function getColNo(): int + public function getColNo() { - return $this->getColumnNumber() ?? 0; + $columnNumber = $this->getColumnNumber(); + + return $columnNumber !== null ? $columnNumber : 0; } /** * @param int<0, max>|null $lineNumber * @param int<0, max>|null $columnNumber */ - public function setPosition(?int $lineNumber, ?int $columnNumber = null): void + public function setPosition($lineNumber, $columnNumber = null) { // The conditional is for backwards compatibility (backcompat); `0` will not be allowed in future. $this->lineNumber = $lineNumber !== 0 ? $lineNumber : null; diff --git a/src/Position/Positionable.php b/src/Position/Positionable.php index 124752feb..d57cc7fdb 100644 --- a/src/Position/Positionable.php +++ b/src/Position/Positionable.php @@ -14,26 +14,26 @@ interface Positionable /** * @return int<1, max>|null */ - public function getLineNumber(): ?int; + public function getLineNumber(); /** * @deprecated in version 9.0.0, will be removed in v10.0. Use `getLineNumber()` instead. * * @return int<0, max> */ - public function getLineNo(): int; + public function getLineNo(); /** * @return int<0, max>|null */ - public function getColumnNumber(): ?int; + public function getColumnNumber(); /** * @deprecated in version 9.0.0, will be removed in v10.0. Use `getColumnNumber()` instead. * * @return int<0, max> */ - public function getColNo(): int; + public function getColNo(); /** * @param int<0, max>|null $lineNumber @@ -41,5 +41,5 @@ public function getColNo(): int; * Use `null` instead when no line number is available. * @param int<0, max>|null $columnNumber */ - public function setPosition(?int $lineNumber, ?int $columnNumber = null): void; + public function setPosition($lineNumber, $columnNumber = null); } diff --git a/tests/Unit/Position/PositionTest.php b/tests/Unit/Position/PositionTest.php index 86233d7c9..8f07b0a45 100644 --- a/tests/Unit/Position/PositionTest.php +++ b/tests/Unit/Position/PositionTest.php @@ -18,7 +18,10 @@ final class PositionTest extends TestCase */ private $subject; - protected function setUp(): void + /** + * The method signature of `setUp()` is not compatible with all PHP and PHPUnit versions supported. + */ + protected function doSetUp() { $this->subject = new ConcretePosition(); } @@ -26,23 +29,27 @@ protected function setUp(): void /** * @test */ - public function getLineNumberInitiallyReturnsNull(): void + public function getLineNumberInitiallyReturnsNull() { + $this->doSetUp(); + self::assertNull($this->subject->getLineNumber()); } /** * @test */ - public function getColumnNumberInitiallyReturnsNull(): void + public function getColumnNumberInitiallyReturnsNull() { + $this->doSetUp(); + self::assertNull($this->subject->getColumnNumber()); } /** * @return array}> */ - public function provideLineNumber(): array + public function provideLineNumber() { return [ 'line 1' => [1], @@ -57,8 +64,10 @@ public function provideLineNumber(): array * * @dataProvider provideLineNumber */ - public function setPositionOnVirginSetsLineNumber(int $lineNumber): void + public function setPositionOnVirginSetsLineNumber($lineNumber) { + $this->doSetUp(); + $this->subject->setPosition($lineNumber); self::assertSame($lineNumber, $this->subject->getLineNumber()); @@ -71,8 +80,10 @@ public function setPositionOnVirginSetsLineNumber(int $lineNumber): void * * @dataProvider provideLineNumber */ - public function setPositionSetsNewLineNumber(int $lineNumber): void + public function setPositionSetsNewLineNumber($lineNumber) { + $this->doSetUp(); + $this->subject->setPosition(99); $this->subject->setPosition($lineNumber); @@ -83,8 +94,10 @@ public function setPositionSetsNewLineNumber(int $lineNumber): void /** * @test */ - public function setPositionWithNullClearsLineNumber(): void + public function setPositionWithNullClearsLineNumber() { + $this->doSetUp(); + $this->subject->setPosition(99); $this->subject->setPosition(null); @@ -95,7 +108,7 @@ public function setPositionWithNullClearsLineNumber(): void /** * @return array}> */ - public function provideColumnNumber(): array + public function provideColumnNumber() { return [ 'column 0' => [0], @@ -111,8 +124,10 @@ public function provideColumnNumber(): array * * @dataProvider provideColumnNumber */ - public function setPositionOnVirginSetsColumnNumber(int $columnNumber): void + public function setPositionOnVirginSetsColumnNumber($columnNumber) { + $this->doSetUp(); + $this->subject->setPosition(1, $columnNumber); self::assertSame($columnNumber, $this->subject->getColumnNumber()); @@ -121,10 +136,14 @@ public function setPositionOnVirginSetsColumnNumber(int $columnNumber): void /** * @test * + * @param int $columnNumber + * * @dataProvider provideColumnNumber */ - public function setPositionSetsNewColumnNumber(int $columnNumber): void + public function setPositionSetsNewColumnNumber($columnNumber) { + $this->doSetUp(); + $this->subject->setPosition(1, 99); $this->subject->setPosition(2, $columnNumber); @@ -135,8 +154,10 @@ public function setPositionSetsNewColumnNumber(int $columnNumber): void /** * @test */ - public function setPositionWithoutColumnNumberClearsColumnNumber(): void + public function setPositionWithoutColumnNumberClearsColumnNumber() { + $this->doSetUp(); + $this->subject->setPosition(1, 99); $this->subject->setPosition(2); @@ -147,8 +168,10 @@ public function setPositionWithoutColumnNumberClearsColumnNumber(): void /** * @test */ - public function setPositionWithNullForColumnNumberClearsColumnNumber(): void + public function setPositionWithNullForColumnNumberClearsColumnNumber() { + $this->doSetUp(); + $this->subject->setPosition(1, 99); $this->subject->setPosition(2, null); @@ -159,7 +182,7 @@ public function setPositionWithNullForColumnNumberClearsColumnNumber(): void /** * @return array, 1: int<0, max>}> */ - public function provideLineAndColumnNumber(): array + public function provideLineAndColumnNumber() { return DataProviders::cross($this->provideLineNumber(), $this->provideColumnNumber()); } @@ -167,10 +190,15 @@ public function provideLineAndColumnNumber(): array /** * @test * + * @param int $lineNumber + * @param int $columnNumber + * * @dataProvider provideLineAndColumnNumber */ - public function setPositionOnVirginSetsLineAndColumnNumber(int $lineNumber, int $columnNumber): void + public function setPositionOnVirginSetsLineAndColumnNumber($lineNumber, $columnNumber) { + $this->doSetUp(); + $this->subject->setPosition($lineNumber, $columnNumber); self::assertSame($lineNumber, $this->subject->getLineNumber()); @@ -180,10 +208,15 @@ public function setPositionOnVirginSetsLineAndColumnNumber(int $lineNumber, int /** * @test * + * @param int $lineNumber + * @param int $columnNumber + * * @dataProvider provideLineAndColumnNumber */ - public function setPositionSetsNewLineAndColumnNumber(int $lineNumber, int $columnNumber): void + public function setPositionSetsNewLineAndColumnNumber($lineNumber, $columnNumber) { + $this->doSetUp(); + $this->subject->setPosition(98, 99); $this->subject->setPosition($lineNumber, $columnNumber); diff --git a/tests/UnitDeprecated/Position/PositionTest.php b/tests/UnitDeprecated/Position/PositionTest.php index 2e06cc6d8..f7378e04b 100644 --- a/tests/UnitDeprecated/Position/PositionTest.php +++ b/tests/UnitDeprecated/Position/PositionTest.php @@ -17,7 +17,10 @@ final class PositionTest extends TestCase */ private $subject; - protected function setUp(): void + /** + * The method signature of `setUp()` is not compatible with all PHP and PHPUnit versions supported. + */ + protected function doSetUp() { $this->subject = new ConcretePosition(); } @@ -25,7 +28,7 @@ protected function setUp(): void /** * @return array}> */ - public function provideLineNumber(): array + public function provideLineNumber() { return [ 'line 1' => [1], @@ -36,7 +39,7 @@ public function provideLineNumber(): array /** * @return array}> */ - public function provideColumnNumber(): array + public function provideColumnNumber() { return [ 'column 0' => [0], @@ -48,18 +51,24 @@ public function provideColumnNumber(): array /** * @test */ - public function getLineNoInitiallyReturnsZero(): void + public function getLineNoInitiallyReturnsZero() { + $this->doSetUp(); + self::assertSame(0, $this->subject->getLineNo()); } /** * @test * + * @paarm int $lineNumber + * * @dataProvider provideLineNumber */ - public function getLineNoReturnsLineNumberSet(int $lineNumber): void + public function getLineNoReturnsLineNumberSet($lineNumber) { + $this->doSetUp(); + $this->subject->setPosition($lineNumber); self::assertSame($lineNumber, $this->subject->getLineNo()); @@ -68,8 +77,10 @@ public function getLineNoReturnsLineNumberSet(int $lineNumber): void /** * @test */ - public function getLineNoReturnsZeroAfterLineNumberCleared(): void + public function getLineNoReturnsZeroAfterLineNumberCleared() { + $this->doSetUp(); + $this->subject->setPosition(99); $this->subject->setPosition(null); @@ -80,18 +91,24 @@ public function getLineNoReturnsZeroAfterLineNumberCleared(): void /** * @test */ - public function getColNoInitiallyReturnsZero(): void + public function getColNoInitiallyReturnsZero() { + $this->doSetUp(); + self::assertSame(0, $this->subject->getColNo()); } /** * @test * + * @param int $columnNumber + * * @dataProvider provideColumnNumber */ - public function getColNoReturnsColumnNumberSet(int $columnNumber): void + public function getColNoReturnsColumnNumberSet($columnNumber) { + $this->doSetUp(); + $this->subject->setPosition(1, $columnNumber); self::assertSame($columnNumber, $this->subject->getColNo()); @@ -100,8 +117,10 @@ public function getColNoReturnsColumnNumberSet(int $columnNumber): void /** * @test */ - public function getColNoReturnsZeroAfterColumnNumberCleared(): void + public function getColNoReturnsZeroAfterColumnNumberCleared() { + $this->doSetUp(); + $this->subject->setPosition(1, 99); $this->subject->setPosition(2); @@ -112,8 +131,10 @@ public function getColNoReturnsZeroAfterColumnNumberCleared(): void /** * @test */ - public function setPositionWithZeroClearsLineNumber(): void + public function setPositionWithZeroClearsLineNumber() { + $this->doSetUp(); + $this->subject->setPosition(99); $this->subject->setPosition(0); @@ -124,8 +145,10 @@ public function setPositionWithZeroClearsLineNumber(): void /** * @test */ - public function getLineNoAfterSetPositionWithZeroReturnsZero(): void + public function getLineNoAfterSetPositionWithZeroReturnsZero() { + $this->doSetUp(); + $this->subject->setPosition(99); $this->subject->setPosition(0); From 7bef8876aecd2a8c641656bb98d73b7af4461261 Mon Sep 17 00:00:00 2001 From: Jake Hotson Date: Mon, 7 Apr 2025 01:19:08 +0100 Subject: [PATCH 7/9] Allow "rawr/cross-data-providers" version compatible with PHP 7.0 This version supposedly supports PHP 5.6, but seems to have different class names, so is not compatible. Instead, on 5.6, mark tests as skipped if they can't be run. --- composer.json | 2 +- tests/Unit/Position/PositionTest.php | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index e8973f092..e3caf0cca 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ }, "require-dev": { "phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.41", - "rawr/cross-data-providers": "2.4.0" + "rawr/cross-data-providers": "^2.0.0" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" diff --git a/tests/Unit/Position/PositionTest.php b/tests/Unit/Position/PositionTest.php index 8f07b0a45..862228805 100644 --- a/tests/Unit/Position/PositionTest.php +++ b/tests/Unit/Position/PositionTest.php @@ -184,6 +184,11 @@ public function setPositionWithNullForColumnNumberClearsColumnNumber() */ public function provideLineAndColumnNumber() { + if (!\class_exists(DataProviders::class)) { + self::markTestSkipped('`DataProviders` class is not available'); + return []; + } + return DataProviders::cross($this->provideLineNumber(), $this->provideColumnNumber()); } From e06ed2eeff3d237a7be05ba0edac0e69c22ecc59 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Mon, 7 Apr 2025 10:22:26 +0100 Subject: [PATCH 8/9] Apply #1233 (and #1235) Also include the relevant DocBlock reordering from autoformatting in #1228. --- src/Position/Positionable.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Position/Positionable.php b/src/Position/Positionable.php index d57cc7fdb..4539c4251 100644 --- a/src/Position/Positionable.php +++ b/src/Position/Positionable.php @@ -17,9 +17,9 @@ interface Positionable public function getLineNumber(); /** - * @deprecated in version 9.0.0, will be removed in v10.0. Use `getLineNumber()` instead. - * * @return int<0, max> + * + * @deprecated in version 8.9.0, will be removed in v9.0. Use `getLineNumber()` instead. */ public function getLineNo(); @@ -29,15 +29,15 @@ public function getLineNo(); public function getColumnNumber(); /** - * @deprecated in version 9.0.0, will be removed in v10.0. Use `getColumnNumber()` instead. - * * @return int<0, max> + * + * @deprecated in version 8.9.0, will be removed in v9.0. Use `getColumnNumber()` instead. */ public function getColNo(); /** * @param int<0, max>|null $lineNumber - * Providing zero for this parameter is deprecated in version 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 0c586e78b890df5f4155b1071a0581bff9bd433c Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Mon, 7 Apr 2025 18:13:58 +0100 Subject: [PATCH 9/9] [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 79cd6ce3c..891adc8af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,10 +24,11 @@ This project adheres to [Semantic Versioning](https://semver.org/). - `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