From 4597183300a157b8eddd8322137d04469c00f137 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 7 Dec 2024 12:58:41 +0000 Subject: [PATCH 01/14] Mark DOMNamedNodeMap as taking a covariant DOMNode And indicate that `DOMNode::$attributes` is a node map of `DOMAttr` --- stubs/dom.stub | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/stubs/dom.stub b/stubs/dom.stub index d2a5c575fc..dac7505556 100644 --- a/stubs/dom.stub +++ b/stubs/dom.stub @@ -29,7 +29,8 @@ class DOMDocument class DOMNode { - + /** @var DOMNamedNodeMap|null */ + public readonly ?DOMNamedNodeMap $attributes; } class DOMElement extends DOMNode @@ -141,9 +142,12 @@ class DOMProcessingInstruction } /** + * @template-covariant TNode as DOMNode + * @implements Traversable + * @implements IteratorAggregate * @property-read int $length */ -class DOMNamedNodeMap +class DOMNamedNodeMap implements Traversable, IteratorAggregate, Countable { } From 7d7fd5434263c97cbde4916d2f698d6ed2d0f16a Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 7 Dec 2024 13:15:49 +0000 Subject: [PATCH 02/14] Add missing `extends DOMNode` to DOM classes --- stubs/dom.stub | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/stubs/dom.stub b/stubs/dom.stub index dac7505556..ae1b31cbfd 100644 --- a/stubs/dom.stub +++ b/stubs/dom.stub @@ -1,6 +1,6 @@ Date: Sun, 8 Dec 2024 14:03:01 +0000 Subject: [PATCH 03/14] Add test --- .../Properties/AccessPropertiesRuleTest.php | 8 ++++++++ .../data/dom-legacy-ext-template-nodes.php | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php index a07611980b..e33dbd7b4e 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php @@ -957,4 +957,12 @@ public function testTraitMixin(): void $this->analyse([__DIR__ . '/data/trait-mixin.php'], []); } + public function testDomExtensionLegacyTemplateNodes(): void + { + $this->checkThisOnly = false; + $this->checkUnionTypes = true; + $this->checkDynamicProperties = true; + $this->analyse([__DIR__ . '/data/dom-legacy-ext-template-nodes.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php b/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php new file mode 100644 index 0000000000..ce722cc246 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php @@ -0,0 +1,18 @@ +', $node->attributes); +}; + +function parse(\DOMElement $node): void +{ + assertType(DOMNamedNodeMap::class . '<' . DOMAttr::class . '>', $node->attributes); + $attribute = $node->attributes->getNamedItem('class'); + if ($attribute === null) { + return; + } + assertType(DOMAttr::class, $attribute); + assertType('string', $attribute->value); +} From bceb24d86fd45ca727248f167b842e4144cd36a8 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sun, 8 Dec 2024 14:29:11 +0000 Subject: [PATCH 04/14] Add stuff --- stubs/dom.stub | 25 +++++++++++++++++-- .../Analyser/nsrt/DOMLegacyNamedNodeNap.php | 22 ++++++++++++++++ .../data/dom-legacy-ext-template-nodes.php | 4 +-- 3 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/DOMLegacyNamedNodeNap.php diff --git a/stubs/dom.stub b/stubs/dom.stub index ae1b31cbfd..3ab8f15c41 100644 --- a/stubs/dom.stub +++ b/stubs/dom.stub @@ -29,8 +29,11 @@ class DOMDocument extends DOMNode class DOMNode { - /** @var DOMNamedNodeMap|null */ - public readonly ?DOMNamedNodeMap $attributes; + /** + * @readonly + * @var DOMNamedNodeMap|null + */ + public $attributes; } class DOMElement extends DOMNode @@ -150,6 +153,24 @@ class DOMProcessingInstruction extends DOMNode class DOMNamedNodeMap implements Traversable, IteratorAggregate, Countable { + /** + * @return (TNode | null) + */ + public function getNamedItem(string $qualifiedName) + { + } + /** + * @return (TNode | null) + */ + public function getNamedItemNS(?string $namespace, string $localName) + { + } + /** + * @return (TNode | null) + */ + public function item(int $index) + { + } } class DOMText extends DOMCharacterData diff --git a/tests/PHPStan/Analyser/nsrt/DOMLegacyNamedNodeNap.php b/tests/PHPStan/Analyser/nsrt/DOMLegacyNamedNodeNap.php new file mode 100644 index 0000000000..542feeaf59 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/DOMLegacyNamedNodeNap.php @@ -0,0 +1,22 @@ +', $node->attributes); + if ($node->hasAttribute('attr')) { + assertType('DOMAttr', $node->attributes->getNamedItem('attr')); + } else { + assertType('null', $node->attributes->getNamedItem('attr')); + } + } +} diff --git a/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php b/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php index ce722cc246..44f5e882f0 100644 --- a/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php +++ b/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php @@ -2,11 +2,11 @@ use function PHPStan\Testing\assertType; -function (\DOMNode $node): void { +function basic_node(\DOMNode $node): void { assertType(DOMNamedNodeMap::class . '<' . DOMAttr::class . '>', $node->attributes); }; -function parse(\DOMElement $node): void +function element_node(\DOMElement $node): void { assertType(DOMNamedNodeMap::class . '<' . DOMAttr::class . '>', $node->attributes); $attribute = $node->attributes->getNamedItem('class'); From a874a1f7f04b6bad40b7740ac4422136c5218f1a Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sun, 8 Dec 2024 17:03:24 +0000 Subject: [PATCH 05/14] Try to parameterrize DOMAttr --- stubs/dom.stub | 39 ++++++++++++++++++- .../data/dom-legacy-ext-template-nodes.php | 24 ++++++++---- 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/stubs/dom.stub b/stubs/dom.stub index 3ab8f15c41..cd1ed421fc 100644 --- a/stubs/dom.stub +++ b/stubs/dom.stub @@ -31,9 +31,12 @@ class DOMNode { /** * @readonly - * @var DOMNamedNodeMap|null + * @var DOMNamedNodeMap>|null */ public $attributes; + + /** @phpstan-assert-if-true !null $this->attributes */ + public function hasAttributes(): bool } class DOMElement extends DOMNode @@ -42,6 +45,13 @@ class DOMElement extends DOMNode /** @var DOMDocument */ public $ownerDocument; + /** + * @readonly + * @inherited + * @var DOMNamedNodeMap>|null + */ + public $attributes; + /** * @param string $name * @return DOMNodeList @@ -55,6 +65,23 @@ class DOMElement extends DOMNode */ public function getElementsByTagNameNS ($namespaceURI, $localName) {} + /** + * @template T of string + * @param T $qualifiedName + * @return bool + * @phpstan-assert-if-true (T is 'xml:id' ? DOMNameSpaceNode : DOMAttr) $this->hasAttribute($qualifiedName) + * @phpstan-assert-if-true DOMNameSpaceNode|DOMAttr> $this->$attributes + */ + public function hasAttribute(string $qualifiedName): bool + + public function hasAttributeNS(?string $namespace, string $localName): bool + + /** + * @template T of string + * @param T $qualifiedName + * @return (T is 'xml:id' ? DOMNameSpaceNode|false : DOMAttr|false) + */ + public function getAttributeNode(string $qualifiedName): DOMAttr|DOMNameSpaceNode|false } /** @@ -86,12 +113,20 @@ class DOMXPath } +/** + * @template Name as string + */ class DOMAttr extends DOMNode { - /** @var DOMDocument */ public $ownerDocument; + /** + * @var Name + * @readonly + */ + public $name; + } class DOMCharacterData extends DOMNode diff --git a/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php b/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php index 44f5e882f0..ace3e16145 100644 --- a/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php +++ b/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php @@ -3,16 +3,24 @@ use function PHPStan\Testing\assertType; function basic_node(\DOMNode $node): void { - assertType(DOMNamedNodeMap::class . '<' . DOMAttr::class . '>', $node->attributes); + if ($node->hasAttributes()) { + assertType(DOMNamedNodeMap::class . '<' . DOMAttr::class . '>', $node->attributes); + } else { + assertType('null', $node->attributes); + } }; -function element_node(\DOMElement $node): void +function element_node(\DOMElement $element): void { - assertType(DOMNamedNodeMap::class . '<' . DOMAttr::class . '>', $node->attributes); - $attribute = $node->attributes->getNamedItem('class'); - if ($attribute === null) { - return; + if ($element->hasAttribute('class')) { + assertType(DOMNamedNodeMap::class . '<' . DOMAttr::class . '>', $element->attributes); + $attribute = $element->attributes->getNamedItem('class'); + if ($attribute === null) { + return; + } + assertType(DOMAttr::class, $attribute); + assertType('string', $attribute->value); + } else { + assertType('null', $element->attributes); } - assertType(DOMAttr::class, $attribute); - assertType('string', $attribute->value); } From 4b27e4bad5c07863204435601ffa62ad856795b6 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sun, 8 Dec 2024 17:13:25 +0000 Subject: [PATCH 06/14] bleh --- stubs/dom.stub | 34 ++++------------------------------ 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/stubs/dom.stub b/stubs/dom.stub index cd1ed421fc..378b393c88 100644 --- a/stubs/dom.stub +++ b/stubs/dom.stub @@ -31,7 +31,7 @@ class DOMNode { /** * @readonly - * @var DOMNamedNodeMap>|null + * @var DOMNamedNodeMap|null */ public $attributes; @@ -45,13 +45,6 @@ class DOMElement extends DOMNode /** @var DOMDocument */ public $ownerDocument; - /** - * @readonly - * @inherited - * @var DOMNamedNodeMap>|null - */ - public $attributes; - /** * @param string $name * @return DOMNodeList @@ -66,22 +59,12 @@ class DOMElement extends DOMNode public function getElementsByTagNameNS ($namespaceURI, $localName) {} /** - * @template T of string - * @param T $qualifiedName - * @return bool - * @phpstan-assert-if-true (T is 'xml:id' ? DOMNameSpaceNode : DOMAttr) $this->hasAttribute($qualifiedName) - * @phpstan-assert-if-true DOMNameSpaceNode|DOMAttr> $this->$attributes + * @phpstan-assert-if-true DOMNameSpaceNode|DOMAttr $this->hasAttribute($qualifiedName) + * @phpstan-assert-if-true DOMNamedNodeMap $this->attributes */ public function hasAttribute(string $qualifiedName): bool - public function hasAttributeNS(?string $namespace, string $localName): bool - - /** - * @template T of string - * @param T $qualifiedName - * @return (T is 'xml:id' ? DOMNameSpaceNode|false : DOMAttr|false) - */ - public function getAttributeNode(string $qualifiedName): DOMAttr|DOMNameSpaceNode|false + public function getAttributeNode(string $qualifiedName): DOMAttr|DOMNameSpaceNode|false } /** @@ -113,20 +96,11 @@ class DOMXPath } -/** - * @template Name as string - */ class DOMAttr extends DOMNode { /** @var DOMDocument */ public $ownerDocument; - /** - * @var Name - * @readonly - */ - public $name; - } class DOMCharacterData extends DOMNode From f28d6bc0a81a1b8de0fa0d011e7faa0390dc2570 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sun, 8 Dec 2024 17:13:32 +0000 Subject: [PATCH 07/14] Revert "bleh" This reverts commit d68106977a34728f600722d65d3eb651d9d20707. --- stubs/dom.stub | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/stubs/dom.stub b/stubs/dom.stub index 378b393c88..cd1ed421fc 100644 --- a/stubs/dom.stub +++ b/stubs/dom.stub @@ -31,7 +31,7 @@ class DOMNode { /** * @readonly - * @var DOMNamedNodeMap|null + * @var DOMNamedNodeMap>|null */ public $attributes; @@ -45,6 +45,13 @@ class DOMElement extends DOMNode /** @var DOMDocument */ public $ownerDocument; + /** + * @readonly + * @inherited + * @var DOMNamedNodeMap>|null + */ + public $attributes; + /** * @param string $name * @return DOMNodeList @@ -59,12 +66,22 @@ class DOMElement extends DOMNode public function getElementsByTagNameNS ($namespaceURI, $localName) {} /** - * @phpstan-assert-if-true DOMNameSpaceNode|DOMAttr $this->hasAttribute($qualifiedName) - * @phpstan-assert-if-true DOMNamedNodeMap $this->attributes + * @template T of string + * @param T $qualifiedName + * @return bool + * @phpstan-assert-if-true (T is 'xml:id' ? DOMNameSpaceNode : DOMAttr) $this->hasAttribute($qualifiedName) + * @phpstan-assert-if-true DOMNameSpaceNode|DOMAttr> $this->$attributes */ public function hasAttribute(string $qualifiedName): bool - public function getAttributeNode(string $qualifiedName): DOMAttr|DOMNameSpaceNode|false + public function hasAttributeNS(?string $namespace, string $localName): bool + + /** + * @template T of string + * @param T $qualifiedName + * @return (T is 'xml:id' ? DOMNameSpaceNode|false : DOMAttr|false) + */ + public function getAttributeNode(string $qualifiedName): DOMAttr|DOMNameSpaceNode|false } /** @@ -96,11 +113,20 @@ class DOMXPath } +/** + * @template Name as string + */ class DOMAttr extends DOMNode { /** @var DOMDocument */ public $ownerDocument; + /** + * @var Name + * @readonly + */ + public $name; + } class DOMCharacterData extends DOMNode From 1bdd8760db0b661d86363f1df0b99e8330467c57 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sun, 8 Dec 2024 17:13:55 +0000 Subject: [PATCH 08/14] Revert "Try to parameterrize DOMAttr" This reverts commit 3d93d420b54657983f329c5408bab9259daa551b. --- stubs/dom.stub | 39 +------------------ .../data/dom-legacy-ext-template-nodes.php | 24 ++++-------- 2 files changed, 10 insertions(+), 53 deletions(-) diff --git a/stubs/dom.stub b/stubs/dom.stub index cd1ed421fc..3ab8f15c41 100644 --- a/stubs/dom.stub +++ b/stubs/dom.stub @@ -31,12 +31,9 @@ class DOMNode { /** * @readonly - * @var DOMNamedNodeMap>|null + * @var DOMNamedNodeMap|null */ public $attributes; - - /** @phpstan-assert-if-true !null $this->attributes */ - public function hasAttributes(): bool } class DOMElement extends DOMNode @@ -45,13 +42,6 @@ class DOMElement extends DOMNode /** @var DOMDocument */ public $ownerDocument; - /** - * @readonly - * @inherited - * @var DOMNamedNodeMap>|null - */ - public $attributes; - /** * @param string $name * @return DOMNodeList @@ -65,23 +55,6 @@ class DOMElement extends DOMNode */ public function getElementsByTagNameNS ($namespaceURI, $localName) {} - /** - * @template T of string - * @param T $qualifiedName - * @return bool - * @phpstan-assert-if-true (T is 'xml:id' ? DOMNameSpaceNode : DOMAttr) $this->hasAttribute($qualifiedName) - * @phpstan-assert-if-true DOMNameSpaceNode|DOMAttr> $this->$attributes - */ - public function hasAttribute(string $qualifiedName): bool - - public function hasAttributeNS(?string $namespace, string $localName): bool - - /** - * @template T of string - * @param T $qualifiedName - * @return (T is 'xml:id' ? DOMNameSpaceNode|false : DOMAttr|false) - */ - public function getAttributeNode(string $qualifiedName): DOMAttr|DOMNameSpaceNode|false } /** @@ -113,20 +86,12 @@ class DOMXPath } -/** - * @template Name as string - */ class DOMAttr extends DOMNode { + /** @var DOMDocument */ public $ownerDocument; - /** - * @var Name - * @readonly - */ - public $name; - } class DOMCharacterData extends DOMNode diff --git a/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php b/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php index ace3e16145..44f5e882f0 100644 --- a/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php +++ b/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php @@ -3,24 +3,16 @@ use function PHPStan\Testing\assertType; function basic_node(\DOMNode $node): void { - if ($node->hasAttributes()) { - assertType(DOMNamedNodeMap::class . '<' . DOMAttr::class . '>', $node->attributes); - } else { - assertType('null', $node->attributes); - } + assertType(DOMNamedNodeMap::class . '<' . DOMAttr::class . '>', $node->attributes); }; -function element_node(\DOMElement $element): void +function element_node(\DOMElement $node): void { - if ($element->hasAttribute('class')) { - assertType(DOMNamedNodeMap::class . '<' . DOMAttr::class . '>', $element->attributes); - $attribute = $element->attributes->getNamedItem('class'); - if ($attribute === null) { - return; - } - assertType(DOMAttr::class, $attribute); - assertType('string', $attribute->value); - } else { - assertType('null', $element->attributes); + assertType(DOMNamedNodeMap::class . '<' . DOMAttr::class . '>', $node->attributes); + $attribute = $node->attributes->getNamedItem('class'); + if ($attribute === null) { + return; } + assertType(DOMAttr::class, $attribute); + assertType('string', $attribute->value); } From 156201f927e29f6ad9e403809e960b4b29e82e71 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sun, 8 Dec 2024 17:21:33 +0000 Subject: [PATCH 09/14] DOMNode::hasAttributes() assertions --- stubs/dom.stub | 6 ++++++ .../data/dom-legacy-ext-template-nodes.php | 17 +++++------------ 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/stubs/dom.stub b/stubs/dom.stub index 3ab8f15c41..a872364200 100644 --- a/stubs/dom.stub +++ b/stubs/dom.stub @@ -34,6 +34,12 @@ class DOMNode * @var DOMNamedNodeMap|null */ public $attributes; + + /** + * @phpstan-assert-if-true DOMNamedNodeMap $this->attributes + * @phpstan-assert-if-false null $this->attributes + */ + public function hasAttributes(): bool {} } class DOMElement extends DOMNode diff --git a/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php b/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php index 44f5e882f0..77173dddf8 100644 --- a/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php +++ b/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php @@ -3,16 +3,9 @@ use function PHPStan\Testing\assertType; function basic_node(\DOMNode $node): void { - assertType(DOMNamedNodeMap::class . '<' . DOMAttr::class . '>', $node->attributes); -}; - -function element_node(\DOMElement $node): void -{ - assertType(DOMNamedNodeMap::class . '<' . DOMAttr::class . '>', $node->attributes); - $attribute = $node->attributes->getNamedItem('class'); - if ($attribute === null) { - return; + if ($node->hasAttributes()) { + assertType(DOMNamedNodeMap::class . '<' . DOMAttr::class . '>', $node->attributes); + } else { + assertType('null', $node->attributes); } - assertType(DOMAttr::class, $attribute); - assertType('string', $attribute->value); -} +}; From d7ecb21308ebdeff406a971250632165a44929d3 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sun, 8 Dec 2024 21:32:49 +0000 Subject: [PATCH 10/14] Tests updates prior to ondrej --- stubs/dom.stub | 8 ++++ .../Analyser/nsrt/DOMLegacyNamedNodeNap.php | 37 ++++++++++++++++--- .../data/dom-legacy-ext-template-nodes.php | 29 ++++++++++++++- 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/stubs/dom.stub b/stubs/dom.stub index a872364200..a7935e2de5 100644 --- a/stubs/dom.stub +++ b/stubs/dom.stub @@ -61,6 +61,14 @@ class DOMElement extends DOMNode */ public function getElementsByTagNameNS ($namespaceURI, $localName) {} + /** + * @template-covariant T of string + * @param T $qualifiedName + * @phpstan-assert-if-true DOMAttr|DOMNameSpaceNode $this->getAttributeNode($qualifiedName) + */ + public function hasAttribute(string $qualifiedName): bool {} + + public function getAttributeNode(string $qualifiedName): DOMAttr|DOMNameSpaceNode|false {} } /** diff --git a/tests/PHPStan/Analyser/nsrt/DOMLegacyNamedNodeNap.php b/tests/PHPStan/Analyser/nsrt/DOMLegacyNamedNodeNap.php index 542feeaf59..dacab6d5b2 100644 --- a/tests/PHPStan/Analyser/nsrt/DOMLegacyNamedNodeNap.php +++ b/tests/PHPStan/Analyser/nsrt/DOMLegacyNamedNodeNap.php @@ -5,18 +5,45 @@ namespace DOMLegacyNamedNodeMap; use DOMAttr; +use DOMElement; use DOMNode; use DOMNamedNodeMap; use function PHPStan\Testing\assertType; class Foo { - public function checkAttributes(DOMNode $node): void { - assertType('DOMNamedNodeMap', $node->attributes); - if ($node->hasAttribute('attr')) { - assertType('DOMAttr', $node->attributes->getNamedItem('attr')); + public function basic_node(DOMNode $node): void { + if ($node->hasAttributes()) { + assertType('DOMNamedNodeMap', $node->attributes); } else { - assertType('null', $node->attributes->getNamedItem('attr')); + assertType('null', $node->attributes); + } + } + + public function element_node(DOMElement $element): void + { + if ($element->hasAttribute('class')) { + assertType('DOMNamedNodeMap', $element->attributes); + $attribute = $element->getAttributeNode('class'); + assertType(DOMAttr::class, $attribute); + assertType('string', $attribute->value); + } else { + assertType('DOMNamedNodeMap|null', $element->attributes); + } + } + + public function element_node_attribute_fetch_via_attributes_property(DOMElement $element): void + { + if ($element->hasAttribute('class')) { + assertType('DOMNamedNodeMap', $element->attributes); + $attribute = $element->attributes->getNamedItem('class'); + if ($attribute === null) { + return; + } + assertType(DOMAttr::class, $attribute); + assertType('string', $attribute->value); + } else { + assertType('DOMNamedNodeMap|null', $element->attributes); } } } diff --git a/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php b/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php index 77173dddf8..53f7ccd263 100644 --- a/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php +++ b/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php @@ -8,4 +8,31 @@ function basic_node(\DOMNode $node): void { } else { assertType('null', $node->attributes); } -}; +} + +function element_node(\DOMElement $element): void +{ + if ($element->hasAttribute('class')) { + assertType(DOMNamedNodeMap::class . '<' . DOMAttr::class . '>', $element->attributes); + $attribute = $element->getAttributeNode('class'); + assertType(DOMAttr::class, $attribute); + assertType('string', $attribute->value); + } else { + assertType('DOMNamedNodeMap::class . '<' . DOMAttr::class . '>'|null', $element->attributes); + } +} + +function element_node_attribute_fetch_via_attributes_property(\DOMElement $element): void +{ + if ($element->hasAttribute('class')) { + assertType(DOMNamedNodeMap::class . '<' . DOMAttr::class . '>', $element->attributes); + $attribute = $element->attributes->getNamedItem('class'); + if ($attribute === null) { + return; + } + assertType(DOMAttr::class, $attribute); + assertType('string', $attribute->value); + } else { + assertType('DOMNamedNodeMap::class . '<' . DOMAttr::class . '>'|null', $element->attributes); + } +} From f8476d45f7c86667f5d62e11fbaaaf231d42a455 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sun, 8 Dec 2024 22:21:09 +0000 Subject: [PATCH 11/14] Fix issues by copying code from Ondrej --- stubs/dom.stub | 12 ++++++-- .../Analyser/nsrt/DOMLegacyNamedNodeNap.php | 2 ++ .../data/dom-legacy-ext-template-nodes.php | 28 +++++-------------- 3 files changed, 18 insertions(+), 24 deletions(-) diff --git a/stubs/dom.stub b/stubs/dom.stub index a7935e2de5..755247c942 100644 --- a/stubs/dom.stub +++ b/stubs/dom.stub @@ -37,7 +37,6 @@ class DOMNode /** * @phpstan-assert-if-true DOMNamedNodeMap $this->attributes - * @phpstan-assert-if-false null $this->attributes */ public function hasAttributes(): bool {} } @@ -48,6 +47,12 @@ class DOMElement extends DOMNode /** @var DOMDocument */ public $ownerDocument; + /** + * @readonly + * @var DOMNamedNodeMap|null + */ + public $attributes; + /** * @param string $name * @return DOMNodeList @@ -64,11 +69,12 @@ class DOMElement extends DOMNode /** * @template-covariant T of string * @param T $qualifiedName - * @phpstan-assert-if-true DOMAttr|DOMNameSpaceNode $this->getAttributeNode($qualifiedName) + * @phpstan-assert-if-true =DOMAttr $this->getAttributeNode($qualifiedName) + * @phpstan-assert-if-true =DOMNamedNodeMap $this->attributes */ public function hasAttribute(string $qualifiedName): bool {} - public function getAttributeNode(string $qualifiedName): DOMAttr|DOMNameSpaceNode|false {} + public function getAttributeNode(string $qualifiedName): DOMAttr|false {} } /** diff --git a/tests/PHPStan/Analyser/nsrt/DOMLegacyNamedNodeNap.php b/tests/PHPStan/Analyser/nsrt/DOMLegacyNamedNodeNap.php index dacab6d5b2..69bc94a93e 100644 --- a/tests/PHPStan/Analyser/nsrt/DOMLegacyNamedNodeNap.php +++ b/tests/PHPStan/Analyser/nsrt/DOMLegacyNamedNodeNap.php @@ -22,6 +22,7 @@ public function basic_node(DOMNode $node): void { public function element_node(DOMElement $element): void { + assertType('DOMNamedNodeMap|null', $element->attributes); if ($element->hasAttribute('class')) { assertType('DOMNamedNodeMap', $element->attributes); $attribute = $element->getAttributeNode('class'); @@ -34,6 +35,7 @@ public function element_node(DOMElement $element): void public function element_node_attribute_fetch_via_attributes_property(DOMElement $element): void { + assertType('DOMNamedNodeMap|null', $element->attributes); if ($element->hasAttribute('class')) { assertType('DOMNamedNodeMap', $element->attributes); $attribute = $element->attributes->getNamedItem('class'); diff --git a/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php b/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php index 53f7ccd263..ad39d72ab4 100644 --- a/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php +++ b/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php @@ -1,38 +1,24 @@ hasAttributes()) { - assertType(DOMNamedNodeMap::class . '<' . DOMAttr::class . '>', $node->attributes); - } else { - assertType('null', $node->attributes); - } + var_dump($node->attributes); } function element_node(\DOMElement $element): void { if ($element->hasAttribute('class')) { - assertType(DOMNamedNodeMap::class . '<' . DOMAttr::class . '>', $element->attributes); $attribute = $element->getAttributeNode('class'); - assertType(DOMAttr::class, $attribute); - assertType('string', $attribute->value); - } else { - assertType('DOMNamedNodeMap::class . '<' . DOMAttr::class . '>'|null', $element->attributes); + echo $attribute->value; } } function element_node_attribute_fetch_via_attributes_property(\DOMElement $element): void { - if ($element->hasAttribute('class')) { - assertType(DOMNamedNodeMap::class . '<' . DOMAttr::class . '>', $element->attributes); - $attribute = $element->attributes->getNamedItem('class'); - if ($attribute === null) { - return; - } - assertType(DOMAttr::class, $attribute); - assertType('string', $attribute->value); - } else { - assertType('DOMNamedNodeMap::class . '<' . DOMAttr::class . '>'|null', $element->attributes); + $attribute = $element->attributes->getNamedItem('class'); + if ($attribute === null) { + return; } + echo $attribute->value; } From eab5a734b017706a926fd0924a1ca790381deb57 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sun, 8 Dec 2024 22:23:21 +0000 Subject: [PATCH 12/14] Remove junk --- stubs/dom.stub | 5 ----- 1 file changed, 5 deletions(-) diff --git a/stubs/dom.stub b/stubs/dom.stub index 755247c942..9f2b78b7da 100644 --- a/stubs/dom.stub +++ b/stubs/dom.stub @@ -67,14 +67,9 @@ class DOMElement extends DOMNode public function getElementsByTagNameNS ($namespaceURI, $localName) {} /** - * @template-covariant T of string - * @param T $qualifiedName - * @phpstan-assert-if-true =DOMAttr $this->getAttributeNode($qualifiedName) * @phpstan-assert-if-true =DOMNamedNodeMap $this->attributes */ public function hasAttribute(string $qualifiedName): bool {} - - public function getAttributeNode(string $qualifiedName): DOMAttr|false {} } /** From e49a3b15f44b6885d3188a3274e84787a331de54 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Wed, 11 Dec 2024 13:51:06 +0000 Subject: [PATCH 13/14] Add stuff and fix some --- stubs/dom.stub | 9 +++++++-- tests/PHPStan/Analyser/nsrt/DOMLegacyNamedNodeNap.php | 11 ++++------- .../Properties/data/dom-legacy-ext-template-nodes.php | 9 +++++++++ 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/stubs/dom.stub b/stubs/dom.stub index 9f2b78b7da..209e07230c 100644 --- a/stubs/dom.stub +++ b/stubs/dom.stub @@ -49,7 +49,7 @@ class DOMElement extends DOMNode /** * @readonly - * @var DOMNamedNodeMap|null + * @var DOMNamedNodeMap */ public $attributes; @@ -67,9 +67,14 @@ class DOMElement extends DOMNode public function getElementsByTagNameNS ($namespaceURI, $localName) {} /** - * @phpstan-assert-if-true =DOMNamedNodeMap $this->attributes + * @phpstan-assert-if-true non-empty-string $this->getAttribute($qualifiedName) + * @phpstan-assert-if-true DOMAttr|DOMNameSpaceNode $this->getAttributeNode($qualifiedName) */ public function hasAttribute(string $qualifiedName): bool {} + + public function getAttribute(string $qualifiedName): string {} + + public function getAttributeNode(string $qualifiedName): DOMAttr|DOMNameSpaceNode|false {} } /** diff --git a/tests/PHPStan/Analyser/nsrt/DOMLegacyNamedNodeNap.php b/tests/PHPStan/Analyser/nsrt/DOMLegacyNamedNodeNap.php index 69bc94a93e..8d2a406f8c 100644 --- a/tests/PHPStan/Analyser/nsrt/DOMLegacyNamedNodeNap.php +++ b/tests/PHPStan/Analyser/nsrt/DOMLegacyNamedNodeNap.php @@ -22,30 +22,27 @@ public function basic_node(DOMNode $node): void { public function element_node(DOMElement $element): void { - assertType('DOMNamedNodeMap|null', $element->attributes); + assertType('DOMNamedNodeMap', $element->attributes); if ($element->hasAttribute('class')) { - assertType('DOMNamedNodeMap', $element->attributes); $attribute = $element->getAttributeNode('class'); assertType(DOMAttr::class, $attribute); assertType('string', $attribute->value); } else { - assertType('DOMNamedNodeMap|null', $element->attributes); + $attribute = $element->getAttributeNode('class'); + assertType('false', $attribute); } } public function element_node_attribute_fetch_via_attributes_property(DOMElement $element): void { - assertType('DOMNamedNodeMap|null', $element->attributes); + assertType('DOMNamedNodeMap', $element->attributes); if ($element->hasAttribute('class')) { - assertType('DOMNamedNodeMap', $element->attributes); $attribute = $element->attributes->getNamedItem('class'); if ($attribute === null) { return; } assertType(DOMAttr::class, $attribute); assertType('string', $attribute->value); - } else { - assertType('DOMNamedNodeMap|null', $element->attributes); } } } diff --git a/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php b/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php index ad39d72ab4..262510c714 100644 --- a/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php +++ b/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php @@ -22,3 +22,12 @@ function element_node_attribute_fetch_via_attributes_property(\DOMElement $eleme } echo $attribute->value; } + +function element_node_attribute_fetch_via_getAttributeNode(\DOMElement $element): void +{ + $attribute = $element->getAttributeNode('class'); + if ($attribute === null) { + return; + } + echo $attribute->value; +} From 578fc00e33239412df347698e48f4a3da316d718 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Thu, 12 Dec 2024 22:33:25 +0000 Subject: [PATCH 14/14] Remove assertions depending on param --- stubs/dom.stub | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/stubs/dom.stub b/stubs/dom.stub index 209e07230c..b219756b1d 100644 --- a/stubs/dom.stub +++ b/stubs/dom.stub @@ -65,16 +65,6 @@ class DOMElement extends DOMNode * @return DOMNodeList */ public function getElementsByTagNameNS ($namespaceURI, $localName) {} - - /** - * @phpstan-assert-if-true non-empty-string $this->getAttribute($qualifiedName) - * @phpstan-assert-if-true DOMAttr|DOMNameSpaceNode $this->getAttributeNode($qualifiedName) - */ - public function hasAttribute(string $qualifiedName): bool {} - - public function getAttribute(string $qualifiedName): string {} - - public function getAttributeNode(string $qualifiedName): DOMAttr|DOMNameSpaceNode|false {} } /**